]> git.proxmox.com Git - libgit2.git/blame - src/refs.c
Update clar and remove old helpers
[libgit2.git] / src / refs.c
CommitLineData
9282e921 1/*
5e0de328 2 * Copyright (C) 2009-2012 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"
a5cd086d 13#include "reflog.h"
9282e921 14
87d3acf4
VM
15#include <git2/tag.h>
16#include <git2/object.h>
17
9282e921 18#define MAX_NESTING_LEVEL 5
19
d4a0b124
VM
20enum {
21 GIT_PACKREF_HAS_PEEL = 1,
22 GIT_PACKREF_WAS_LOOSE = 2
23};
86194b24 24
d4a0b124
VM
25struct packref {
26 git_oid oid;
27 git_oid peel;
28 char flags;
29 char name[GIT_FLEX_ARRAY];
30};
86194b24 31
9282e921 32static const int default_table_size = 32;
33
d4a0b124 34static int reference_read(
13224ea4 35 git_buf *file_content,
d4a0b124
VM
36 time_t *mtime,
37 const char *repo_path,
38 const char *ref_name,
39 int *updated);
a46ec457 40
87d3acf4 41/* loose refs */
13224ea4
VM
42static int loose_parse_symbolic(git_reference *ref, git_buf *file_content);
43static int loose_parse_oid(git_oid *ref, git_buf *file_content);
d4a0b124
VM
44static int loose_lookup(git_reference *ref);
45static int loose_lookup_to_packfile(struct packref **ref_out,
46 git_repository *repo, const char *name);
47static int loose_write(git_reference *ref);
87d3acf4
VM
48
49/* packed refs */
d4a0b124
VM
50static int packed_parse_peel(struct packref *tag_ref,
51 const char **buffer_out, const char *buffer_end);
52static int packed_parse_oid(struct packref **ref_out,
53 const char **buffer_out, const char *buffer_end);
87d3acf4
VM
54static int packed_load(git_repository *repo);
55static int packed_loadloose(git_repository *repository);
d4a0b124
VM
56static int packed_write_ref(struct packref *ref, git_filebuf *file);
57static int packed_find_peel(git_repository *repo, struct packref *ref);
87d3acf4
VM
58static int packed_remove_loose(git_repository *repo, git_vector *packing_list);
59static int packed_sort(const void *a, const void *b);
d4a0b124 60static int packed_lookup(git_reference *ref);
87d3acf4
VM
61static int packed_write(git_repository *repo);
62
95cde17c 63/* internal helpers */
1a481123
VM
64static int reference_path_available(git_repository *repo,
65 const char *ref, const char *old_ref);
d4a0b124
VM
66static int reference_delete(git_reference *ref);
67static int reference_lookup(git_reference *ref);
95cde17c 68
87d3acf4 69/* name normalization */
d4a0b124
VM
70static int normalize_name(char *buffer_out, size_t out_size,
71 const char *name, int is_oid_ref);
a46ec457
MS
72
73
d4a0b124 74void git_reference_free(git_reference *reference)
9282e921 75{
2f8a8ab2
VM
76 if (reference == NULL)
77 return;
9282e921 78
d4a0b124 79 git__free(reference->name);
97769280 80 reference->name = NULL;
9282e921 81
97769280 82 if (reference->flags & GIT_REF_SYMBOLIC) {
d4a0b124 83 git__free(reference->target.symbolic);
97769280
RB
84 reference->target.symbolic = NULL;
85 }
9282e921 86
3286c408 87 git__free(reference);
9282e921 88}
89
fa515656 90static int reference_alloc(
d4a0b124 91 git_reference **ref_out,
87d3acf4 92 git_repository *repo,
d4a0b124 93 const char *name)
87d3acf4 94{
d4a0b124 95 git_reference *reference = NULL;
9282e921 96
1d8cc731 97 assert(ref_out && repo && name);
98
d4a0b124 99 reference = git__malloc(sizeof(git_reference));
1a481123 100 GITERR_CHECK_ALLOC(reference);
9282e921 101
d4a0b124 102 memset(reference, 0x0, sizeof(git_reference));
2f8a8ab2 103 reference->owner = repo;
1d8cc731 104
d4a0b124 105 reference->name = git__strdup(name);
1a481123 106 GITERR_CHECK_ALLOC(reference->name);
9282e921 107
2f8a8ab2 108 *ref_out = reference;
1a481123 109 return 0;
1d8cc731 110}
111
1a481123
VM
112static int reference_read(
113 git_buf *file_content,
114 time_t *mtime,
115 const char *repo_path,
116 const char *ref_name,
117 int *updated)
7341bf87 118{
97769280 119 git_buf path = GIT_BUF_INIT;
1a481123 120 int result;
7341bf87 121
c4982328
CMN
122 assert(file_content && repo_path && ref_name);
123
7341bf87 124 /* Determine the full path of the file */
1a481123
VM
125 if (git_buf_joinpath(&path, repo_path, ref_name) < 0)
126 return -1;
97769280 127
1a481123 128 result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated);
97769280 129 git_buf_free(&path);
1a481123 130 return result;
7341bf87
VM
131}
132
13224ea4 133static int loose_parse_symbolic(git_reference *ref, git_buf *file_content)
2f8a8ab2 134{
ddc9e79a 135 const unsigned int header_len = strlen(GIT_SYMREF);
2f8a8ab2
VM
136 const char *refname_start;
137 char *eol;
9282e921 138
13224ea4 139 refname_start = (const char *)file_content->ptr;
9282e921 140
13224ea4 141 if (file_content->size < (header_len + 1))
1a481123 142 goto corrupt;
9282e921 143
932d1baf 144 /*
2f8a8ab2 145 * Assume we have already checked for the header
932d1baf 146 * before calling this function
2f8a8ab2 147 */
2f8a8ab2 148 refname_start += header_len;
9282e921 149
d4a0b124 150 ref->target.symbolic = git__strdup(refname_start);
1a481123 151 GITERR_CHECK_ALLOC(ref->target.symbolic);
9282e921 152
2f8a8ab2 153 /* remove newline at the end of file */
d4a0b124 154 eol = strchr(ref->target.symbolic, '\n');
ff5873ad 155 if (eol == NULL)
1a481123 156 goto corrupt;
ff5873ad
VM
157
158 *eol = '\0';
159 if (eol[-1] == '\r')
160 eol[-1] = '\0';
9282e921 161
1a481123
VM
162 return 0;
163
164corrupt:
165 giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
166 return -1;
9282e921 167}
168
13224ea4 169static int loose_parse_oid(git_oid *oid, git_buf *file_content)
2f8a8ab2
VM
170{
171 char *buffer;
86194b24 172
13224ea4 173 buffer = (char *)file_content->ptr;
9282e921 174
2f8a8ab2 175 /* File format: 40 chars (OID) + newline */
13224ea4 176 if (file_content->size < GIT_OID_HEXSZ + 1)
1a481123 177 goto corrupt;
9282e921 178
1a481123
VM
179 if (git_oid_fromstr(oid, buffer) < 0)
180 goto corrupt;
9282e921 181
ff5873ad
VM
182 buffer = buffer + GIT_OID_HEXSZ;
183 if (*buffer == '\r')
184 buffer++;
185
186 if (*buffer != '\n')
1a481123 187 goto corrupt;
9282e921 188
0d0fa7c3 189 return 0;
1a481123
VM
190
191corrupt:
192 giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
193 return -1;
9282e921 194}
195
97769280 196static git_rtype loose_guess_rtype(const git_buf *full_path)
00571828 197{
13224ea4 198 git_buf ref_file = GIT_BUF_INIT;
00571828
VM
199 git_rtype type;
200
201 type = GIT_REF_INVALID;
202
0d0fa7c3 203 if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) {
13224ea4 204 if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0)
00571828
VM
205 type = GIT_REF_SYMBOLIC;
206 else
207 type = GIT_REF_OID;
208 }
209
13224ea4 210 git_buf_free(&ref_file);
00571828
VM
211 return type;
212}
213
d4a0b124
VM
214static int loose_lookup(git_reference *ref)
215{
1a481123 216 int result, updated;
13224ea4 217 git_buf ref_file = GIT_BUF_INIT;
d4a0b124 218
1a481123
VM
219 result = reference_read(&ref_file, &ref->mtime,
220 ref->owner->path_repository, ref->name, &updated);
221
222 if (result < 0)
223 return result;
d4a0b124
VM
224
225 if (!updated)
1a481123 226 return 0;
d4a0b124 227
fa515656 228 if (ref->flags & GIT_REF_SYMBOLIC) {
854eccbb 229 git__free(ref->target.symbolic);
fa515656
VM
230 ref->target.symbolic = NULL;
231 }
d4a0b124
VM
232
233 ref->flags = 0;
234
13224ea4 235 if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) {
d4a0b124 236 ref->flags |= GIT_REF_SYMBOLIC;
1a481123 237 result = loose_parse_symbolic(ref, &ref_file);
d4a0b124
VM
238 } else {
239 ref->flags |= GIT_REF_OID;
1a481123 240 result = loose_parse_oid(&ref->target.oid, &ref_file);
d4a0b124
VM
241 }
242
13224ea4 243 git_buf_free(&ref_file);
1a481123 244 return result;
d4a0b124
VM
245}
246
247static int loose_lookup_to_packfile(
248 struct packref **ref_out,
932d1baf 249 git_repository *repo,
d4a0b124 250 const char *name)
9282e921 251{
13224ea4 252 git_buf ref_file = GIT_BUF_INIT;
d4a0b124
VM
253 struct packref *ref = NULL;
254 size_t name_len;
9282e921 255
2f8a8ab2 256 *ref_out = NULL;
9282e921 257
1a481123
VM
258 if (reference_read(&ref_file, NULL, repo->path_repository, name, NULL) < 0)
259 return -1;
9282e921 260
d4a0b124
VM
261 name_len = strlen(name);
262 ref = git__malloc(sizeof(struct packref) + name_len + 1);
1a481123 263 GITERR_CHECK_ALLOC(ref);
1d8cc731 264
d4a0b124
VM
265 memcpy(ref->name, name, name_len);
266 ref->name[name_len] = 0;
1d8cc731 267
1a481123
VM
268 if (loose_parse_oid(&ref->oid, &ref_file) < 0) {
269 git_buf_free(&ref_file);
270 free(ref);
271 return -1;
272 }
ff5873ad 273
d4a0b124
VM
274 ref->flags = GIT_PACKREF_WAS_LOOSE;
275
2f8a8ab2 276 *ref_out = ref;
13224ea4 277 git_buf_free(&ref_file);
1a481123 278 return 0;
9282e921 279}
280
d4a0b124 281static int loose_write(git_reference *ref)
87d3acf4 282{
b762e576 283 git_filebuf file = GIT_FILEBUF_INIT;
97769280 284 git_buf ref_path = GIT_BUF_INIT;
7341bf87 285 struct stat st;
87d3acf4 286
1a481123
VM
287 if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0)
288 return -1;
289
4615f0f7 290 /* Remove a possibly existing empty directory hierarchy
291 * which name would collide with the reference name
292 */
293 if (git_path_isdir(git_buf_cstr(&ref_path)) &&
294 (git_futils_rmdir_r(git_buf_cstr(&ref_path), GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0)) {
295 git_buf_free(&ref_path);
296 return -1;
297 }
298
1a481123
VM
299 if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {
300 git_buf_free(&ref_path);
301 return -1;
302 }
87d3acf4 303
1a481123 304 git_buf_free(&ref_path);
87d3acf4 305
d4a0b124 306 if (ref->flags & GIT_REF_OID) {
e7e0e20f 307 char oid[GIT_OID_HEXSZ + 1];
87d3acf4 308
d4a0b124
VM
309 git_oid_fmt(oid, &ref->target.oid);
310 oid[GIT_OID_HEXSZ] = '\0';
87d3acf4 311
1a481123 312 git_filebuf_printf(&file, "%s\n", oid);
87d3acf4 313
1a481123
VM
314 } else if (ref->flags & GIT_REF_SYMBOLIC) {
315 git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic);
87d3acf4 316 } else {
1a481123 317 assert(0); /* don't let this happen */
87d3acf4
VM
318 }
319
1a481123 320 if (p_stat(ref_path.ptr, &st) == 0)
7341bf87
VM
321 ref->mtime = st.st_mtime;
322
d4a0b124 323 return git_filebuf_commit(&file, GIT_REFS_FILE_MODE);
87d3acf4
VM
324}
325
87d3acf4 326static int packed_parse_peel(
d4a0b124 327 struct packref *tag_ref,
932d1baf 328 const char **buffer_out,
2f8a8ab2 329 const char *buffer_end)
9282e921 330{
2f8a8ab2
VM
331 const char *buffer = *buffer_out + 1;
332
333 assert(buffer[-1] == '^');
9282e921 334
335 /* Ensure it's not the first entry of the file */
2f8a8ab2 336 if (tag_ref == NULL)
1a481123 337 goto corrupt;
9282e921 338
339 /* Ensure reference is a tag */
d4a0b124 340 if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0)
1a481123 341 goto corrupt;
9282e921 342
2f8a8ab2 343 if (buffer + GIT_OID_HEXSZ >= buffer_end)
1a481123 344 goto corrupt;
9282e921 345
2f8a8ab2 346 /* Is this a valid object id? */
0d0fa7c3 347 if (git_oid_fromstr(&tag_ref->peel, buffer) < 0)
1a481123 348 goto corrupt;
2f8a8ab2 349
ff5873ad
VM
350 buffer = buffer + GIT_OID_HEXSZ;
351 if (*buffer == '\r')
352 buffer++;
353
354 if (*buffer != '\n')
1a481123 355 goto corrupt;
ff5873ad
VM
356
357 *buffer_out = buffer + 1;
1a481123
VM
358 return 0;
359
360corrupt:
361 giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
362 return -1;
9282e921 363}
364
87d3acf4 365static int packed_parse_oid(
d4a0b124 366 struct packref **ref_out,
2f8a8ab2
VM
367 const char **buffer_out,
368 const char *buffer_end)
9282e921 369{
d4a0b124 370 struct packref *ref = NULL;
2f8a8ab2
VM
371
372 const char *buffer = *buffer_out;
373 const char *refname_begin, *refname_end;
374
d4a0b124 375 size_t refname_len;
1d8cc731 376 git_oid id;
9282e921 377
2f8a8ab2 378 refname_begin = (buffer + GIT_OID_HEXSZ + 1);
1a481123
VM
379 if (refname_begin >= buffer_end || refname_begin[-1] != ' ')
380 goto corrupt;
9282e921 381
2f8a8ab2 382 /* Is this a valid object id? */
1a481123
VM
383 if (git_oid_fromstr(&id, buffer) < 0)
384 goto corrupt;
9282e921 385
2f8a8ab2 386 refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
1a481123
VM
387 if (refname_end == NULL)
388 goto corrupt;
9282e921 389
d4a0b124
VM
390 if (refname_end[-1] == '\r')
391 refname_end--;
9282e921 392
d4a0b124 393 refname_len = refname_end - refname_begin;
9282e921 394
d4a0b124 395 ref = git__malloc(sizeof(struct packref) + refname_len + 1);
1a481123 396 GITERR_CHECK_ALLOC(ref);
1d8cc731 397
d4a0b124
VM
398 memcpy(ref->name, refname_begin, refname_len);
399 ref->name[refname_len] = 0;
6c8b458d 400
86194b24 401 git_oid_cpy(&ref->oid, &id);
d4a0b124
VM
402
403 ref->flags = 0;
9282e921 404
2f8a8ab2 405 *ref_out = ref;
9282e921 406 *buffer_out = refname_end + 1;
407
1a481123 408 return 0;
2f8a8ab2 409
1a481123 410corrupt:
854eccbb 411 git__free(ref);
1a481123
VM
412 giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
413 return -1;
9282e921 414}
415
87d3acf4 416static int packed_load(git_repository *repo)
9282e921 417{
1a481123 418 int result, updated;
13224ea4 419 git_buf packfile = GIT_BUF_INIT;
2f8a8ab2 420 const char *buffer_start, *buffer_end;
87d3acf4
VM
421 git_refcache *ref_cache = &repo->references;
422
c4982328
CMN
423 /* First we make sure we have allocated the hash table */
424 if (ref_cache->packfile == NULL) {
7341bf87 425 ref_cache->packfile = git_hashtable_alloc(
ee1f0b1a 426 default_table_size, git_hash__strhash_cb, git_hash__strcmp_cb);
7341bf87 427
1a481123 428 GITERR_CHECK_ALLOC(ref_cache->packfile);
7341bf87
VM
429 }
430
1a481123 431 result = reference_read(&packfile, &ref_cache->packfile_time,
d4a0b124 432 repo->path_repository, GIT_PACKEDREFS_FILE, &updated);
87d3acf4 433
c4982328
CMN
434 /*
435 * If we couldn't find the file, we need to clear the table and
436 * return. On any other error, we return that error. If everything
437 * went fine and the file wasn't updated, then there's nothing new
438 * for us here, so just return. Anything else means we need to
439 * refresh the packed refs.
440 */
1a481123 441 if (result == GIT_ENOTFOUND) {
c4982328 442 git_hashtable_clear(ref_cache->packfile);
1a481123 443 return 0;
c4982328 444 }
2f8a8ab2 445
1a481123
VM
446 if (result < 0)
447 return -1;
448
449 if (!updated)
450 return 0;
451
c4982328
CMN
452 /*
453 * At this point, we want to refresh the packed refs. We already
454 * have the contents in our buffer.
455 */
c4982328 456 git_hashtable_clear(ref_cache->packfile);
9282e921 457
13224ea4
VM
458 buffer_start = (const char *)packfile.ptr;
459 buffer_end = (const char *)(buffer_start) + packfile.size;
9282e921 460
7c8a7b91
VM
461 while (buffer_start < buffer_end && buffer_start[0] == '#') {
462 buffer_start = strchr(buffer_start, '\n');
1a481123
VM
463 if (buffer_start == NULL)
464 goto parse_failed;
465
ddc9e79a 466 buffer_start++;
7c8a7b91 467 }
ddc9e79a 468
9282e921 469 while (buffer_start < buffer_end) {
d4a0b124 470 struct packref *ref = NULL;
2f8a8ab2 471
1a481123
VM
472 if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0)
473 goto parse_failed;
2f8a8ab2 474
9282e921 475 if (buffer_start[0] == '^') {
1a481123
VM
476 if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0)
477 goto parse_failed;
2f8a8ab2 478 }
9282e921 479
1a481123
VM
480 if (git_hashtable_insert(ref_cache->packfile, ref->name, ref) < 0)
481 return -1;
2f8a8ab2 482 }
9282e921 483
13224ea4 484 git_buf_free(&packfile);
1a481123 485 return 0;
7341bf87 486
1a481123 487parse_failed:
7341bf87
VM
488 git_hashtable_free(ref_cache->packfile);
489 ref_cache->packfile = NULL;
13224ea4 490 git_buf_free(&packfile);
1a481123 491 return -1;
2f8a8ab2 492}
9282e921 493
00571828 494
00571828 495struct dirent_list_data {
7ad96e51 496 git_repository *repo;
00571828
VM
497 size_t repo_path_len;
498 unsigned int list_flags;
09e8de0f
VM
499
500 int (*callback)(const char *, void *);
501 void *callback_payload;
00571828
VM
502};
503
1a481123 504static int _dirent_loose_listall(void *_data, git_buf *full_path)
00571828
VM
505{
506 struct dirent_list_data *data = (struct dirent_list_data *)_data;
97769280 507 const char *file_path = full_path->ptr + data->repo_path_len;
00571828 508
1a481123 509 if (git_path_isdir(full_path->ptr) == true)
1744fafe 510 return git_path_direach(full_path, _dirent_loose_listall, _data);
00571828 511
7ad96e51 512 /* do not add twice a reference that exists already in the packfile */
b5abb881
VM
513 if ((data->list_flags & GIT_REF_PACKED) != 0 &&
514 git_hashtable_lookup(data->repo->references.packfile, file_path) != NULL)
1a481123 515 return 0;
7ad96e51 516
09e8de0f
VM
517 if (data->list_flags != GIT_REF_LISTALL) {
518 if ((data->list_flags & loose_guess_rtype(full_path)) == 0)
1a481123 519 return 0; /* we are filtering out this reference */
09e8de0f 520 }
00571828 521
09e8de0f 522 return data->callback(file_path, data->callback_payload);
00571828
VM
523}
524
97769280 525static int _dirent_loose_load(void *data, git_buf *full_path)
2f8a8ab2 526{
87d3acf4 527 git_repository *repository = (git_repository *)data;
6c8b458d 528 void *old_ref = NULL;
d4a0b124 529 struct packref *ref;
97769280 530 const char *file_path;
86194b24 531
1a481123 532 if (git_path_isdir(full_path->ptr) == true)
1744fafe 533 return git_path_direach(full_path, _dirent_loose_load, repository);
9282e921 534
97769280 535 file_path = full_path->ptr + strlen(repository->path_repository);
d4a0b124 536
1a481123
VM
537 if (loose_lookup_to_packfile(&ref, repository, file_path) < 0)
538 return -1;
87d3acf4 539
1a481123
VM
540 if (git_hashtable_insert2(repository->references.packfile,
541 ref->name, ref, &old_ref) < 0) {
542 git__free(ref);
543 return -1;
87d3acf4 544 }
9282e921 545
1a481123
VM
546 git__free(old_ref);
547 return 0;
2f8a8ab2
VM
548}
549
87d3acf4
VM
550/*
551 * Load all the loose references from the repository
552 * into the in-memory Packfile, and build a vector with
553 * all the references so it can be written back to
554 * disk.
555 */
556static int packed_loadloose(git_repository *repository)
2f8a8ab2 557{
97769280 558 git_buf refs_path = GIT_BUF_INIT;
1a481123 559 int result;
86194b24 560
87d3acf4
VM
561 /* the packfile must have been previously loaded! */
562 assert(repository->references.packfile);
9282e921 563
1a481123
VM
564 if (git_buf_joinpath(&refs_path, repository->path_repository, GIT_REFS_DIR) < 0)
565 return -1;
86194b24 566
87d3acf4
VM
567 /*
568 * Load all the loose files from disk into the Packfile table.
569 * This will overwrite any old packed entries with their
932d1baf 570 * updated loose versions
87d3acf4 571 */
1a481123 572 result = git_path_direach(&refs_path, _dirent_loose_load, repository);
97769280 573 git_buf_free(&refs_path);
1a481123
VM
574
575 return result;
2f8a8ab2 576}
9282e921 577
87d3acf4
VM
578/*
579 * Write a single reference into a packfile
580 */
d4a0b124 581static int packed_write_ref(struct packref *ref, git_filebuf *file)
2f8a8ab2 582{
87d3acf4 583 char oid[GIT_OID_HEXSZ + 1];
2f8a8ab2 584
87d3acf4
VM
585 git_oid_fmt(oid, &ref->oid);
586 oid[GIT_OID_HEXSZ] = 0;
2f8a8ab2 587
932d1baf 588 /*
87d3acf4
VM
589 * For references that peel to an object in the repo, we must
590 * write the resulting peel on a separate line, e.g.
591 *
592 * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
593 * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
594 *
595 * This obviously only applies to tags.
596 * The required peels have already been loaded into `ref->peel_target`.
597 */
d4a0b124 598 if (ref->flags & GIT_PACKREF_HAS_PEEL) {
87d3acf4 599 char peel[GIT_OID_HEXSZ + 1];
d4a0b124 600 git_oid_fmt(peel, &ref->peel);
87d3acf4
VM
601 peel[GIT_OID_HEXSZ] = 0;
602
1a481123
VM
603 if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0)
604 return -1;
87d3acf4 605 } else {
1a481123
VM
606 if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0)
607 return -1;
87d3acf4
VM
608 }
609
1a481123 610 return 0;
9282e921 611}
612
87d3acf4
VM
613/*
614 * Find out what object this reference resolves to.
615 *
932d1baf 616 * For references that point to a 'big' tag (e.g. an
87d3acf4
VM
617 * actual tag object on the repository), we need to
618 * cache on the packfile the OID of the object to
619 * which that 'big tag' is pointing to.
620 */
d4a0b124 621static int packed_find_peel(git_repository *repo, struct packref *ref)
9282e921 622{
d79f1da6 623 git_object *object;
32054c24 624
d4a0b124 625 if (ref->flags & GIT_PACKREF_HAS_PEEL)
1a481123 626 return 0;
9282e921 627
87d3acf4
VM
628 /*
629 * Only applies to tags, i.e. references
630 * in the /refs/tags folder
631 */
d4a0b124 632 if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0)
1a481123 633 return 0;
9282e921 634
87d3acf4 635 /*
d79f1da6 636 * Find the tagged object in the repository
87d3acf4 637 */
1a481123
VM
638 if (git_object_lookup(&object, repo, &ref->oid, GIT_OBJ_ANY) < 0)
639 return -1;
86194b24 640
87d3acf4 641 /*
d79f1da6
VM
642 * If the tagged object is a Tag object, we need to resolve it;
643 * if the ref is actually a 'weak' ref, we don't need to resolve
644 * anything.
87d3acf4 645 */
d79f1da6
VM
646 if (git_object_type(object) == GIT_OBJ_TAG) {
647 git_tag *tag = (git_tag *)object;
86194b24 648
d79f1da6
VM
649 /*
650 * Find the object pointed at by this tag
651 */
d4a0b124
VM
652 git_oid_cpy(&ref->peel, git_tag_target_oid(tag));
653 ref->flags |= GIT_PACKREF_HAS_PEEL;
d79f1da6
VM
654
655 /*
656 * The reference has now cached the resolved OID, and is
657 * marked at such. When written to the packfile, it'll be
658 * accompanied by this resolved oid
659 */
660 }
87d3acf4 661
45e79e37 662 git_object_free(object);
1a481123 663 return 0;
2f8a8ab2 664}
9282e921 665
87d3acf4
VM
666/*
667 * Remove all loose references
668 *
669 * Once we have successfully written a packfile,
670 * all the loose references that were packed must be
671 * removed from disk.
672 *
673 * This is a dangerous method; make sure the packfile
674 * is well-written, because we are destructing references
675 * here otherwise.
676 */
677static int packed_remove_loose(git_repository *repo, git_vector *packing_list)
2f8a8ab2 678{
87d3acf4 679 unsigned int i;
97769280 680 git_buf full_path = GIT_BUF_INIT;
1a481123 681 int failed = 0;
87d3acf4
VM
682
683 for (i = 0; i < packing_list->length; ++i) {
d4a0b124 684 struct packref *ref = git_vector_get(packing_list, i);
8f90ced5 685
d4a0b124 686 if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0)
8f90ced5 687 continue;
688
1a481123
VM
689 if (git_buf_joinpath(&full_path, repo->path_repository, ref->name) < 0)
690 return -1; /* critical; do not try to recover on oom */
97769280 691
1a481123
VM
692 if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) {
693 if (failed)
694 continue;
87d3acf4 695
1a481123
VM
696 giterr_set(GITERR_REFERENCE,
697 "Failed to remove loose reference '%s' after packing: %s",
698 full_path.ptr, strerror(errno));
699
700 failed = 1;
701 }
87d3acf4
VM
702
703 /*
704 * if we fail to remove a single file, this is *not* good,
705 * but we should keep going and remove as many as possible.
706 * After we've removed as many files as possible, we return
707 * the error code anyway.
87d3acf4
VM
708 */
709 }
710
97769280 711 git_buf_free(&full_path);
1a481123 712 return failed ? -1 : 0;
2f8a8ab2 713}
9282e921 714
87d3acf4 715static int packed_sort(const void *a, const void *b)
2f8a8ab2 716{
d4a0b124
VM
717 const struct packref *ref_a = (const struct packref *)a;
718 const struct packref *ref_b = (const struct packref *)b;
87d3acf4
VM
719
720 return strcmp(ref_a->name, ref_b->name);
2f8a8ab2
VM
721}
722
87d3acf4
VM
723/*
724 * Write all the contents in the in-memory packfile to disk.
725 */
726static int packed_write(git_repository *repo)
2f8a8ab2 727{
b762e576 728 git_filebuf pack_file = GIT_FILEBUF_INIT;
87d3acf4 729 unsigned int i;
97769280 730 git_buf pack_file_path = GIT_BUF_INIT;
87d3acf4
VM
731 git_vector packing_list;
732 size_t total_refs;
2f8a8ab2 733
87d3acf4 734 assert(repo && repo->references.packfile);
9282e921 735
87d3acf4 736 total_refs = repo->references.packfile->key_count;
1a481123
VM
737
738 if (git_vector_init(&packing_list, total_refs, packed_sort) < 0)
739 return -1;
9282e921 740
87d3acf4
VM
741 /* Load all the packfile into a vector */
742 {
d4a0b124 743 struct packref *reference;
87d3acf4 744
854eccbb 745 GIT_HASHTABLE_FOREACH_VALUE(repo->references.packfile, reference,
d4a0b124
VM
746 /* cannot fail: vector already has the right size */
747 git_vector_insert(&packing_list, reference);
87d3acf4 748 );
9282e921 749 }
750
87d3acf4
VM
751 /* sort the vector so the entries appear sorted on the packfile */
752 git_vector_sort(&packing_list);
9282e921 753
87d3acf4 754 /* Now we can open the file! */
1a481123
VM
755 if (git_buf_joinpath(&pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE) < 0)
756 goto cleanup_memory;
97769280 757
1a481123
VM
758 if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0)
759 goto cleanup_packfile;
9282e921 760
7c8a7b91
VM
761 /* Packfiles have a header... apparently
762 * This is in fact not required, but we might as well print it
763 * just for kicks */
1a481123
VM
764 if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0)
765 goto cleanup_packfile;
2f8a8ab2 766
87d3acf4 767 for (i = 0; i < packing_list.length; ++i) {
d4a0b124 768 struct packref *ref = (struct packref *)git_vector_get(&packing_list, i);
2f8a8ab2 769
1a481123
VM
770 if (packed_find_peel(repo, ref) < 0)
771 goto cleanup_packfile;
2b397327 772
1a481123
VM
773 if (packed_write_ref(ref, &pack_file) < 0)
774 goto cleanup_packfile;
87d3acf4 775 }
2f8a8ab2 776
87d3acf4
VM
777 /* if we've written all the references properly, we can commit
778 * the packfile to make the changes effective */
1a481123
VM
779 if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0)
780 goto cleanup_memory;
7341bf87 781
1a481123
VM
782 /* when and only when the packfile has been properly written,
783 * we can go ahead and remove the loose refs */
784 if (packed_remove_loose(repo, &packing_list) < 0)
785 goto cleanup_memory;
7341bf87 786
1a481123
VM
787 {
788 struct stat st;
789 if (p_stat(pack_file_path.ptr, &st) == 0)
790 repo->references.packfile_time = st.st_mtime;
791 }
9282e921 792
87d3acf4 793 git_vector_free(&packing_list);
97769280 794 git_buf_free(&pack_file_path);
1d8cc731 795
1a481123
VM
796 /* we're good now */
797 return 0;
798
799cleanup_packfile:
800 git_filebuf_cleanup(&pack_file);
97769280 801
1a481123
VM
802cleanup_memory:
803 git_vector_free(&packing_list);
804 git_buf_free(&pack_file_path);
805
806 return -1;
87d3acf4 807}
2f8a8ab2 808
45d387ac
VM
809struct reference_available_t {
810 const char *new_ref;
811 const char *old_ref;
812 int available;
813};
814
1b6d8163
MS
815static int _reference_available_cb(const char *ref, void *data)
816{
45d387ac 817 struct reference_available_t *d;
f120e92b 818
1b6d8163 819 assert(ref && data);
1a481123 820 d = (struct reference_available_t *)data;
1b6d8163 821
45d387ac 822 if (!d->old_ref || strcmp(d->old_ref, ref)) {
1b6d8163 823 int reflen = strlen(ref);
45d387ac 824 int newlen = strlen(d->new_ref);
1b6d8163 825 int cmplen = reflen < newlen ? reflen : newlen;
45d387ac 826 const char *lead = reflen < newlen ? d->new_ref : ref;
1b6d8163 827
45d387ac
VM
828 if (!strncmp(d->new_ref, ref, cmplen) && lead[cmplen] == '/') {
829 d->available = 0;
830 return -1;
831 }
1b6d8163
MS
832 }
833
45d387ac 834 return 0;
1b6d8163
MS
835}
836
1a481123 837static int reference_path_available(
d4a0b124
VM
838 git_repository *repo,
839 const char *ref,
1a481123 840 const char* old_ref)
1b6d8163 841{
45d387ac 842 struct reference_available_t data;
1b6d8163 843
45d387ac
VM
844 data.new_ref = ref;
845 data.old_ref = old_ref;
846 data.available = 1;
1b6d8163 847
d4a0b124 848 if (git_reference_foreach(repo, GIT_REF_LISTALL,
1a481123
VM
849 _reference_available_cb, (void *)&data) < 0)
850 return -1;
851
852 if (!data.available) {
853 giterr_set(GITERR_REFERENCE,
e1de726c 854 "The path to reference '%s' collides with an existing one", ref);
45d387ac 855 return -1;
1a481123 856 }
1b6d8163 857
45d387ac 858 return 0;
1b6d8163
MS
859}
860
d4a0b124 861static int reference_exists(int *exists, git_repository *repo, const char *ref_name)
2f8a8ab2 862{
97769280 863 git_buf ref_path = GIT_BUF_INIT;
2f8a8ab2 864
1a481123
VM
865 if (packed_load(repo) < 0)
866 return -1;
2f8a8ab2 867
1a481123
VM
868 if (git_buf_joinpath(&ref_path, repo->path_repository, ref_name) < 0)
869 return -1;
2f8a8ab2 870
1a481123 871 if (git_path_isfile(ref_path.ptr) == true ||
97769280 872 git_hashtable_lookup(repo->references.packfile, ref_path.ptr) != NULL) {
d4a0b124
VM
873 *exists = 1;
874 } else {
875 *exists = 0;
876 }
2f8a8ab2 877
97769280 878 git_buf_free(&ref_path);
1a481123
VM
879 return 0;
880}
97769280 881
1a481123
VM
882/*
883 * Check if a reference could be written to disk, based on:
884 *
885 * - Whether a reference with the same name already exists,
886 * and we are allowing or disallowing overwrites
887 *
888 * - Whether the name of the reference would collide with
889 * an existing path
890 */
891static int reference_can_write(
892 git_repository *repo,
893 const char *refname,
894 const char *previous_name,
895 int force)
896{
897 /* see if the reference shares a path with an existing reference;
898 * if a path is shared, we cannot create the reference, even when forcing */
899 if (reference_path_available(repo, refname, previous_name) < 0)
900 return -1;
901
902 /* check if the reference actually exists, but only if we are not forcing
903 * the rename. If we are forcing, it's OK to overwrite */
904 if (!force) {
905 int exists;
906
907 if (reference_exists(&exists, repo, refname) < 0)
908 return -1;
909
910 /* We cannot proceed if the reference already exists and we're not forcing
911 * the rename; the existing one would be overwritten */
912 if (exists) {
913 giterr_set(GITERR_REFERENCE,
e1de726c 914 "A reference with that name (%s) already exists", refname);
1a481123
VM
915 return GIT_EEXISTS;
916 }
917 }
918
919 /* FIXME: if the reference exists and we are forcing, do we really need to
920 * remove the reference first?
921 *
922 * Two cases:
923 *
924 * - the reference already exists and is loose: not a problem, the file
925 * gets overwritten on disk
926 *
927 * - the reference already exists and is packed: we write a new one as
928 * loose, which by all means renders the packed one useless
929 */
930
931 return 0;
d4a0b124 932}
2f8a8ab2 933
1a481123 934
d4a0b124
VM
935static int packed_lookup(git_reference *ref)
936{
d4a0b124 937 struct packref *pack_ref = NULL;
9282e921 938
1a481123
VM
939 if (packed_load(ref->owner) < 0)
940 return -1;
9282e921 941
1a481123
VM
942 /* maybe the packfile hasn't changed at all, so we don't
943 * have to re-lookup the reference */
944 if ((ref->flags & GIT_REF_PACKED) &&
d4a0b124 945 ref->mtime == ref->owner->references.packfile_time)
1a481123 946 return 0;
86194b24 947
fa515656 948 if (ref->flags & GIT_REF_SYMBOLIC) {
854eccbb 949 git__free(ref->target.symbolic);
fa515656
VM
950 ref->target.symbolic = NULL;
951 }
d4a0b124
VM
952
953 /* Look up on the packfile */
954 pack_ref = git_hashtable_lookup(ref->owner->references.packfile, ref->name);
1a481123
VM
955 if (pack_ref == NULL) {
956 giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref->name);
957 return GIT_ENOTFOUND;
958 }
d4a0b124
VM
959
960 ref->flags = GIT_REF_OID | GIT_REF_PACKED;
961 ref->mtime = ref->owner->references.packfile_time;
962 git_oid_cpy(&ref->target.oid, &pack_ref->oid);
963
1a481123 964 return 0;
2f8a8ab2 965}
9282e921 966
1a481123 967static int reference_lookup(git_reference *ref)
a46ec457 968{
45d387ac 969 int result;
a46ec457 970
1a481123
VM
971 result = loose_lookup(ref);
972 if (result == 0)
973 return 0;
a46ec457 974
1a481123
VM
975 /* only try to lookup this reference on the packfile if it
976 * wasn't found on the loose refs; not if there was a critical error */
977 if (result == GIT_ENOTFOUND) {
978 giterr_clear();
979 result = packed_lookup(ref);
980 if (result == 0)
981 return 0;
982 }
a46ec457 983
1a481123 984 /* unexpected error; free the reference */
d4a0b124 985 git_reference_free(ref);
1a481123 986 return result;
a46ec457
MS
987}
988
d4a0b124
VM
989/*
990 * Delete a reference.
991 * This is an internal method; the reference is removed
992 * from disk or the packfile, but the pointer is not freed
87d3acf4 993 */
1a481123 994static int reference_delete(git_reference *ref)
87d3acf4 995{
45d387ac 996 int result;
d4a0b124 997
87d3acf4
VM
998 assert(ref);
999
d4a0b124
VM
1000 /* If the reference is packed, this is an expensive operation.
1001 * We need to reload the packfile, remove the reference from the
1002 * packing list, and repack */
1003 if (ref->flags & GIT_REF_PACKED) {
20c50b9e 1004 struct packref *packref;
d4a0b124 1005 /* load the existing packfile */
1a481123 1006 if (packed_load(ref->owner) < 0)
45d387ac 1007 return -1;
87d3acf4 1008
20c50b9e 1009 if (git_hashtable_remove2(ref->owner->references.packfile,
45d387ac 1010 ref->name, (void **) &packref) < 0) {
1a481123 1011 giterr_set(GITERR_REFERENCE,
45d387ac
VM
1012 "Reference %s stopped existing in the packfile", ref->name);
1013 return -1;
1014 }
87d3acf4 1015
45d387ac 1016 git__free(packref);
1a481123 1017 if (packed_write(ref->owner) < 0)
45d387ac 1018 return -1;
87d3acf4 1019
d4a0b124
VM
1020 /* If the reference is loose, we can just remove the reference
1021 * from the filesystem */
1022 } else {
d4a0b124 1023 git_reference *ref_in_pack;
97769280 1024 git_buf full_path = GIT_BUF_INIT;
a46ec457 1025
1a481123 1026 if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0)
45d387ac 1027 return -1;
a46ec457 1028
45d387ac 1029 result = p_unlink(full_path.ptr);
97769280 1030 git_buf_free(&full_path); /* done with path at this point */
45d387ac
VM
1031
1032 if (result < 0) {
ae9e29fd 1033 giterr_set(GITERR_OS, "Failed to unlink '%s'", full_path.ptr);
45d387ac
VM
1034 return -1;
1035 }
a46ec457 1036
d4a0b124
VM
1037 /* When deleting a loose reference, we have to ensure that an older
1038 * packed version of it doesn't exist */
1a481123 1039 if (git_reference_lookup(&ref_in_pack, ref->owner, ref->name) == 0) {
d4a0b124 1040 assert((ref_in_pack->flags & GIT_REF_PACKED) != 0);
1a481123 1041 return git_reference_delete(ref_in_pack);
d4a0b124 1042 }
1a481123
VM
1043
1044 giterr_clear();
d4a0b124
VM
1045 }
1046
45d387ac 1047 return 0;
a46ec457
MS
1048}
1049
1a481123 1050int git_reference_delete(git_reference *ref)
a46ec457 1051{
1a481123 1052 int result = reference_delete(ref);
d4a0b124 1053 git_reference_free(ref);
45d387ac 1054 return result;
a46ec457
MS
1055}
1056
d4a0b124 1057int git_reference_lookup(git_reference **ref_out,
1a481123 1058 git_repository *repo, const char *name)
a46ec457 1059{
d4a0b124
VM
1060 char normalized_name[GIT_REFNAME_MAX];
1061 git_reference *ref = NULL;
1a481123 1062 int result;
d4a0b124
VM
1063
1064 assert(ref_out && repo && name);
1a481123 1065 *ref_out = NULL;
d4a0b124 1066
1a481123 1067 if (normalize_name(normalized_name, sizeof(normalized_name), name, 0) < 0)
45d387ac 1068 return -1;
a46ec457 1069
1a481123 1070 if (reference_alloc(&ref, repo, normalized_name) < 0)
45d387ac 1071 return -1;
d4a0b124 1072
1a481123
VM
1073 result = reference_lookup(ref);
1074 if (result == 0)
1075 *ref_out = ref;
1076
1077 return result;
a46ec457
MS
1078}
1079
d4a0b124
VM
1080/**
1081 * Getters
1082 */
1083git_rtype git_reference_type(git_reference *ref)
87d3acf4
VM
1084{
1085 assert(ref);
d4a0b124
VM
1086
1087 if (ref->flags & GIT_REF_OID)
1088 return GIT_REF_OID;
1089
1090 if (ref->flags & GIT_REF_SYMBOLIC)
1091 return GIT_REF_SYMBOLIC;
1092
1093 return GIT_REF_INVALID;
87d3acf4
VM
1094}
1095
d4a0b124 1096int git_reference_is_packed(git_reference *ref)
87d3acf4
VM
1097{
1098 assert(ref);
d4a0b124 1099 return !!(ref->flags & GIT_REF_PACKED);
87d3acf4
VM
1100}
1101
d4a0b124 1102const char *git_reference_name(git_reference *ref)
87d3acf4
VM
1103{
1104 assert(ref);
d4a0b124 1105 return ref->name;
87d3acf4
VM
1106}
1107
d4a0b124 1108git_repository *git_reference_owner(git_reference *ref)
a46ec457 1109{
d4a0b124
VM
1110 assert(ref);
1111 return ref->owner;
a46ec457
MS
1112}
1113
d4a0b124 1114const git_oid *git_reference_oid(git_reference *ref)
87d3acf4
VM
1115{
1116 assert(ref);
1117
d4a0b124 1118 if ((ref->flags & GIT_REF_OID) == 0)
87d3acf4
VM
1119 return NULL;
1120
d4a0b124 1121 return &ref->target.oid;
87d3acf4
VM
1122}
1123
d4a0b124 1124const char *git_reference_target(git_reference *ref)
a46ec457 1125{
d4a0b124 1126 assert(ref);
a46ec457 1127
d4a0b124 1128 if ((ref->flags & GIT_REF_SYMBOLIC) == 0)
a46ec457
MS
1129 return NULL;
1130
d4a0b124 1131 return ref->target.symbolic;
a46ec457
MS
1132}
1133
d4a0b124
VM
1134int git_reference_create_symbolic(
1135 git_reference **ref_out,
1136 git_repository *repo,
1137 const char *name,
1138 const char *target,
1a481123 1139 int force)
d5afc039
VM
1140{
1141 char normalized[GIT_REFNAME_MAX];
d4a0b124 1142 git_reference *ref = NULL;
d5afc039 1143
1a481123 1144 if (normalize_name(normalized, sizeof(normalized), name, 0) < 0)
45d387ac 1145 return -1;
d5afc039 1146
1a481123 1147 if (reference_can_write(repo, normalized, NULL, force) < 0)
45d387ac 1148 return -1;
d5afc039 1149
1a481123 1150 if (reference_alloc(&ref, repo, normalized) < 0)
45d387ac 1151 return -1;
d5afc039 1152
d4a0b124
VM
1153 ref->flags |= GIT_REF_SYMBOLIC;
1154
1155 /* set the target; this will normalize the name automatically
1156 * and write the reference on disk */
1a481123 1157 if (git_reference_set_target(ref, target) < 0) {
45d387ac
VM
1158 git_reference_free(ref);
1159 return -1;
1160 }
d4a0b124
VM
1161 if (ref_out == NULL) {
1162 git_reference_free(ref);
1163 } else {
1164 *ref_out = ref;
d5afc039
VM
1165 }
1166
45d387ac 1167 return 0;
d5afc039
VM
1168}
1169
d4a0b124
VM
1170int git_reference_create_oid(
1171 git_reference **ref_out,
1172 git_repository *repo,
1173 const char *name,
1174 const git_oid *id,
1a481123 1175 int force)
a46ec457 1176{
d4a0b124
VM
1177 git_reference *ref = NULL;
1178 char normalized[GIT_REFNAME_MAX];
a46ec457 1179
1a481123 1180 if (normalize_name(normalized, sizeof(normalized), name, 1) < 0)
45d387ac 1181 return -1;
d5afc039 1182
1a481123 1183 if (reference_can_write(repo, normalized, NULL, force) < 0)
45d387ac 1184 return -1;
d5afc039 1185
1a481123 1186 if (reference_alloc(&ref, repo, name) < 0)
45d387ac 1187 return -1;
d4a0b124
VM
1188
1189 ref->flags |= GIT_REF_OID;
d5afc039
VM
1190
1191 /* set the oid; this will write the reference on disk */
1a481123 1192 if (git_reference_set_oid(ref, id) < 0) {
45d387ac
VM
1193 git_reference_free(ref);
1194 return -1;
1195 }
d5afc039 1196
d4a0b124
VM
1197 if (ref_out == NULL) {
1198 git_reference_free(ref);
1199 } else {
1200 *ref_out = ref;
d5afc039
VM
1201 }
1202
45d387ac 1203 return 0;
a46ec457 1204}
87d3acf4
VM
1205/*
1206 * Change the OID target of a reference.
1207 *
d4a0b124
VM
1208 * For both loose and packed references, just change
1209 * the oid in memory and (over)write the file in disk.
87d3acf4 1210 *
d4a0b124
VM
1211 * We do not repack packed references because of performance
1212 * reasons.
87d3acf4 1213 */
1a481123 1214int git_reference_set_oid(git_reference *ref, const git_oid *id)
87d3acf4 1215{
9462c471 1216 git_odb *odb = NULL;
87d3acf4 1217
45d387ac 1218 if ((ref->flags & GIT_REF_OID) == 0) {
1a481123 1219 giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");
45d387ac
VM
1220 return -1;
1221 }
87d3acf4 1222
9a53df7e
VM
1223 assert(ref->owner);
1224
1a481123 1225 if (git_repository_odb__weakptr(&odb, ref->owner) < 0)
45d387ac 1226 return -1;
9462c471 1227
9a53df7e
VM
1228 /* Don't let the user create references to OIDs that
1229 * don't exist in the ODB */
45d387ac 1230 if (!git_odb_exists(odb, id)) {
1a481123 1231 giterr_set(GITERR_REFERENCE,
45d387ac
VM
1232 "Target OID for the reference doesn't exist on the repository");
1233 return -1;
1234 }
87d3acf4 1235
d4a0b124
VM
1236 /* Update the OID value on `ref` */
1237 git_oid_cpy(&ref->target.oid, id);
87d3acf4 1238
9462c471 1239 /* Write back to disk */
1a481123 1240 return loose_write(ref);
a46ec457
MS
1241}
1242
87d3acf4
VM
1243/*
1244 * Change the target of a symbolic reference.
1245 *
1246 * This is easy because symrefs cannot be inside
1247 * a pack. We just change the target in memory
1248 * and overwrite the file on disk.
1249 */
1a481123 1250int git_reference_set_target(git_reference *ref, const char *target)
87d3acf4 1251{
d4a0b124 1252 char normalized[GIT_REFNAME_MAX];
87d3acf4 1253
45d387ac 1254 if ((ref->flags & GIT_REF_SYMBOLIC) == 0) {
1a481123 1255 giterr_set(GITERR_REFERENCE,
45d387ac
VM
1256 "Cannot set symbolic target on a direct reference");
1257 return -1;
1258 }
87d3acf4 1259
1a481123 1260 if (normalize_name(normalized, sizeof(normalized), target, 0))
45d387ac 1261 return -1;
87d3acf4 1262
d4a0b124
VM
1263 git__free(ref->target.symbolic);
1264 ref->target.symbolic = git__strdup(normalized);
1a481123 1265 GITERR_CHECK_ALLOC(ref->target.symbolic);
87d3acf4 1266
1a481123 1267 return loose_write(ref);
87d3acf4
VM
1268}
1269
1a481123 1270int git_reference_rename(git_reference *ref, const char *new_name, int force)
7376ad99 1271{
1a481123 1272 int result;
97769280 1273 git_buf aux_path = GIT_BUF_INIT;
0ffcf78a 1274 char normalized[GIT_REFNAME_MAX];
858dba58 1275
0ffcf78a 1276 const char *head_target = NULL;
1a481123 1277 git_reference *head = NULL;
7376ad99 1278
45d387ac 1279 if (normalize_name(normalized, sizeof(normalized),
1a481123 1280 new_name, ref->flags & GIT_REF_OID) < 0)
45d387ac 1281 return -1;
7376ad99 1282
1a481123 1283 if (reference_can_write(ref->owner, normalized, ref->name, force) < 0)
45d387ac 1284 return -1;
ca6f203c 1285
97769280 1286 /* Initialize path now so we won't get an allocation failure once
1a481123
VM
1287 * we actually start removing things. */
1288 if (git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name) < 0)
45d387ac 1289 return -1;
97769280 1290
0ffcf78a
MS
1291 /*
1292 * Now delete the old ref and remove an possibly existing directory
d4a0b124
VM
1293 * named `new_name`. Note that using the internal `reference_delete`
1294 * method deletes the ref from disk but doesn't free the pointer, so
1295 * we can still access the ref's attributes for creating the new one
0ffcf78a 1296 */
1a481123 1297 if (reference_delete(ref) < 0)
549bbd13 1298 goto cleanup;
7376ad99 1299
0ffcf78a
MS
1300 /*
1301 * Finally we can create the new reference.
1302 */
d4a0b124 1303 if (ref->flags & GIT_REF_SYMBOLIC) {
45d387ac 1304 result = git_reference_create_symbolic(
1a481123 1305 NULL, ref->owner, new_name, ref->target.symbolic, force);
0ffcf78a 1306 } else {
45d387ac 1307 result = git_reference_create_oid(
1a481123 1308 NULL, ref->owner, new_name, &ref->target.oid, force);
0ffcf78a 1309 }
7376ad99 1310
45d387ac 1311 if (result < 0)
64093ce5 1312 goto rollback;
7376ad99 1313
0ffcf78a
MS
1314 /*
1315 * Check if we have to update HEAD.
1316 */
1a481123
VM
1317 if (git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE) < 0) {
1318 giterr_set(GITERR_REFERENCE,
45d387ac 1319 "Failed to update HEAD after renaming reference");
0ffcf78a 1320 goto cleanup;
45d387ac 1321 }
7376ad99 1322
d4a0b124 1323 head_target = git_reference_target(head);
7376ad99 1324
d4a0b124 1325 if (head_target && !strcmp(head_target, ref->name)) {
1a481123
VM
1326 if (git_reference_create_symbolic(&head, ref->owner, "HEAD", new_name, 1) < 0) {
1327 giterr_set(GITERR_REFERENCE,
45d387ac 1328 "Failed to update HEAD after renaming reference");
d4a0b124 1329 goto cleanup;
45d387ac 1330 }
d4a0b124
VM
1331 }
1332
a5cd086d
MS
1333 /*
1334 * Rename the reflog file.
1335 */
1a481123 1336 if (git_buf_join_n(&aux_path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0)
97769280
RB
1337 goto cleanup;
1338
1a481123
VM
1339 if (git_path_exists(aux_path.ptr) == true) {
1340 if (git_reflog_rename(ref, new_name) < 0)
45d387ac 1341 goto cleanup;
1a481123
VM
1342 } else {
1343 giterr_clear();
45d387ac 1344 }
a5cd086d 1345
d4a0b124
VM
1346 /*
1347 * Change the name of the reference given by the user.
1348 */
1349 git__free(ref->name);
1350 ref->name = git__strdup(new_name);
1351
1352 /* The reference is no longer packed */
1353 ref->flags &= ~GIT_REF_PACKED;
7376ad99 1354
45d387ac
VM
1355 git_reference_free(head);
1356 git_buf_free(&aux_path);
1357 return 0;
1358
0ffcf78a 1359cleanup:
d4a0b124 1360 git_reference_free(head);
97769280 1361 git_buf_free(&aux_path);
45d387ac 1362 return -1;
7376ad99 1363
0ffcf78a
MS
1364rollback:
1365 /*
45d387ac 1366 * Try to create the old reference again, ignore failures
0ffcf78a 1367 */
d4a0b124 1368 if (ref->flags & GIT_REF_SYMBOLIC)
45d387ac 1369 git_reference_create_symbolic(
1a481123 1370 NULL, ref->owner, ref->name, ref->target.symbolic, 0);
0ffcf78a 1371 else
45d387ac 1372 git_reference_create_oid(
1a481123 1373 NULL, ref->owner, ref->name, &ref->target.oid, 0);
0ffcf78a 1374
64093ce5
MS
1375 /* The reference is no longer packed */
1376 ref->flags &= ~GIT_REF_PACKED;
1377
97769280 1378 git_buf_free(&aux_path);
45d387ac 1379 return -1;
0ffcf78a 1380}
7376ad99 1381
1a481123 1382int git_reference_resolve(git_reference **ref_out, git_reference *ref)
87d3acf4 1383{
45d387ac 1384 int result, i = 0;
d4a0b124 1385 git_repository *repo;
87d3acf4
VM
1386
1387 assert(ref);
1388
d4a0b124
VM
1389 *ref_out = NULL;
1390 repo = ref->owner;
87d3acf4 1391
d4a0b124
VM
1392 /* If the reference is already resolved, we need to return a
1393 * copy. Instead of duplicating `ref`, we look it up again to
1394 * ensure the copy is out to date */
1395 if (ref->flags & GIT_REF_OID)
1a481123 1396 return git_reference_lookup(ref_out, ref->owner, ref->name);
7341bf87 1397
d4a0b124
VM
1398 /* Otherwise, keep iterating until the reference is resolved */
1399 for (i = 0; i < MAX_NESTING_LEVEL; ++i) {
1400 git_reference *new_ref;
932d1baf 1401
1a481123 1402 result = git_reference_lookup(&new_ref, repo, ref->target.symbolic);
45d387ac
VM
1403 if (result < 0)
1404 return result;
87d3acf4 1405
d4a0b124
VM
1406 /* Free intermediate references, except for the original one
1407 * we've received */
1408 if (i > 0)
1409 git_reference_free(ref);
87d3acf4 1410
d4a0b124 1411 ref = new_ref;
68a146c1 1412
d4a0b124
VM
1413 /* When the reference we've just looked up is an OID, we've
1414 * successfully resolved the symbolic ref */
1415 if (ref->flags & GIT_REF_OID) {
1416 *ref_out = ref;
1a481123 1417 return 0;
d4a0b124 1418 }
87d3acf4
VM
1419 }
1420
1a481123 1421 giterr_set(GITERR_REFERENCE,
45d387ac
VM
1422 "Symbolic reference too nested (%d levels deep)", MAX_NESTING_LEVEL);
1423
1424 return -1;
a46ec457
MS
1425}
1426
1a481123 1427int git_reference_packall(git_repository *repo)
87d3acf4 1428{
1a481123
VM
1429 if (packed_load(repo) < 0 || /* load the existing packfile */
1430 packed_loadloose(repo) < 0 || /* add all the loose refs */
1431 packed_write(repo) < 0) /* write back to disk */
45d387ac 1432 return -1;
87d3acf4 1433
45d387ac 1434 return 0;
87d3acf4
VM
1435}
1436
d4a0b124
VM
1437int git_reference_foreach(
1438 git_repository *repo,
1439 unsigned int list_flags,
1440 int (*callback)(const char *, void *),
1a481123 1441 void *payload)
00571828 1442{
45d387ac 1443 int result;
00571828 1444 struct dirent_list_data data;
97769280 1445 git_buf refs_path = GIT_BUF_INIT;
00571828 1446
7ad96e51 1447 /* list all the packed references first */
00571828
VM
1448 if (list_flags & GIT_REF_PACKED) {
1449 const char *ref_name;
00571828 1450
1a481123 1451 if (packed_load(repo) < 0)
45d387ac 1452 return -1;
00571828 1453
1a481123 1454 GIT_HASHTABLE_FOREACH_KEY(repo->references.packfile, ref_name,
45d387ac
VM
1455 if (callback(ref_name, payload) < 0)
1456 return 0;
00571828
VM
1457 );
1458 }
1459
7ad96e51
VM
1460 /* now list the loose references, trying not to
1461 * duplicate the ref names already in the packed-refs file */
09e8de0f
VM
1462
1463 data.repo_path_len = strlen(repo->path_repository);
1464 data.list_flags = list_flags;
1465 data.repo = repo;
1466 data.callback = callback;
1467 data.callback_payload = payload;
1468
1a481123 1469 if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0)
45d387ac 1470 return -1;
97769280 1471
1a481123 1472 result = git_path_direach(&refs_path, _dirent_loose_listall, &data);
97769280
RB
1473 git_buf_free(&refs_path);
1474
45d387ac 1475 return result;
09e8de0f
VM
1476}
1477
d568d585 1478static int cb__reflist_add(const char *ref, void *data)
09e8de0f
VM
1479{
1480 return git_vector_insert((git_vector *)data, git__strdup(ref));
1481}
1482
d4a0b124
VM
1483int git_reference_listall(
1484 git_strarray *array,
1485 git_repository *repo,
1a481123 1486 unsigned int list_flags)
09e8de0f 1487{
09e8de0f
VM
1488 git_vector ref_list;
1489
1490 assert(array && repo);
1491
1492 array->strings = NULL;
1493 array->count = 0;
1494
0d0fa7c3 1495 if (git_vector_init(&ref_list, 8, NULL) < 0)
45d387ac 1496 return -1;
7ad96e51 1497
45d387ac 1498 if (git_reference_foreach(
1a481123 1499 repo, list_flags, &cb__reflist_add, (void *)&ref_list) < 0) {
09e8de0f 1500 git_vector_free(&ref_list);
45d387ac 1501 return -1;
7ad96e51
VM
1502 }
1503
09e8de0f
VM
1504 array->strings = (char **)ref_list.contents;
1505 array->count = ref_list.length;
1a481123 1506 return 0;
00571828 1507}
87d3acf4 1508
1a481123 1509int git_reference_reload(git_reference *ref)
2f8a8ab2 1510{
1a481123 1511 return reference_lookup(ref);
2f8a8ab2 1512}
9282e921 1513
2f8a8ab2
VM
1514void git_repository__refcache_free(git_refcache *refs)
1515{
2f8a8ab2
VM
1516 assert(refs);
1517
87d3acf4 1518 if (refs->packfile) {
d4a0b124
VM
1519 struct packref *reference;
1520
854eccbb
RB
1521 GIT_HASHTABLE_FOREACH_VALUE(
1522 refs->packfile, reference, git__free(reference));
87d3acf4
VM
1523
1524 git_hashtable_free(refs->packfile);
1525 }
9282e921 1526}
2f8a8ab2 1527
d4a0b124 1528static int is_valid_ref_char(char ch)
aa2120e9 1529{
50a8fd03 1530 if ((unsigned) ch <= ' ')
d4a0b124 1531 return 0;
aa2120e9 1532
1533 switch (ch) {
1534 case '~':
1535 case '^':
1536 case ':':
1537 case '\\':
1538 case '?':
1539 case '[':
e1be1028 1540 case '*':
d4a0b124 1541 return 0;
aa2120e9 1542 default:
d4a0b124 1543 return 1;
aa2120e9 1544 }
1545}
1546
d4a0b124
VM
1547static int normalize_name(
1548 char *buffer_out,
1549 size_t out_size,
1550 const char *name,
1551 int is_oid_ref)
aa2120e9 1552{
aa2120e9 1553 const char *name_end, *buffer_out_start;
b75bec94 1554 const char *current;
aa2120e9 1555 int contains_a_slash = 0;
1556
1557 assert(name && buffer_out);
1558
1559 buffer_out_start = buffer_out;
b75bec94 1560 current = name;
aa2120e9 1561 name_end = name + strlen(name);
1562
3101a3e5
VM
1563 /* Terminating null byte */
1564 out_size--;
1565
aa2120e9 1566 /* A refname can not be empty */
1567 if (name_end == name)
1a481123 1568 goto invalid_name;
aa2120e9 1569
1570 /* A refname can not end with a dot or a slash */
1571 if (*(name_end - 1) == '.' || *(name_end - 1) == '/')
1a481123 1572 goto invalid_name;
aa2120e9 1573
3101a3e5 1574 while (current < name_end && out_size) {
d4a0b124 1575 if (!is_valid_ref_char(*current))
1a481123 1576 goto invalid_name;
aa2120e9 1577
1578 if (buffer_out > buffer_out_start) {
1579 char prev = *(buffer_out - 1);
1580
1581 /* A refname can not start with a dot nor contain a double dot */
1582 if (*current == '.' && ((prev == '.') || (prev == '/')))
1a481123 1583 goto invalid_name;
aa2120e9 1584
1585 /* '@{' is forbidden within a refname */
1586 if (*current == '{' && prev == '@')
1a481123 1587 goto invalid_name;
aa2120e9 1588
1589 /* Prevent multiple slashes from being added to the output */
1590 if (*current == '/' && prev == '/') {
1591 current++;
1592 continue;
1593 }
1594 }
1595
e1be1028 1596 if (*current == '/')
aa2120e9 1597 contains_a_slash = 1;
aa2120e9 1598
1599 *buffer_out++ = *current++;
3101a3e5 1600 out_size--;
aa2120e9 1601 }
1602
3101a3e5 1603 if (!out_size)
1a481123 1604 goto invalid_name;
3101a3e5 1605
83c95128 1606 /* Object id refname have to contain at least one slash, except
df30eac1
JP
1607 * for HEAD in a detached state or MERGE_HEAD if we're in the
1608 * middle of a merge */
d4a0b124
VM
1609 if (is_oid_ref &&
1610 !contains_a_slash &&
1611 strcmp(name, GIT_HEAD_FILE) != 0 &&
1612 strcmp(name, GIT_MERGE_HEAD_FILE) != 0 &&
1613 strcmp(name, GIT_FETCH_HEAD_FILE) != 0)
1a481123 1614 goto invalid_name;
aa2120e9 1615
1616 /* A refname can not end with ".lock" */
1617 if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION))
1a481123 1618 goto invalid_name;
aa2120e9 1619
1620 *buffer_out = '\0';
1621
83c95128
CMN
1622 /*
1623 * For object id references, name has to start with refs/. Again,
1624 * we need to allow HEAD to be in a detached state.
1625 */
0d5d5190
VM
1626 if (is_oid_ref && !(git__prefixcmp(buffer_out_start, GIT_REFS_DIR) ||
1627 strcmp(buffer_out_start, GIT_HEAD_FILE)))
1a481123 1628 goto invalid_name;
aa2120e9 1629
1a481123
VM
1630 return 0;
1631
1632invalid_name:
1633 giterr_set(GITERR_REFERENCE, "The given reference name is not valid");
1634 return -1;
aa2120e9 1635}
2f8a8ab2 1636
d4a0b124
VM
1637int git_reference__normalize_name(
1638 char *buffer_out,
1639 size_t out_size,
1640 const char *name)
86194b24 1641{
3101a3e5 1642 return normalize_name(buffer_out, out_size, name, 0);
86194b24
VM
1643}
1644
d4a0b124
VM
1645int git_reference__normalize_name_oid(
1646 char *buffer_out,
1647 size_t out_size,
1648 const char *name)
86194b24 1649{
3101a3e5 1650 return normalize_name(buffer_out, out_size, name, 1);
50a8fd03 1651}