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