]> git.proxmox.com Git - libgit2.git/blame - src/refs.c
git_reference_rename: cleanup reference renaming
[libgit2.git] / src / refs.c
CommitLineData
9282e921 1/*
bb742ede 2 * Copyright (C) 2009-2011 the libgit2 contributors
9282e921 3 *
bb742ede
VM
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
9282e921 6 */
7
8#include "refs.h"
9#include "hash.h"
10#include "repository.h"
11#include "fileops.h"
01ad7b3a 12#include "pack.h"
9282e921 13
87d3acf4
VM
14#include <git2/tag.h>
15#include <git2/object.h>
16
9282e921 17#define MAX_NESTING_LEVEL 5
18
86194b24 19typedef struct {
a46ec457
MS
20 git_repository *owner;
21 char *name;
22 unsigned int type;
23 time_t mtime;
24} reference;
25
26typedef struct {
27 reference ref;
86194b24
VM
28 git_oid oid;
29 git_oid peel_target;
30} reference_oid;
31
32typedef struct {
a46ec457 33 reference ref;
86194b24
VM
34 char *target;
35} reference_symbolic;
36
9282e921 37static const int default_table_size = 32;
38
fc658755 39static uint32_t reftable_hash(const void *key, int hash_id)
9282e921 40{
fc658755
VM
41 static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = {
42 2147483647,
43 0x5d20bb23,
932d1baf 44 0x7daaab3c
fc658755 45 };
9282e921 46
fc658755 47 return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]);
9282e921 48}
49
a46ec457
MS
50static void reference_free(reference *reference);
51static int reference_create(reference **ref_out, git_repository *repo, const char *name, git_rtype type);
c4982328 52static int reference_read(git_fbuffer *file_content, time_t *mtime, const char *repo_path, const char *ref_name, int *updated);
87d3acf4 53
a46ec457
MS
54static int reference_set_target(reference *ref, const char *target);
55static int reference_set_oid(reference *ref, const git_oid *oid);
56
57static int reference_delete(reference *ref);
58
87d3acf4 59/* loose refs */
a46ec457
MS
60static int loose_parse_symbolic(reference *ref, git_fbuffer *file_content);
61static int loose_parse_oid(reference *ref, git_fbuffer *file_content);
62static int loose_lookup(reference **ref_out, git_repository *repo, const char *name, int skip_symbolic);
63static int loose_write(reference *ref);
64static int loose_update(reference *ref);
87d3acf4
VM
65
66/* packed refs */
87d3acf4
VM
67static int packed_parse_peel(reference_oid *tag_ref, const char **buffer_out, const char *buffer_end);
68static int packed_parse_oid(reference_oid **ref_out, git_repository *repo, const char **buffer_out, const char *buffer_end);
69static int packed_load(git_repository *repo);
70static int packed_loadloose(git_repository *repository);
71static int packed_write_ref(reference_oid *ref, git_filebuf *file);
72static int packed_find_peel(reference_oid *ref);
73static int packed_remove_loose(git_repository *repo, git_vector *packing_list);
74static int packed_sort(const void *a, const void *b);
75static int packed_write(git_repository *repo);
76
95cde17c 77/* internal helpers */
1b6d8163 78static int reference_available(git_repository *repo, const char *ref, const char *old_ref);
a46ec457 79static int reference_lookup(reference **out, git_repository *repo, const char *name);
95cde17c 80
87d3acf4
VM
81/* name normalization */
82static int check_valid_ref_char(char ch);
3101a3e5 83static int normalize_name(char *buffer_out, size_t out_size, const char *name, int is_oid_ref);
87d3acf4 84
a46ec457
MS
85/* reference transition */
86static git_reference * reference_create_external(reference *ref);
87static reference * reference_get_internal(git_reference *ref);
88
89
90
91static void reference_free(reference *reference)
9282e921 92{
2f8a8ab2
VM
93 if (reference == NULL)
94 return;
9282e921 95
2f8a8ab2 96 if (reference->name)
3286c408 97 git__free(reference->name);
9282e921 98
2f8a8ab2 99 if (reference->type == GIT_REF_SYMBOLIC)
3286c408 100 git__free(((reference_symbolic *)reference)->target);
9282e921 101
3286c408 102 git__free(reference);
9282e921 103}
104
87d3acf4 105static int reference_create(
a46ec457 106 reference **ref_out,
87d3acf4
VM
107 git_repository *repo,
108 const char *name,
109 git_rtype type)
110{
3101a3e5 111 char normalized[GIT_REFNAME_MAX];
86194b24 112 int error = GIT_SUCCESS, size;
a46ec457 113 reference *reference = NULL;
9282e921 114
1d8cc731 115 assert(ref_out && repo && name);
116
86194b24
VM
117 if (type == GIT_REF_SYMBOLIC)
118 size = sizeof(reference_symbolic);
119 else if (type == GIT_REF_OID)
120 size = sizeof(reference_oid);
121 else
fa59f18d
VM
122 return git__throw(GIT_EINVALIDARGS,
123 "Invalid reference type. Use either GIT_REF_OID or GIT_REF_SYMBOLIC as type specifier");
9282e921 124
86194b24 125 reference = git__malloc(size);
9282e921 126 if (reference == NULL)
127 return GIT_ENOMEM;
128
86194b24 129 memset(reference, 0x0, size);
2f8a8ab2 130 reference->owner = repo;
1d8cc731 131 reference->type = type;
132
3101a3e5 133 error = normalize_name(normalized, sizeof(normalized), name, (type & GIT_REF_OID));
1d8cc731 134 if (error < GIT_SUCCESS)
135 goto cleanup;
136
137 reference->name = git__strdup(normalized);
138 if (reference->name == NULL) {
139 error = GIT_ENOMEM;
140 goto cleanup;
141 }
9282e921 142
2f8a8ab2 143 *ref_out = reference;
1d8cc731 144
5bdf7b9f 145 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference");
1d8cc731 146
147cleanup:
148 reference_free(reference);
5bdf7b9f 149 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference");
1d8cc731 150}
151
c4982328 152static int reference_read(git_fbuffer *file_content, time_t *mtime, const char *repo_path, const char *ref_name, int *updated)
7341bf87 153{
7341bf87
VM
154 char path[GIT_PATH_MAX];
155
c4982328
CMN
156 assert(file_content && repo_path && ref_name);
157
7341bf87 158 /* Determine the full path of the file */
f79026b4 159 git_path_join(path, repo_path, ref_name);
7341bf87 160
c4982328 161 return git_futils_readbuffer_updated(file_content, path, mtime, updated);
7341bf87
VM
162}
163
a46ec457
MS
164static git_reference * reference_create_external(reference *ref)
165{
166 size_t size = sizeof(git_reference);
167 git_reference *out;
168
169 out = git__malloc(size);
170 if (out == NULL)
171 return NULL;
172
173 memset(out, 0x0, size);
174
175 out->owner = ref->owner;
176
177 out->name = git__strdup(ref->name);
178 if (out->name == NULL) {
179 git__free(out);
180 return NULL;
181 }
182
183 return out;
184}
185
186static reference * reference_get_internal(git_reference *ref)
187{
188 reference *out = NULL;
1d8cc731 189
a46ec457 190 reference_lookup(&out, ref->owner, ref->name);
1d8cc731 191
a46ec457
MS
192 return out;
193}
9282e921 194
a46ec457 195static int loose_update(reference *ref)
7341bf87 196{
c4982328 197 int error, updated;
f79026b4 198 git_fbuffer ref_file = GIT_FBUFFER_INIT;
7341bf87
VM
199
200 if (ref->type & GIT_REF_PACKED)
201 return packed_load(ref->owner);
202
a46ec457 203
c4982328 204/* error = reference_read(NULL, &ref_time, ref->owner->path_repository, ref->name);
7341bf87
VM
205 if (error < GIT_SUCCESS)
206 goto cleanup;
207
208 if (ref_time == ref->mtime)
209 return GIT_SUCCESS;
c4982328
CMN
210*/
211 error = reference_read(&ref_file, &ref->mtime, ref->owner->path_repository, ref->name, &updated);
7341bf87
VM
212 if (error < GIT_SUCCESS)
213 goto cleanup;
214
c4982328
CMN
215 if (!updated)
216 goto cleanup;
217
7341bf87
VM
218 if (ref->type == GIT_REF_SYMBOLIC)
219 error = loose_parse_symbolic(ref, &ref_file);
220 else if (ref->type == GIT_REF_OID)
221 error = loose_parse_oid(ref, &ref_file);
222 else
fa59f18d
VM
223 error = git__throw(GIT_EOBJCORRUPTED,
224 "Invalid reference type (%d) for loose reference", ref->type);
7341bf87 225
7341bf87 226cleanup:
c4982328 227 git_futils_freebuffer(&ref_file);
7341bf87
VM
228 if (error != GIT_SUCCESS) {
229 reference_free(ref);
230 git_hashtable_remove(ref->owner->references.loose_cache, ref->name);
231 }
232
5bdf7b9f 233 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to update loose reference");
7341bf87
VM
234}
235
a46ec457 236static int loose_parse_symbolic(reference *ref, git_fbuffer *file_content)
2f8a8ab2 237{
ddc9e79a 238 const unsigned int header_len = strlen(GIT_SYMREF);
2f8a8ab2
VM
239 const char *refname_start;
240 char *eol;
86194b24 241 reference_symbolic *ref_sym;
9282e921 242
2f8a8ab2 243 refname_start = (const char *)file_content->data;
86194b24 244 ref_sym = (reference_symbolic *)ref;
9282e921 245
2f8a8ab2 246 if (file_content->len < (header_len + 1))
fa59f18d
VM
247 return git__throw(GIT_EOBJCORRUPTED,
248 "Failed to parse loose reference. Object too short");
9282e921 249
932d1baf 250 /*
2f8a8ab2 251 * Assume we have already checked for the header
932d1baf 252 * before calling this function
2f8a8ab2 253 */
9282e921 254
2f8a8ab2 255 refname_start += header_len;
9282e921 256
3286c408 257 git__free(ref_sym->target);
86194b24
VM
258 ref_sym->target = git__strdup(refname_start);
259 if (ref_sym->target == NULL)
32054c24 260 return GIT_ENOMEM;
9282e921 261
2f8a8ab2 262 /* remove newline at the end of file */
86194b24 263 eol = strchr(ref_sym->target, '\n');
ff5873ad 264 if (eol == NULL)
fa59f18d
VM
265 return git__throw(GIT_EOBJCORRUPTED,
266 "Failed to parse loose reference. Missing EOL");
ff5873ad
VM
267
268 *eol = '\0';
269 if (eol[-1] == '\r')
270 eol[-1] = '\0';
9282e921 271
2f8a8ab2 272 return GIT_SUCCESS;
9282e921 273}
274
a46ec457 275static int loose_parse_oid(reference *ref, git_fbuffer *file_content)
2f8a8ab2 276{
fa59f18d 277 int error;
86194b24 278 reference_oid *ref_oid;
2f8a8ab2 279 char *buffer;
86194b24 280
2f8a8ab2 281 buffer = (char *)file_content->data;
86194b24 282 ref_oid = (reference_oid *)ref;
9282e921 283
2f8a8ab2 284 /* File format: 40 chars (OID) + newline */
ff5873ad 285 if (file_content->len < GIT_OID_HEXSZ + 1)
fa59f18d
VM
286 return git__throw(GIT_EOBJCORRUPTED,
287 "Failed to parse loose reference. Reference too short");
9282e921 288
fa48608e 289 if ((error = git_oid_fromstr(&ref_oid->oid, buffer)) < GIT_SUCCESS)
fa59f18d 290 return git__rethrow(GIT_EOBJCORRUPTED, "Failed to parse loose reference.");
9282e921 291
ff5873ad
VM
292 buffer = buffer + GIT_OID_HEXSZ;
293 if (*buffer == '\r')
294 buffer++;
295
296 if (*buffer != '\n')
fa59f18d
VM
297 return git__throw(GIT_EOBJCORRUPTED,
298 "Failed to parse loose reference. Missing EOL");
9282e921 299
2f8a8ab2 300 return GIT_SUCCESS;
9282e921 301}
302
9282e921 303
00571828
VM
304static git_rtype loose_guess_rtype(const char *full_path)
305{
f79026b4 306 git_fbuffer ref_file = GIT_FBUFFER_INIT;
00571828
VM
307 git_rtype type;
308
309 type = GIT_REF_INVALID;
310
f79026b4 311 if (git_futils_readbuffer(&ref_file, full_path) == GIT_SUCCESS) {
00571828
VM
312 if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0)
313 type = GIT_REF_SYMBOLIC;
314 else
315 type = GIT_REF_OID;
316 }
317
f79026b4 318 git_futils_freebuffer(&ref_file);
00571828
VM
319 return type;
320}
321
87d3acf4 322static int loose_lookup(
a46ec457 323 reference **ref_out,
932d1baf 324 git_repository *repo,
87d3acf4
VM
325 const char *name,
326 int skip_symbolic)
9282e921 327{
328 int error = GIT_SUCCESS;
f79026b4 329 git_fbuffer ref_file = GIT_FBUFFER_INIT;
a46ec457 330 reference *ref = NULL;
c4982328 331 time_t ref_time = 0;
9282e921 332
2f8a8ab2 333 *ref_out = NULL;
9282e921 334
c4982328 335 error = reference_read(&ref_file, &ref_time, repo->path_repository, name, NULL);
9282e921 336 if (error < GIT_SUCCESS)
337 goto cleanup;
338
1d8cc731 339 if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) {
87d3acf4
VM
340 if (skip_symbolic)
341 return GIT_SUCCESS;
342
32054c24 343 error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC);
1d8cc731 344 if (error < GIT_SUCCESS)
345 goto cleanup;
9282e921 346
87d3acf4 347 error = loose_parse_symbolic(ref, &ref_file);
1d8cc731 348 } else {
32054c24 349 error = reference_create(&ref, repo, name, GIT_REF_OID);
1d8cc731 350 if (error < GIT_SUCCESS)
351 goto cleanup;
352
87d3acf4 353 error = loose_parse_oid(ref, &ref_file);
1d8cc731 354 }
355
ff5873ad
VM
356 if (error < GIT_SUCCESS)
357 goto cleanup;
358
7341bf87 359 ref->mtime = ref_time;
2f8a8ab2 360 *ref_out = ref;
f79026b4 361 git_futils_freebuffer(&ref_file);
2f8a8ab2 362 return GIT_SUCCESS;
9282e921 363
364cleanup:
f79026b4 365 git_futils_freebuffer(&ref_file);
2f8a8ab2 366 reference_free(ref);
5bdf7b9f 367 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to lookup loose reference");
9282e921 368}
369
a46ec457 370static int loose_write(reference *ref)
87d3acf4
VM
371{
372 git_filebuf file;
373 char ref_path[GIT_PATH_MAX];
e7e0e20f 374 int error;
7341bf87 375 struct stat st;
87d3acf4 376
f79026b4 377 git_path_join(ref_path, ref->owner->path_repository, ref->name);
87d3acf4 378
55ffebe3 379 if ((error = git_filebuf_open(&file, ref_path, GIT_FILEBUF_FORCE)) < GIT_SUCCESS)
5bdf7b9f 380 return git__rethrow(error, "Failed to write loose reference");
87d3acf4
VM
381
382 if (ref->type & GIT_REF_OID) {
383 reference_oid *ref_oid = (reference_oid *)ref;
e7e0e20f 384 char oid[GIT_OID_HEXSZ + 1];
87d3acf4 385
e7e0e20f 386 memset(oid, 0x0, sizeof(oid));
87d3acf4 387
e7e0e20f
CMN
388 git_oid_fmt(oid, &ref_oid->oid);
389 error = git_filebuf_printf(&file, "%s\n", oid);
390 if (error < GIT_SUCCESS)
391 goto unlock;
87d3acf4
VM
392
393 } else if (ref->type & GIT_REF_SYMBOLIC) { /* GIT_REF_SYMBOLIC */
394 reference_symbolic *ref_sym = (reference_symbolic *)ref;
395
e7e0e20f 396 error = git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref_sym->target);
87d3acf4 397 } else {
fa59f18d 398 error = git__throw(GIT_EOBJCORRUPTED, "Failed to write reference. Invalid reference type");
87d3acf4
VM
399 goto unlock;
400 }
401
01ad7b3a 402 error = git_filebuf_commit(&file, GIT_REFS_FILE_MODE);
87d3acf4 403
f79026b4 404 if (p_stat(ref_path, &st) == GIT_SUCCESS)
7341bf87
VM
405 ref->mtime = st.st_mtime;
406
5bdf7b9f 407 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference");
87d3acf4
VM
408
409unlock:
410 git_filebuf_cleanup(&file);
5bdf7b9f 411 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference");
87d3acf4
VM
412}
413
87d3acf4 414static int packed_parse_peel(
86194b24 415 reference_oid *tag_ref,
932d1baf 416 const char **buffer_out,
2f8a8ab2 417 const char *buffer_end)
9282e921 418{
2f8a8ab2
VM
419 const char *buffer = *buffer_out + 1;
420
421 assert(buffer[-1] == '^');
9282e921 422
423 /* Ensure it's not the first entry of the file */
2f8a8ab2 424 if (tag_ref == NULL)
5bdf7b9f 425 return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Reference is the first entry of the file");
9282e921 426
427 /* Ensure reference is a tag */
86194b24 428 if (git__prefixcmp(tag_ref->ref.name, GIT_REFS_TAGS_DIR) != 0)
5bdf7b9f 429 return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Reference is not a tag");
9282e921 430
2f8a8ab2 431 if (buffer + GIT_OID_HEXSZ >= buffer_end)
5bdf7b9f 432 return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Buffer too small");
9282e921 433
2f8a8ab2 434 /* Is this a valid object id? */
fa48608e 435 if (git_oid_fromstr(&tag_ref->peel_target, buffer) < GIT_SUCCESS)
5bdf7b9f 436 return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Not a valid object ID");
2f8a8ab2 437
ff5873ad
VM
438 buffer = buffer + GIT_OID_HEXSZ;
439 if (*buffer == '\r')
440 buffer++;
441
442 if (*buffer != '\n')
5bdf7b9f 443 return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Buffer not terminated correctly");
ff5873ad
VM
444
445 *buffer_out = buffer + 1;
86194b24 446 tag_ref->ref.type |= GIT_REF_HAS_PEEL;
2f8a8ab2 447
2f8a8ab2 448 return GIT_SUCCESS;
9282e921 449}
450
87d3acf4 451static int packed_parse_oid(
86194b24 452 reference_oid **ref_out,
2f8a8ab2
VM
453 git_repository *repo,
454 const char **buffer_out,
455 const char *buffer_end)
9282e921 456{
6c8b458d 457 git_reference *_ref = NULL;
e06dd9b6 458 reference_oid *ref = NULL;
2f8a8ab2
VM
459
460 const char *buffer = *buffer_out;
461 const char *refname_begin, *refname_end;
462
9282e921 463 int error = GIT_SUCCESS;
9282e921 464 int refname_len;
3101a3e5 465 char refname[GIT_REFNAME_MAX];
1d8cc731 466 git_oid id;
9282e921 467
2f8a8ab2
VM
468 refname_begin = (buffer + GIT_OID_HEXSZ + 1);
469 if (refname_begin >= buffer_end ||
470 refname_begin[-1] != ' ') {
471 error = GIT_EPACKEDREFSCORRUPTED;
472 goto cleanup;
473 }
9282e921 474
2f8a8ab2 475 /* Is this a valid object id? */
fa48608e 476 if ((error = git_oid_fromstr(&id, buffer)) < GIT_SUCCESS)
2f8a8ab2 477 goto cleanup;
9282e921 478
2f8a8ab2
VM
479 refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
480 if (refname_end == NULL) {
481 error = GIT_EPACKEDREFSCORRUPTED;
482 goto cleanup;
483 }
9282e921 484
2f8a8ab2 485 refname_len = refname_end - refname_begin;
9282e921 486
1d8cc731 487 memcpy(refname, refname_begin, refname_len);
488 refname[refname_len] = 0;
9282e921 489
1d8cc731 490 if (refname[refname_len - 1] == '\r')
491 refname[refname_len - 1] = 0;
9282e921 492
a46ec457 493 error = reference_create((reference **)&_ref, repo, refname, GIT_REF_OID);
1d8cc731 494 if (error < GIT_SUCCESS)
495 goto cleanup;
496
6c8b458d
VM
497 ref = (reference_oid *)_ref;
498
86194b24
VM
499 git_oid_cpy(&ref->oid, &id);
500 ref->ref.type |= GIT_REF_PACKED;
9282e921 501
2f8a8ab2 502 *ref_out = ref;
9282e921 503 *buffer_out = refname_end + 1;
504
2f8a8ab2
VM
505 return GIT_SUCCESS;
506
507cleanup:
a46ec457 508 reference_free((reference *)ref);
5bdf7b9f 509 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse OID of packed reference");
9282e921 510}
511
87d3acf4 512static int packed_load(git_repository *repo)
9282e921 513{
c4982328 514 int error = GIT_SUCCESS, updated;
f79026b4 515 git_fbuffer packfile = GIT_FBUFFER_INIT;
2f8a8ab2 516 const char *buffer_start, *buffer_end;
87d3acf4
VM
517 git_refcache *ref_cache = &repo->references;
518
c4982328
CMN
519 /* First we make sure we have allocated the hash table */
520 if (ref_cache->packfile == NULL) {
7341bf87 521 ref_cache->packfile = git_hashtable_alloc(
932d1baf 522 default_table_size,
7341bf87 523 reftable_hash,
d1f34693 524 (git_hash_keyeq_ptr)(&git__strcmp_cb));
7341bf87 525
c4982328
CMN
526 if (ref_cache->packfile == NULL) {
527 error = GIT_ENOMEM;
528 goto cleanup;
529 }
7341bf87
VM
530 }
531
c4982328 532 error = reference_read(&packfile, &ref_cache->packfile_time,
87d9869f 533 repo->path_repository, GIT_PACKEDREFS_FILE, &updated);
87d3acf4 534
c4982328
CMN
535 /*
536 * If we couldn't find the file, we need to clear the table and
537 * return. On any other error, we return that error. If everything
538 * went fine and the file wasn't updated, then there's nothing new
539 * for us here, so just return. Anything else means we need to
540 * refresh the packed refs.
541 */
542 if (error == GIT_ENOTFOUND) {
543 git_hashtable_clear(ref_cache->packfile);
87d3acf4 544 return GIT_SUCCESS;
c4982328
CMN
545 } else if (error < GIT_SUCCESS) {
546 return git__rethrow(error, "Failed to read packed refs");
547 } else if (!updated) {
548 return GIT_SUCCESS;
549 }
2f8a8ab2 550
c4982328
CMN
551 /*
552 * At this point, we want to refresh the packed refs. We already
553 * have the contents in our buffer.
554 */
555
556 git_hashtable_clear(ref_cache->packfile);
9282e921 557
2f8a8ab2
VM
558 buffer_start = (const char *)packfile.data;
559 buffer_end = (const char *)(buffer_start) + packfile.len;
9282e921 560
7c8a7b91
VM
561 while (buffer_start < buffer_end && buffer_start[0] == '#') {
562 buffer_start = strchr(buffer_start, '\n');
563 if (buffer_start == NULL) {
564 error = GIT_EPACKEDREFSCORRUPTED;
565 goto cleanup;
566 }
ddc9e79a 567 buffer_start++;
7c8a7b91 568 }
ddc9e79a 569
9282e921 570 while (buffer_start < buffer_end) {
86194b24 571 reference_oid *ref = NULL;
2f8a8ab2 572
87d3acf4 573 error = packed_parse_oid(&ref, repo, &buffer_start, buffer_end);
2f8a8ab2
VM
574 if (error < GIT_SUCCESS)
575 goto cleanup;
576
9282e921 577 if (buffer_start[0] == '^') {
87d3acf4 578 error = packed_parse_peel(ref, &buffer_start, buffer_end);
9282e921 579 if (error < GIT_SUCCESS)
580 goto cleanup;
2f8a8ab2 581 }
9282e921 582
87d3acf4 583 error = git_hashtable_insert(ref_cache->packfile, ref->ref.name, ref);
2f8a8ab2 584 if (error < GIT_SUCCESS) {
a46ec457 585 reference_free((reference *)ref);
9282e921 586 goto cleanup;
2f8a8ab2
VM
587 }
588 }
9282e921 589
f79026b4 590 git_futils_freebuffer(&packfile);
7341bf87
VM
591 return GIT_SUCCESS;
592
2f8a8ab2 593cleanup:
7341bf87
VM
594 git_hashtable_free(ref_cache->packfile);
595 ref_cache->packfile = NULL;
f79026b4 596 git_futils_freebuffer(&packfile);
5bdf7b9f 597 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to load packed references");
2f8a8ab2 598}
9282e921 599
00571828 600
00571828 601struct dirent_list_data {
7ad96e51 602 git_repository *repo;
00571828
VM
603 size_t repo_path_len;
604 unsigned int list_flags;
09e8de0f
VM
605
606 int (*callback)(const char *, void *);
607 void *callback_payload;
00571828
VM
608};
609
610static int _dirent_loose_listall(void *_data, char *full_path)
611{
612 struct dirent_list_data *data = (struct dirent_list_data *)_data;
7ad96e51 613 char *file_path = full_path + data->repo_path_len;
00571828 614
f79026b4
VM
615 if (git_futils_isdir(full_path) == GIT_SUCCESS)
616 return git_futils_direach(full_path, GIT_PATH_MAX, _dirent_loose_listall, _data);
00571828 617
7ad96e51 618 /* do not add twice a reference that exists already in the packfile */
b5abb881
VM
619 if ((data->list_flags & GIT_REF_PACKED) != 0 &&
620 git_hashtable_lookup(data->repo->references.packfile, file_path) != NULL)
7ad96e51
VM
621 return GIT_SUCCESS;
622
09e8de0f
VM
623 if (data->list_flags != GIT_REF_LISTALL) {
624 if ((data->list_flags & loose_guess_rtype(full_path)) == 0)
625 return GIT_SUCCESS; /* we are filtering out this reference */
626 }
00571828 627
09e8de0f 628 return data->callback(file_path, data->callback_payload);
00571828
VM
629}
630
87d3acf4 631static int _dirent_loose_load(void *data, char *full_path)
2f8a8ab2 632{
87d3acf4 633 git_repository *repository = (git_repository *)data;
6c8b458d 634 void *old_ref = NULL;
a46ec457 635 reference *ref;
87d3acf4
VM
636 char *file_path;
637 int error;
86194b24 638
f79026b4
VM
639 if (git_futils_isdir(full_path) == GIT_SUCCESS)
640 return git_futils_direach(full_path, GIT_PATH_MAX, _dirent_loose_load, repository);
9282e921 641
87d3acf4 642 file_path = full_path + strlen(repository->path_repository);
a46ec457
MS
643 error = loose_lookup(&ref, repository, file_path, 1);
644 if (error == GIT_SUCCESS && ref != NULL) {
645 ref->type |= GIT_REF_PACKED;
86194b24 646
a46ec457
MS
647 if (git_hashtable_insert2(repository->references.packfile, ref->name, ref, &old_ref) < GIT_SUCCESS) {
648 reference_free(ref);
87d3acf4
VM
649 return GIT_ENOMEM;
650 }
651
652 if (old_ref != NULL)
a46ec457 653 reference_free((reference *)old_ref);
87d3acf4 654 }
9282e921 655
5bdf7b9f 656 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to load loose dirent");
2f8a8ab2
VM
657}
658
87d3acf4
VM
659/*
660 * Load all the loose references from the repository
661 * into the in-memory Packfile, and build a vector with
662 * all the references so it can be written back to
663 * disk.
664 */
665static int packed_loadloose(git_repository *repository)
2f8a8ab2 666{
87d3acf4 667 char refs_path[GIT_PATH_MAX];
86194b24 668
87d3acf4
VM
669 /* the packfile must have been previously loaded! */
670 assert(repository->references.packfile);
9282e921 671
f79026b4 672 git_path_join(refs_path, repository->path_repository, GIT_REFS_DIR);
86194b24 673
87d3acf4
VM
674 /* Remove any loose references from the cache */
675 {
402a47a7 676 const void *GIT_UNUSED(_unused);
a46ec457 677 reference *reference;
9282e921 678
87d3acf4
VM
679 GIT_HASHTABLE_FOREACH(repository->references.loose_cache, _unused, reference,
680 reference_free(reference);
681 );
682 }
683
684 git_hashtable_clear(repository->references.loose_cache);
685
686 /*
687 * Load all the loose files from disk into the Packfile table.
688 * This will overwrite any old packed entries with their
932d1baf 689 * updated loose versions
87d3acf4 690 */
f79026b4 691 return git_futils_direach(refs_path, GIT_PATH_MAX, _dirent_loose_load, repository);
2f8a8ab2 692}
9282e921 693
87d3acf4
VM
694/*
695 * Write a single reference into a packfile
696 */
697static int packed_write_ref(reference_oid *ref, git_filebuf *file)
2f8a8ab2 698{
87d3acf4
VM
699 int error;
700 char oid[GIT_OID_HEXSZ + 1];
2f8a8ab2 701
87d3acf4
VM
702 git_oid_fmt(oid, &ref->oid);
703 oid[GIT_OID_HEXSZ] = 0;
2f8a8ab2 704
932d1baf 705 /*
87d3acf4
VM
706 * For references that peel to an object in the repo, we must
707 * write the resulting peel on a separate line, e.g.
708 *
709 * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
710 * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
711 *
712 * This obviously only applies to tags.
713 * The required peels have already been loaded into `ref->peel_target`.
714 */
715 if (ref->ref.type & GIT_REF_HAS_PEEL) {
716 char peel[GIT_OID_HEXSZ + 1];
717 git_oid_fmt(peel, &ref->peel_target);
718 peel[GIT_OID_HEXSZ] = 0;
719
720 error = git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->ref.name, peel);
721 } else {
722 error = git_filebuf_printf(file, "%s %s\n", oid, ref->ref.name);
723 }
724
5bdf7b9f 725 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write packed reference");
9282e921 726}
727
87d3acf4
VM
728/*
729 * Find out what object this reference resolves to.
730 *
932d1baf 731 * For references that point to a 'big' tag (e.g. an
87d3acf4
VM
732 * actual tag object on the repository), we need to
733 * cache on the packfile the OID of the object to
734 * which that 'big tag' is pointing to.
735 */
736static int packed_find_peel(reference_oid *ref)
9282e921 737{
d79f1da6 738 git_object *object;
87d3acf4 739 int error;
32054c24 740
87d3acf4
VM
741 if (ref->ref.type & GIT_REF_HAS_PEEL)
742 return GIT_SUCCESS;
9282e921 743
87d3acf4
VM
744 /*
745 * Only applies to tags, i.e. references
746 * in the /refs/tags folder
747 */
748 if (git__prefixcmp(ref->ref.name, GIT_REFS_TAGS_DIR) != 0)
749 return GIT_SUCCESS;
9282e921 750
87d3acf4 751 /*
d79f1da6 752 * Find the tagged object in the repository
87d3acf4 753 */
d79f1da6 754 error = git_object_lookup(&object, ref->ref.owner, &ref->oid, GIT_OBJ_ANY);
87d3acf4 755 if (error < GIT_SUCCESS)
5bdf7b9f 756 return git__throw(GIT_EOBJCORRUPTED, "Failed to find packed reference");
86194b24 757
87d3acf4 758 /*
d79f1da6
VM
759 * If the tagged object is a Tag object, we need to resolve it;
760 * if the ref is actually a 'weak' ref, we don't need to resolve
761 * anything.
87d3acf4 762 */
d79f1da6
VM
763 if (git_object_type(object) == GIT_OBJ_TAG) {
764 git_tag *tag = (git_tag *)object;
86194b24 765
d79f1da6
VM
766 /*
767 * Find the object pointed at by this tag
768 */
769 git_oid_cpy(&ref->peel_target, git_tag_target_oid(tag));
770 ref->ref.type |= GIT_REF_HAS_PEEL;
771
772 /*
773 * The reference has now cached the resolved OID, and is
774 * marked at such. When written to the packfile, it'll be
775 * accompanied by this resolved oid
776 */
777 }
87d3acf4 778
1648fbd3
VM
779 git_object_close(object);
780
87d3acf4 781 return GIT_SUCCESS;
2f8a8ab2 782}
9282e921 783
87d3acf4
VM
784/*
785 * Remove all loose references
786 *
787 * Once we have successfully written a packfile,
788 * all the loose references that were packed must be
789 * removed from disk.
790 *
791 * This is a dangerous method; make sure the packfile
792 * is well-written, because we are destructing references
793 * here otherwise.
794 */
795static int packed_remove_loose(git_repository *repo, git_vector *packing_list)
2f8a8ab2 796{
87d3acf4
VM
797 unsigned int i;
798 char full_path[GIT_PATH_MAX];
799 int error = GIT_SUCCESS;
a46ec457 800 reference *r;
87d3acf4
VM
801
802 for (i = 0; i < packing_list->length; ++i) {
a46ec457 803 reference *ref = git_vector_get(packing_list, i);
8f90ced5 804
805 /* Ensure the packed reference doesn't exist
806 * in a (more up-to-date?) state as a loose reference
807 */
a46ec457
MS
808 r = git_hashtable_lookup(ref->owner->references.loose_cache, ref->name);
809 if (r != NULL)
8f90ced5 810 continue;
811
f79026b4 812 git_path_join(full_path, repo->path_repository, ref->name);
87d3acf4 813
f79026b4
VM
814 if (git_futils_exists(full_path) == GIT_SUCCESS &&
815 p_unlink(full_path) < GIT_SUCCESS)
87d3acf4
VM
816 error = GIT_EOSERR;
817
818 /*
819 * if we fail to remove a single file, this is *not* good,
820 * but we should keep going and remove as many as possible.
821 * After we've removed as many files as possible, we return
822 * the error code anyway.
823 *
824 * TODO: mark this with a very special error code?
825 * GIT_EFAILTORMLOOSE
826 */
827 }
828
5bdf7b9f 829 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to remove loose packed reference");
2f8a8ab2 830}
9282e921 831
87d3acf4 832static int packed_sort(const void *a, const void *b)
2f8a8ab2 833{
a46ec457
MS
834 const reference *ref_a = (const reference *)a;
835 const reference *ref_b = (const reference *)b;
87d3acf4
VM
836
837 return strcmp(ref_a->name, ref_b->name);
2f8a8ab2
VM
838}
839
87d3acf4
VM
840/*
841 * Write all the contents in the in-memory packfile to disk.
842 */
843static int packed_write(git_repository *repo)
2f8a8ab2 844{
87d3acf4
VM
845 git_filebuf pack_file;
846 int error;
847 unsigned int i;
848 char pack_file_path[GIT_PATH_MAX];
2f8a8ab2 849
87d3acf4
VM
850 git_vector packing_list;
851 size_t total_refs;
2f8a8ab2 852
87d3acf4 853 assert(repo && repo->references.packfile);
9282e921 854
87d3acf4 855 total_refs = repo->references.packfile->key_count;
86d7e1ca 856 if ((error = git_vector_init(&packing_list, total_refs, packed_sort)) < GIT_SUCCESS)
5bdf7b9f 857 return git__rethrow(error, "Failed to write packed reference");
9282e921 858
87d3acf4
VM
859 /* Load all the packfile into a vector */
860 {
a46ec457 861 reference *reference;
402a47a7 862 const void *GIT_UNUSED(_unused);
87d3acf4
VM
863
864 GIT_HASHTABLE_FOREACH(repo->references.packfile, _unused, reference,
87d9869f 865 git_vector_insert(&packing_list, reference); /* cannot fail: vector already has the right size */
87d3acf4 866 );
9282e921 867 }
868
87d3acf4
VM
869 /* sort the vector so the entries appear sorted on the packfile */
870 git_vector_sort(&packing_list);
9282e921 871
87d3acf4 872 /* Now we can open the file! */
f79026b4 873 git_path_join(pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE);
87d3acf4 874 if ((error = git_filebuf_open(&pack_file, pack_file_path, 0)) < GIT_SUCCESS)
5bdf7b9f 875 return git__rethrow(error, "Failed to write packed reference");
9282e921 876
7c8a7b91
VM
877 /* Packfiles have a header... apparently
878 * This is in fact not required, but we might as well print it
879 * just for kicks */
87d3acf4 880 if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < GIT_SUCCESS)
5bdf7b9f 881 return git__rethrow(error, "Failed to write packed reference");
2f8a8ab2 882
87d3acf4
VM
883 for (i = 0; i < packing_list.length; ++i) {
884 reference_oid *ref = (reference_oid *)git_vector_get(&packing_list, i);
2f8a8ab2 885
87d3acf4
VM
886 /* only direct references go to the packfile; otherwise
887 * this is a disaster */
888 assert(ref->ref.type & GIT_REF_OID);
2f8a8ab2 889
2b397327
VM
890 if ((error = packed_find_peel(ref)) < GIT_SUCCESS) {
891 error = git__throw(GIT_EOBJCORRUPTED, "A reference cannot be peeled");
87d3acf4 892 goto cleanup;
2b397327
VM
893 }
894
2f8a8ab2 895
87d3acf4
VM
896 if ((error = packed_write_ref(ref, &pack_file)) < GIT_SUCCESS)
897 goto cleanup;
898 }
2f8a8ab2 899
87d3acf4
VM
900cleanup:
901 /* if we've written all the references properly, we can commit
902 * the packfile to make the changes effective */
903 if (error == GIT_SUCCESS) {
01ad7b3a 904 error = git_filebuf_commit(&pack_file, GIT_PACK_FILE_MODE);
87d3acf4
VM
905
906 /* when and only when the packfile has been properly written,
907 * we can go ahead and remove the loose refs */
7341bf87
VM
908 if (error == GIT_SUCCESS) {
909 struct stat st;
910
87d3acf4 911 error = packed_remove_loose(repo, &packing_list);
7341bf87 912
f79026b4 913 if (p_stat(pack_file_path, &st) == GIT_SUCCESS)
7341bf87
VM
914 repo->references.packfile_time = st.st_mtime;
915 }
9282e921 916 }
87d3acf4 917 else git_filebuf_cleanup(&pack_file);
9282e921 918
87d3acf4 919 git_vector_free(&packing_list);
1d8cc731 920
5bdf7b9f 921 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write packed reference");
87d3acf4 922}
2f8a8ab2 923
1b6d8163
MS
924static int _reference_available_cb(const char *ref, void *data)
925{
f120e92b 926 const char *new, *old;
927 git_vector *refs;
928
1b6d8163
MS
929 assert(ref && data);
930
f120e92b 931 refs = (git_vector *)data;
1b6d8163 932
f120e92b 933 new = (const char *)git_vector_get(refs, 0);
934 old = (const char *)git_vector_get(refs, 1);
1b6d8163
MS
935
936 if (!old || strcmp(old, ref)) {
937 int reflen = strlen(ref);
938 int newlen = strlen(new);
939 int cmplen = reflen < newlen ? reflen : newlen;
940 const char *lead = reflen < newlen ? new : ref;
941
942 if (!strncmp(new, ref, cmplen) &&
87d9869f 943 lead[cmplen] == '/')
1b6d8163
MS
944 return GIT_EEXISTS;
945 }
946
947 return GIT_SUCCESS;
948}
949
950static int reference_available(git_repository *repo, const char *ref, const char* old_ref)
951{
952 int error;
953 git_vector refs;
954
955 if (git_vector_init(&refs, 2, NULL) < GIT_SUCCESS)
956 return GIT_ENOMEM;
957
958 git_vector_insert(&refs, (void *)ref);
959 git_vector_insert(&refs, (void *)old_ref);
960
43521d06 961 error = git_reference_foreach(repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&refs);
1b6d8163
MS
962
963 git_vector_free(&refs);
964
76b15cb1 965 return error == GIT_SUCCESS ? GIT_SUCCESS : git__throw(GIT_EEXISTS, "Reference name `%s` conflicts with existing reference", ref);
1b6d8163
MS
966}
967
a46ec457 968int reference_lookup(reference **ref_out, git_repository *repo, const char *name)
2f8a8ab2
VM
969{
970 int error;
3101a3e5 971 char normalized_name[GIT_REFNAME_MAX];
2f8a8ab2
VM
972
973 assert(ref_out && repo && name);
974
975 *ref_out = NULL;
976
3101a3e5 977 error = normalize_name(normalized_name, sizeof(normalized_name), name, 0);
2f8a8ab2 978 if (error < GIT_SUCCESS)
5bdf7b9f 979 return git__rethrow(error, "Failed to lookup reference");
2f8a8ab2 980
87d3acf4
VM
981 /* First, check has been previously loaded and cached */
982 *ref_out = git_hashtable_lookup(repo->references.loose_cache, normalized_name);
2f8a8ab2 983 if (*ref_out != NULL)
7341bf87 984 return loose_update(*ref_out);
2f8a8ab2 985
87d3acf4
VM
986 /* Then check if there is a loose file for that reference.*/
987 error = loose_lookup(ref_out, repo, normalized_name, 0);
2f8a8ab2 988
87d3acf4 989 /* If the file exists, we store it on the cache */
2f8a8ab2 990 if (error == GIT_SUCCESS)
87d3acf4 991 return git_hashtable_insert(repo->references.loose_cache, (*ref_out)->name, (*ref_out));
2f8a8ab2 992
87d3acf4
VM
993 /* The loose lookup has failed, but not because the reference wasn't found;
994 * probably the loose reference is corrupted. this is bad. */
9282e921 995 if (error != GIT_ENOTFOUND)
5bdf7b9f 996 return git__rethrow(error, "Failed to lookup reference");
9282e921 997
87d3acf4
VM
998 /*
999 * If we cannot find a loose reference, we look into the packfile
932d1baf 1000 * Load the packfile first if it hasn't been loaded
87d3acf4 1001 */
7341bf87
VM
1002 /* load all the packed references */
1003 error = packed_load(repo);
1004 if (error < GIT_SUCCESS)
5bdf7b9f 1005 return git__rethrow(error, "Failed to lookup reference");
9282e921 1006
87d3acf4
VM
1007 /* Look up on the packfile */
1008 *ref_out = git_hashtable_lookup(repo->references.packfile, normalized_name);
86194b24
VM
1009 if (*ref_out != NULL)
1010 return GIT_SUCCESS;
1011
2f8a8ab2 1012 /* The reference doesn't exist anywhere */
5bdf7b9f 1013 return git__throw(GIT_ENOTFOUND, "Failed to lookup reference. Reference doesn't exist");
2f8a8ab2 1014}
9282e921 1015
a46ec457
MS
1016int git_reference_lookup(git_reference **ref_out, git_repository *repo, const char *name)
1017{
1018 int error;
1019 reference *ref;
1020
1021 assert(ref_out && repo && name);
1022
1023 *ref_out = NULL;
1024
1025 error = reference_lookup(&ref, repo, name);
1026 if (error < GIT_SUCCESS)
1027 return error;
1028
1029 *ref_out = reference_create_external(ref);
1030 if (*ref_out == NULL)
1031 return GIT_ENOMEM;
1032
1033 return GIT_SUCCESS;
1034}
1035
87d3acf4
VM
1036/**
1037 * Getters
1038 */
a46ec457 1039static git_rtype ref_type(reference *ref)
87d3acf4
VM
1040{
1041 assert(ref);
1042
1043 if (ref->type & GIT_REF_OID)
1044 return GIT_REF_OID;
1045
1046 if (ref->type & GIT_REF_SYMBOLIC)
1047 return GIT_REF_SYMBOLIC;
1048
1049 return GIT_REF_INVALID;
1050}
1051
a46ec457
MS
1052git_rtype git_reference_type(git_reference *ref_in)
1053{
1054 reference *ref;
1055
1056 assert(ref_in);
1057
1058 ref = reference_get_internal(ref_in);
1059 if (ref == NULL)
1060 return GIT_REF_INVALID;
1061
1062 return ref_type(ref);
1063}
1064
1065int git_reference_is_packed(git_reference *ref_in)
1066{
1067 reference *ref;
1068
1069 assert(ref_in);
1070
1071 ref = reference_get_internal(ref_in);
1072 if (ref == NULL)
1073 return GIT_REF_INVALID;
1074
1075 return !!(ref->type & GIT_REF_PACKED);
1076}
1077
1078void git_reference_free(git_reference *ref)
1079{
1080 if (ref == NULL)
1081 return;
1082
1083 if (ref->name)
1084 git__free(ref->name);
1085
1086 git__free(ref);
1087}
1088
87d3acf4
VM
1089const char *git_reference_name(git_reference *ref)
1090{
1091 assert(ref);
1092 return ref->name;
1093}
1094
1095git_repository *git_reference_owner(git_reference *ref)
1096{
1097 assert(ref);
1098 return ref->owner;
1099}
1100
a46ec457 1101static const git_oid *ref_oid(reference *ref)
87d3acf4
VM
1102{
1103 assert(ref);
1104
1105 if ((ref->type & GIT_REF_OID) == 0)
1106 return NULL;
1107
7341bf87
VM
1108 if (loose_update(ref) < GIT_SUCCESS)
1109 return NULL;
1110
87d3acf4
VM
1111 return &((reference_oid *)ref)->oid;
1112}
1113
a46ec457
MS
1114const git_oid *git_reference_oid(git_reference *ref_in)
1115{
1116 reference *ref;
1117
1118 assert(ref_in);
1119
1120 ref = reference_get_internal(ref_in);
1121 if (ref == NULL)
1122 return NULL;
1123
1124 return ref_oid(ref);
1125}
1126
1127static const char *ref_target(reference *ref)
87d3acf4
VM
1128{
1129 assert(ref);
1130
1131 if ((ref->type & GIT_REF_SYMBOLIC) == 0)
1132 return NULL;
1133
7341bf87
VM
1134 if (loose_update(ref) < GIT_SUCCESS)
1135 return NULL;
1136
87d3acf4
VM
1137 return ((reference_symbolic *)ref)->target;
1138}
1139
a46ec457
MS
1140const char *git_reference_target(git_reference *ref_in)
1141{
1142 reference *ref;
1143
1144 assert(ref_in);
1145
1146 ref = reference_get_internal(ref_in);
1147 if (ref == NULL)
1148 return NULL;
1149
1150 return ref_target(ref);
1151}
1152
1153static int reference_create_symbolic(reference **ref_out, git_repository *repo, const char *name, const char *target, int force)
d5afc039
VM
1154{
1155 char normalized[GIT_REFNAME_MAX];
1156 int error = GIT_SUCCESS, updated = 0;
a46ec457 1157 reference *ref = NULL;
6c8b458d 1158 void *old_ref = NULL;
d5afc039 1159
a46ec457 1160 if (reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force)
d5afc039
VM
1161 return git__throw(GIT_EEXISTS, "Failed to create symbolic reference. Reference already exists");
1162
1163 /*
1164 * If they old ref was of the same type, then we can just update
1165 * it (once we've checked that the target is valid). Otherwise we
1166 * need a new reference because we can't make a symbolic ref out
1167 * of an oid one.
1168 * If if didn't exist, then we need to create a new one anyway.
87d9869f 1169 */
d5afc039
VM
1170 if (ref && ref->type & GIT_REF_SYMBOLIC){
1171 updated = 1;
1172 } else {
1173 ref = NULL;
1174 error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC);
1175 if (error < GIT_SUCCESS)
1176 goto cleanup;
1177 }
1178
1179 /* The target can aither be the name of an object id reference or the name of another symbolic reference */
1180 error = normalize_name(normalized, sizeof(normalized), target, 0);
1181 if (error < GIT_SUCCESS)
1182 goto cleanup;
1183
1184 /* set the target; this will write the reference on disk */
a46ec457 1185 error = reference_set_target(ref, normalized);
d5afc039
VM
1186 if (error < GIT_SUCCESS)
1187 goto cleanup;
1188
1189 /*
1190 * If we didn't update the ref, then we need to insert or replace
1191 * it in the loose cache. If we replaced a ref, free it.
1192 */
1193 if (!updated){
6c8b458d 1194 error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, &old_ref);
d5afc039
VM
1195 if (error < GIT_SUCCESS)
1196 goto cleanup;
1197
6c8b458d 1198 if (old_ref != NULL)
a46ec457 1199 reference_free((reference *)old_ref);
d5afc039
VM
1200 }
1201
1202 *ref_out = ref;
1203
1204 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference");
1205
1206cleanup:
1207 reference_free(ref);
1208 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference");
1209}
1210
a46ec457
MS
1211int git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force)
1212{
1213 int error;
1214 reference *ref;
1215
1216 error = reference_create_symbolic(&ref, repo, name, target, force);
1217 if (error < GIT_SUCCESS)
1218 return error;
1219
1220 *ref_out = reference_create_external(ref);
1221 if (*ref_out == NULL)
1222 return GIT_ENOMEM;
1223
1224 return GIT_SUCCESS;
1225}
1226
1227static int reference_create_oid(reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force)
d5afc039
VM
1228{
1229 int error = GIT_SUCCESS, updated = 0;
a46ec457 1230 reference *ref = NULL;
6c8b458d 1231 void *old_ref = NULL;
d5afc039 1232
a46ec457 1233 if(reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force)
d5afc039
VM
1234 return git__throw(GIT_EEXISTS, "Failed to create reference OID. Reference already exists");
1235
1236 if ((error = reference_available(repo, name, NULL)) < GIT_SUCCESS)
1237 return git__rethrow(error, "Failed to create reference");
1238
1239 /*
1240 * If they old ref was of the same type, then we can just update
1241 * it (once we've checked that the target is valid). Otherwise we
1242 * need a new reference because we can't make a symbolic ref out
1243 * of an oid one.
1244 * If if didn't exist, then we need to create a new one anyway.
87d9869f 1245 */
d5afc039
VM
1246 if (ref && ref-> type & GIT_REF_OID){
1247 updated = 1;
1248 } else {
1249 ref = NULL;
1250 error = reference_create(&ref, repo, name, GIT_REF_OID);
1251 if (error < GIT_SUCCESS)
1252 goto cleanup;
1253 }
1254
1255 /* set the oid; this will write the reference on disk */
a46ec457 1256 error = reference_set_oid(ref, id);
d5afc039
VM
1257 if (error < GIT_SUCCESS)
1258 goto cleanup;
1259
1260 if(!updated){
6c8b458d 1261 error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, &old_ref);
d5afc039
VM
1262 if (error < GIT_SUCCESS)
1263 goto cleanup;
1264
6c8b458d 1265 if (old_ref != NULL)
a46ec457 1266 reference_free((reference *)old_ref);
d5afc039
VM
1267 }
1268
1269 *ref_out = ref;
1270
1271 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID");
1272
1273cleanup:
1274 reference_free(ref);
1275 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID");
1276}
1277
a46ec457
MS
1278int git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force)
1279{
1280 int error;
1281 reference *ref;
1282
1283 error = reference_create_oid(&ref, repo, name, id, force);
1284 if (error < GIT_SUCCESS)
1285 return error;
1286
1287 *ref_out = reference_create_external(ref);
1288 if (*ref_out == NULL)
1289 return GIT_ENOMEM;
1290
1291 return GIT_SUCCESS;
1292}
87d3acf4
VM
1293
1294/*
1295 * Change the OID target of a reference.
1296 *
1297 * For loose references, just change the oid in memory
1298 * and overwrite the file in disk.
1299 *
1300 * For packed files, this is not pretty:
1301 * For performance reasons, we write the new reference
1302 * loose on disk (it replaces the old on the packfile),
1303 * but we cannot invalidate the pointer to the reference,
1304 * and most importantly, the `packfile` object must stay
1305 * consistent with the representation of the packfile
1306 * on disk. This is what we need to:
1307 *
1308 * 1. Copy the reference
1309 * 2. Change the oid on the original
1310 * 3. Write the original to disk
1311 * 4. Write the original to the loose cache
1312 * 5. Replace the original with the copy (old reference) in the packfile cache
1313 */
a46ec457 1314int reference_set_oid(reference *ref, const git_oid *id)
87d3acf4
VM
1315{
1316 reference_oid *ref_oid;
1317 reference_oid *ref_old = NULL;
1318 int error = GIT_SUCCESS;
1319
1320 if ((ref->type & GIT_REF_OID) == 0)
5bdf7b9f 1321 return git__throw(GIT_EINVALIDREFSTATE, "Failed to set OID target of reference. Not an OID reference");
87d3acf4
VM
1322
1323 ref_oid = (reference_oid *)ref;
1324
9a53df7e
VM
1325 assert(ref->owner);
1326
1327 /* Don't let the user create references to OIDs that
1328 * don't exist in the ODB */
1329 if (!git_odb_exists(git_repository_database(ref->owner), id))
5bdf7b9f 1330 return git__throw(GIT_ENOTFOUND, "Failed to set OID target of reference. OID doesn't exist in ODB");
9a53df7e 1331
87d3acf4
VM
1332 /* duplicate the reference;
1333 * this copy will stay on the packfile cache */
1334 if (ref->type & GIT_REF_PACKED) {
1335 ref_old = git__malloc(sizeof(reference_oid));
1336 if (ref_old == NULL)
1337 return GIT_ENOMEM;
1338
1339 ref_old->ref.name = git__strdup(ref->name);
1340 if (ref_old->ref.name == NULL) {
3286c408 1341 git__free(ref_old);
87d3acf4
VM
1342 return GIT_ENOMEM;
1343 }
1344 }
1345
1346 git_oid_cpy(&ref_oid->oid, id);
1347 ref->type &= ~GIT_REF_HAS_PEEL;
1348
932d1baf 1349 error = loose_write(ref);
87d3acf4
VM
1350 if (error < GIT_SUCCESS)
1351 goto cleanup;
1352
1353 if (ref->type & GIT_REF_PACKED) {
1354 /* insert the original on the loose cache */
1355 error = git_hashtable_insert(ref->owner->references.loose_cache, ref->name, ref);
1356 if (error < GIT_SUCCESS)
1357 goto cleanup;
1358
1359 ref->type &= ~GIT_REF_PACKED;
1360
1361 /* replace the original in the packfile with the copy */
1362 error = git_hashtable_insert(ref->owner->references.packfile, ref_old->ref.name, ref_old);
1363 if (error < GIT_SUCCESS)
1364 goto cleanup;
1365 }
1366
1367 return GIT_SUCCESS;
1368
1369cleanup:
a46ec457 1370 reference_free((reference *)ref_old);
5bdf7b9f 1371 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to set OID target of reference");
87d3acf4
VM
1372}
1373
a46ec457
MS
1374int git_reference_set_oid(git_reference *ref_in, const git_oid *target)
1375{
1376 reference *ref;
1377
1378 ref = reference_get_internal(ref_in);
1379 if (ref == NULL)
1380 return git__throw(GIT_ENOTFOUND, "Failed to rename reference. Reference `%s` doesn't exist", ref->name);
1381
1382 return reference_set_oid(ref, target);
1383}
1384
87d3acf4
VM
1385/*
1386 * Change the target of a symbolic reference.
1387 *
1388 * This is easy because symrefs cannot be inside
1389 * a pack. We just change the target in memory
1390 * and overwrite the file on disk.
1391 */
a46ec457 1392int reference_set_target(reference *ref, const char *target)
87d3acf4
VM
1393{
1394 reference_symbolic *ref_sym;
1395
1396 if ((ref->type & GIT_REF_SYMBOLIC) == 0)
5bdf7b9f 1397 return git__throw(GIT_EINVALIDREFSTATE, "Failed to set reference target. Not a symbolic reference");
87d3acf4
VM
1398
1399 ref_sym = (reference_symbolic *)ref;
1400
3286c408 1401 git__free(ref_sym->target);
87d3acf4
VM
1402 ref_sym->target = git__strdup(target);
1403 if (ref_sym->target == NULL)
1404 return GIT_ENOMEM;
1405
1406 return loose_write(ref);
1407}
1408
a46ec457
MS
1409int git_reference_set_target(git_reference *ref_in, const char *target)
1410{
1411 reference *ref;
1412
1413 ref = reference_get_internal(ref_in);
1414 if (ref == NULL)
1415 return git__throw(GIT_ENOTFOUND, "Failed to rename reference. Reference `%s` doesn't exist", ref->name);
87d3acf4 1416
a46ec457
MS
1417 return reference_set_target(ref, target);
1418}
1419
1420int git_reference_rename(git_reference *ref_in, const char *new_name, int force)
7376ad99
VM
1421{
1422 int error;
858dba58
VM
1423
1424 char aux_path[GIT_PATH_MAX];
0ffcf78a 1425 char normalized[GIT_REFNAME_MAX];
858dba58 1426
0ffcf78a 1427 const char *head_target = NULL;
549bbd13 1428 reference *ref = NULL, *new_ref = NULL, *head = NULL, *tmp_ref = NULL;
7376ad99 1429
a46ec457
MS
1430 assert(ref_in);
1431
1432 ref = reference_get_internal(ref_in);
1433 if (ref == NULL)
1434 return git__throw(GIT_ENOTFOUND, "Failed to rename reference. Reference `%s` doesn't exist", ref->name);
7376ad99 1435
0ffcf78a 1436 error = normalize_name(normalized, sizeof(normalized), new_name, ref->type & GIT_REF_OID);
7376ad99 1437 if (error < GIT_SUCCESS)
0ffcf78a 1438 return git__rethrow(error, "Failed to rename reference. Invalid name");
7376ad99 1439
0ffcf78a 1440 new_name = normalized;
7376ad99 1441
a46ec457 1442 error = reference_lookup(&new_ref, ref->owner, new_name);
73294339
MS
1443 if (error == GIT_SUCCESS) {
1444 if (!force)
1445 return git__throw(GIT_EEXISTS, "Failed to rename reference. Reference already exists");
1446
a46ec457 1447 error = reference_delete(new_ref);
73294339 1448 }
7376ad99 1449
93fdbe00
MS
1450 if (error < GIT_SUCCESS) {
1451 git_path_join(aux_path, ref->owner->path_repository, new_name);
1452 /* If we couldn't read the reference because it doesn't
1453 * exist it's ok - otherwise return */
1454 if (git_futils_isfile(aux_path) == GIT_SUCCESS)
1455 goto cleanup;
1456 }
7376ad99
VM
1457
1458 if ((error = reference_available(ref->owner, new_name, ref->name)) < GIT_SUCCESS)
0ffcf78a 1459 return git__rethrow(error, "Failed to rename reference. Reference already exists");
7376ad99 1460
0ffcf78a
MS
1461 /*
1462 * First, we backup the reference targets. Just keeping the old
1463 * reference won't work, since we may have to remove it to create
1464 * the new reference, e.g. when renaming foo/bar -> foo.
1465 */
7376ad99 1466
549bbd13
MS
1467 if (ref->type & GIT_REF_SYMBOLIC)
1468 tmp_ref = git__malloc(sizeof(reference_symbolic));
1469 else
1470 tmp_ref = git__malloc(sizeof(reference_oid));
ca6f203c 1471
549bbd13
MS
1472 if (tmp_ref == NULL)
1473 return GIT_ENOMEM;
1474
1475 tmp_ref->name = git__strdup(ref->name);
1476 tmp_ref->type = ref->type;
1477 tmp_ref->owner = ref->owner;
1478
1479 if (ref->type & GIT_REF_SYMBOLIC)
1480 ((reference_symbolic *)tmp_ref)->target = git__strdup(((reference_symbolic *)ref)->target);
1481 else
1482 ((reference_oid *)tmp_ref)->oid = ((reference_oid *)ref)->oid;
7376ad99 1483
0ffcf78a
MS
1484 /*
1485 * Now delete the old ref and remove an possibly existing directory
1486 * named `new_name`.
1487 */
7376ad99 1488
549bbd13
MS
1489 if ((error = reference_delete(ref)) < GIT_SUCCESS)
1490 goto cleanup;
7376ad99 1491
549bbd13 1492 git_path_join(aux_path, tmp_ref->owner->path_repository, new_name);
858dba58
VM
1493 if (git_futils_exists(aux_path) == GIT_SUCCESS) {
1494 if (git_futils_isdir(aux_path) == GIT_SUCCESS) {
1495 if ((error = git_futils_rmdir_r(aux_path, 0)) < GIT_SUCCESS)
0ffcf78a
MS
1496 goto rollback;
1497 } else goto rollback;
1498 }
7376ad99 1499
0ffcf78a
MS
1500 /*
1501 * Crude hack: delete any logs till we support proper reflogs.
1502 * Otherwise git.git will possibly fail and leave a mess. git.git
1503 * writes reflogs by default in any repo with a working directory:
1504 *
1505 * "We only enable reflogs in repositories that have a working directory
87d9869f
VM
1506 * associated with them, as shared/bare repositories do not have
1507 * an easy means to prune away old log entries, or may fail logging
1508 * entirely if the user's gecos information is not valid during a push.
1509 * This heuristic was suggested on the mailing list by Junio."
0ffcf78a
MS
1510 *
1511 * Shawn O. Pearce - 0bee59186976b1d9e6b2dd77332480c9480131d5
1512 *
1513 * TODO
1514 *
1515 */
1516
549bbd13 1517 git_path_join_n(aux_path, 3, tmp_ref->owner->path_repository, "logs", tmp_ref->name);
858dba58
VM
1518 if (git_futils_isfile(aux_path) == GIT_SUCCESS) {
1519 if ((error = p_unlink(aux_path)) < GIT_SUCCESS)
1520 goto rollback;
0ffcf78a
MS
1521 }
1522
1523 /*
1524 * Finally we can create the new reference.
1525 */
549bbd13
MS
1526 if (tmp_ref->type & GIT_REF_SYMBOLIC) {
1527 if ((error = reference_create_symbolic(&new_ref, tmp_ref->owner, new_name, ((reference_symbolic *)tmp_ref)->target, 0)) < GIT_SUCCESS)
0ffcf78a
MS
1528 goto rollback;
1529 } else {
549bbd13 1530 if ((error = reference_create_oid(&new_ref, tmp_ref->owner, new_name, &((reference_oid *)tmp_ref)->oid, 0)) < GIT_SUCCESS)
0ffcf78a
MS
1531 goto rollback;
1532 }
7376ad99 1533
5c6ae009 1534 /*
a46ec457 1535 * Change the name of the reference given by the user.
5c6ae009 1536 */
a46ec457
MS
1537 git__free(ref_in->name);
1538 ref_in->name = git__strdup(new_ref->name);
5c6ae009 1539
549bbd13 1540 if ((error = git_hashtable_insert(new_ref->owner->references.loose_cache, new_ref->name, new_ref)) < GIT_SUCCESS)
0ffcf78a 1541 goto rollback;
7376ad99 1542
0ffcf78a
MS
1543 /*
1544 * Check if we have to update HEAD.
1545 */
7376ad99 1546
a46ec457 1547 if ((error = reference_lookup(&head, new_ref->owner, GIT_HEAD_FILE)) < GIT_SUCCESS)
0ffcf78a 1548 goto cleanup;
7376ad99 1549
a46ec457 1550 head_target = ref_target(head);
7376ad99 1551
549bbd13 1552 if (head_target && !strcmp(head_target, tmp_ref->name))
a46ec457 1553 if ((error = reference_create_symbolic(&head, new_ref->owner, "HEAD", new_ref->name, 1)) < GIT_SUCCESS)
0ffcf78a 1554 goto rollback;
7376ad99 1555
0ffcf78a 1556cleanup:
549bbd13 1557 reference_free(tmp_ref);
7376ad99 1558 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference");
7376ad99 1559
0ffcf78a
MS
1560rollback:
1561 /*
1562 * Try to create the old reference again.
1563 */
1564 if (ref->type & GIT_REF_SYMBOLIC)
549bbd13 1565 error = reference_create_symbolic(&new_ref, tmp_ref->owner, tmp_ref->name, ((reference_symbolic *)tmp_ref)->target, 0);
0ffcf78a 1566 else
549bbd13
MS
1567 error = reference_create_oid(&new_ref, ref->owner, tmp_ref->name, &((reference_oid *)tmp_ref)->oid, 0);
1568
1569 git__free(ref_in->name);
1570 ref_in->name = git__strdup(tmp_ref->name);
0ffcf78a 1571
549bbd13 1572 reference_free(tmp_ref);
0ffcf78a 1573
549bbd13
MS
1574 return error == GIT_SUCCESS ?
1575 git__rethrow(GIT_ERROR, "Failed to rename reference. Did rollback") :
1576 git__rethrow(error, "Failed to rename reference. Failed to rollback");
0ffcf78a 1577}
7376ad99 1578
87d3acf4
VM
1579/*
1580 * Delete a reference.
1581 *
1582 * If the reference is packed, this is an expensive
1583 * operation. We need to remove the reference from
1584 * the memory cache and then rewrite the whole pack
1585 *
5ad0351d 1586 * If the reference is loose, we remove it on
87d3acf4 1587 * the filesystem and update the in-memory cache
5ad0351d 1588 * accordingly. We also make sure that an older version
1589 * of it doesn't exist as a packed reference. If this
1590 * is the case, this packed reference is removed as well.
87d3acf4
VM
1591 *
1592 * This obviously invalidates the `ref` pointer.
1593 */
a46ec457 1594int reference_delete(reference *ref)
87d3acf4
VM
1595{
1596 int error;
a46ec457 1597 reference *reference;
87d3acf4
VM
1598
1599 assert(ref);
1600
1601 if (ref->type & GIT_REF_PACKED) {
d79f1da6
VM
1602 /* load the existing packfile */
1603 if ((error = packed_load(ref->owner)) < GIT_SUCCESS)
5bdf7b9f 1604 return git__rethrow(error, "Failed to delete reference");
932d1baf 1605
2b397327
VM
1606 if (git_hashtable_remove(ref->owner->references.packfile, ref->name) < GIT_SUCCESS)
1607 return git__throw(GIT_ENOTFOUND, "Reference not found");
1608
87d3acf4
VM
1609 error = packed_write(ref->owner);
1610 } else {
1611 char full_path[GIT_PATH_MAX];
f79026b4 1612 git_path_join(full_path, ref->owner->path_repository, ref->name);
87d3acf4 1613 git_hashtable_remove(ref->owner->references.loose_cache, ref->name);
f79026b4 1614 error = p_unlink(full_path);
5ad0351d 1615 if (error < GIT_SUCCESS)
1616 goto cleanup;
1617
1618 /* When deleting a loose reference, we have to ensure that an older
1619 * packed version of it doesn't exist
1620 */
a46ec457 1621 if (!reference_lookup(&reference, ref->owner, ref->name)) {
5ad0351d 1622 assert((reference->type & GIT_REF_PACKED) != 0);
a46ec457 1623 error = reference_delete(reference);
5ad0351d 1624 }
87d3acf4
VM
1625 }
1626
5ad0351d 1627cleanup:
87d3acf4 1628 reference_free(ref);
5bdf7b9f 1629 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to delete reference");
87d3acf4
VM
1630}
1631
a46ec457
MS
1632int git_reference_delete(git_reference *ref_in)
1633{
1634 reference *ref;
1635
1636 ref = reference_get_internal(ref_in);
1637 if (ref == NULL)
1638 return git__throw(GIT_ENOTFOUND, "Failed to delete reference. Reference `%s` doesn't exist", ref_in->name);
1639
1640 git_reference_free(ref_in);
1641
1642 return reference_delete(ref);
1643}
1644
1645static int reference_resolve(reference **resolved_ref, reference *ref)
87d3acf4
VM
1646{
1647 git_repository *repo;
1648 int error, i;
1649
1650 assert(resolved_ref && ref);
1651 *resolved_ref = NULL;
7341bf87
VM
1652
1653 if ((error = loose_update(ref)) < GIT_SUCCESS)
5bdf7b9f 1654 return git__rethrow(error, "Failed to resolve reference");
932d1baf 1655
87d3acf4
VM
1656 repo = ref->owner;
1657
1658 for (i = 0; i < MAX_NESTING_LEVEL; ++i) {
1659 reference_symbolic *ref_sym;
1660
68a146c1
CMN
1661 *resolved_ref = ref;
1662
0d5d5190 1663 if (ref->type & GIT_REF_OID)
87d3acf4 1664 return GIT_SUCCESS;
87d3acf4
VM
1665
1666 ref_sym = (reference_symbolic *)ref;
a46ec457 1667 if ((error = reference_lookup(&ref, repo, ref_sym->target)) < GIT_SUCCESS)
0d5d5190 1668 return error;
87d3acf4
VM
1669 }
1670
0d5d5190 1671 return git__throw(GIT_ENOMEM, "Failed to resolve reference. Reference is too nested");
87d3acf4
VM
1672}
1673
a46ec457
MS
1674int git_reference_resolve(git_reference **resolved_ref, git_reference *ref_in)
1675{
1676 int error;
1677 reference *ref = NULL, *out = NULL;
1678
1679 *resolved_ref = NULL;
1680
1681 ref = reference_get_internal(ref_in);
1682 if (ref == NULL)
1683 return git__throw(GIT_ENOTFOUND, "Failed to resolve reference. Reference `%s` doesn't exist", ref_in->name);
1684
1685 error = reference_resolve(&out, ref);
1686 if (error < GIT_SUCCESS)
1687 return error;
1688
1689 *resolved_ref = reference_create_external(out);
1690 if (*resolved_ref == NULL)
1691 return GIT_ENOMEM;
1692
1693 return GIT_SUCCESS;
1694}
1695
87d3acf4
VM
1696int git_reference_packall(git_repository *repo)
1697{
1698 int error;
1699
1700 /* load the existing packfile */
1701 if ((error = packed_load(repo)) < GIT_SUCCESS)
5bdf7b9f 1702 return git__rethrow(error, "Failed to pack references");
87d3acf4
VM
1703
1704 /* update it in-memory with all the loose references */
1705 if ((error = packed_loadloose(repo)) < GIT_SUCCESS)
5bdf7b9f 1706 return git__rethrow(error, "Failed to pack references");
87d3acf4
VM
1707
1708 /* write it back to disk */
1709 return packed_write(repo);
1710}
1711
43521d06 1712int git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload)
00571828
VM
1713{
1714 int error;
1715 struct dirent_list_data data;
1716 char refs_path[GIT_PATH_MAX];
1717
7ad96e51 1718 /* list all the packed references first */
00571828
VM
1719 if (list_flags & GIT_REF_PACKED) {
1720 const char *ref_name;
402a47a7 1721 void *GIT_UNUSED(_unused);
00571828 1722
09e8de0f 1723 if ((error = packed_load(repo)) < GIT_SUCCESS)
5bdf7b9f 1724 return git__rethrow(error, "Failed to list references");
00571828
VM
1725
1726 GIT_HASHTABLE_FOREACH(repo->references.packfile, ref_name, _unused,
09e8de0f 1727 if ((error = callback(ref_name, payload)) < GIT_SUCCESS)
0d5d5190 1728 return git__throw(error, "Failed to list references. User callback failed");
00571828
VM
1729 );
1730 }
1731
7ad96e51
VM
1732 /* now list the loose references, trying not to
1733 * duplicate the ref names already in the packed-refs file */
09e8de0f
VM
1734
1735 data.repo_path_len = strlen(repo->path_repository);
1736 data.list_flags = list_flags;
1737 data.repo = repo;
1738 data.callback = callback;
1739 data.callback_payload = payload;
1740
1741
f79026b4
VM
1742 git_path_join(refs_path, repo->path_repository, GIT_REFS_DIR);
1743 return git_futils_direach(refs_path, GIT_PATH_MAX, _dirent_loose_listall, &data);
09e8de0f
VM
1744}
1745
d568d585 1746static int cb__reflist_add(const char *ref, void *data)
09e8de0f
VM
1747{
1748 return git_vector_insert((git_vector *)data, git__strdup(ref));
1749}
1750
1751int git_reference_listall(git_strarray *array, git_repository *repo, unsigned int list_flags)
1752{
1753 int error;
1754 git_vector ref_list;
1755
1756 assert(array && repo);
1757
1758 array->strings = NULL;
1759 array->count = 0;
1760
1761 if (git_vector_init(&ref_list, 8, NULL) < GIT_SUCCESS)
1762 return GIT_ENOMEM;
1763
43521d06 1764 error = git_reference_foreach(repo, list_flags, &cb__reflist_add, (void *)&ref_list);
7ad96e51
VM
1765
1766 if (error < GIT_SUCCESS) {
09e8de0f 1767 git_vector_free(&ref_list);
0d5d5190 1768 return error;
7ad96e51
VM
1769 }
1770
09e8de0f
VM
1771 array->strings = (char **)ref_list.contents;
1772 array->count = ref_list.length;
00571828
VM
1773 return GIT_SUCCESS;
1774}
87d3acf4 1775
2f8a8ab2
VM
1776int git_repository__refcache_init(git_refcache *refs)
1777{
1778 assert(refs);
9282e921 1779
87d3acf4 1780 refs->loose_cache = git_hashtable_alloc(
86194b24
VM
1781 default_table_size,
1782 reftable_hash,
d1f34693 1783 (git_hash_keyeq_ptr)(&git__strcmp_cb));
86194b24 1784
87d3acf4
VM
1785 /* packfile loaded lazily */
1786 refs->packfile = NULL;
c4982328 1787 refs->packfile_time = 0;
9282e921 1788
87d3acf4 1789 return (refs->loose_cache) ? GIT_SUCCESS : GIT_ENOMEM;
2f8a8ab2 1790}
9282e921 1791
2f8a8ab2
VM
1792void git_repository__refcache_free(git_refcache *refs)
1793{
a46ec457 1794 reference *reference;
402a47a7 1795 const void *GIT_UNUSED(_unused);
9282e921 1796
2f8a8ab2
VM
1797 assert(refs);
1798
87d3acf4 1799 GIT_HASHTABLE_FOREACH(refs->loose_cache, _unused, reference,
86194b24
VM
1800 reference_free(reference);
1801 );
1802
87d3acf4 1803 git_hashtable_free(refs->loose_cache);
2f8a8ab2 1804
87d3acf4
VM
1805 if (refs->packfile) {
1806 GIT_HASHTABLE_FOREACH(refs->packfile, _unused, reference,
1807 reference_free(reference);
1808 );
1809
1810 git_hashtable_free(refs->packfile);
1811 }
9282e921 1812}
2f8a8ab2 1813
aa2120e9 1814static int check_valid_ref_char(char ch)
1815{
50a8fd03 1816 if ((unsigned) ch <= ' ')
0d5d5190 1817 return GIT_ERROR;
aa2120e9 1818
1819 switch (ch) {
1820 case '~':
1821 case '^':
1822 case ':':
1823 case '\\':
1824 case '?':
1825 case '[':
e1be1028 1826 case '*':
0d5d5190 1827 return GIT_ERROR;
aa2120e9 1828 default:
1829 return GIT_SUCCESS;
1830 }
1831}
1832
3101a3e5 1833static int normalize_name(char *buffer_out, size_t out_size, const char *name, int is_oid_ref)
aa2120e9 1834{
aa2120e9 1835 const char *name_end, *buffer_out_start;
b75bec94 1836 const char *current;
aa2120e9 1837 int contains_a_slash = 0;
1838
1839 assert(name && buffer_out);
1840
1841 buffer_out_start = buffer_out;
b75bec94 1842 current = name;
aa2120e9 1843 name_end = name + strlen(name);
1844
3101a3e5
VM
1845 /* Terminating null byte */
1846 out_size--;
1847
aa2120e9 1848 /* A refname can not be empty */
1849 if (name_end == name)
5bdf7b9f 1850 return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name is empty");
aa2120e9 1851
1852 /* A refname can not end with a dot or a slash */
1853 if (*(name_end - 1) == '.' || *(name_end - 1) == '/')
5bdf7b9f 1854 return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name ends with dot or slash");
aa2120e9 1855
3101a3e5 1856 while (current < name_end && out_size) {
aa2120e9 1857 if (check_valid_ref_char(*current))
0d5d5190 1858 return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains invalid characters");
aa2120e9 1859
1860 if (buffer_out > buffer_out_start) {
1861 char prev = *(buffer_out - 1);
1862
1863 /* A refname can not start with a dot nor contain a double dot */
1864 if (*current == '.' && ((prev == '.') || (prev == '/')))
5bdf7b9f 1865 return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name starts with a dot or contains a double dot");
aa2120e9 1866
1867 /* '@{' is forbidden within a refname */
1868 if (*current == '{' && prev == '@')
5bdf7b9f 1869 return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains '@{'");
aa2120e9 1870
1871 /* Prevent multiple slashes from being added to the output */
1872 if (*current == '/' && prev == '/') {
1873 current++;
1874 continue;
1875 }
1876 }
1877
e1be1028 1878 if (*current == '/')
aa2120e9 1879 contains_a_slash = 1;
aa2120e9 1880
1881 *buffer_out++ = *current++;
3101a3e5 1882 out_size--;
aa2120e9 1883 }
1884
3101a3e5
VM
1885 if (!out_size)
1886 return git__throw(GIT_EINVALIDREFNAME, "Reference name is too long");
1887
83c95128 1888 /* Object id refname have to contain at least one slash, except
df30eac1
JP
1889 * for HEAD in a detached state or MERGE_HEAD if we're in the
1890 * middle of a merge */
517bda19
CMN
1891 if (is_oid_ref && !contains_a_slash && (strcmp(name, GIT_HEAD_FILE) && strcmp(name, GIT_MERGE_HEAD_FILE)
1892 && strcmp(name, GIT_FETCH_HEAD_FILE)))
0d5d5190 1893 return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains no slashes");
aa2120e9 1894
1895 /* A refname can not end with ".lock" */
1896 if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION))
0d5d5190 1897 return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name ends with '.lock'");
aa2120e9 1898
1899 *buffer_out = '\0';
1900
83c95128
CMN
1901 /*
1902 * For object id references, name has to start with refs/. Again,
1903 * we need to allow HEAD to be in a detached state.
1904 */
0d5d5190
VM
1905 if (is_oid_ref && !(git__prefixcmp(buffer_out_start, GIT_REFS_DIR) ||
1906 strcmp(buffer_out_start, GIT_HEAD_FILE)))
5bdf7b9f 1907 return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name does not start with 'refs/'");
aa2120e9 1908
0d5d5190 1909 return GIT_SUCCESS;
aa2120e9 1910}
2f8a8ab2 1911
3101a3e5 1912int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name)
86194b24 1913{
3101a3e5 1914 return normalize_name(buffer_out, out_size, name, 0);
86194b24
VM
1915}
1916
3101a3e5 1917int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name)
86194b24 1918{
3101a3e5 1919 return normalize_name(buffer_out, out_size, name, 1);
50a8fd03 1920}