]> git.proxmox.com Git - libgit2.git/blame - src/refdb_fs.c
Refcounting
[libgit2.git] / src / refdb_fs.c
CommitLineData
d00d5464
ET
1/*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
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.
6 */
7
8#include "refs.h"
9#include "hash.h"
10#include "repository.h"
11#include "fileops.h"
12#include "pack.h"
13#include "reflog.h"
d00d5464
ET
14#include "refdb.h"
15#include "refdb_fs.h"
4def7035 16#include "iterator.h"
d00d5464
ET
17
18#include <git2/tag.h>
19#include <git2/object.h>
20#include <git2/refdb.h>
4dcd8780 21#include <git2/sys/refdb_backend.h>
21ca0451 22#include <git2/sys/refs.h>
d00d5464
ET
23
24GIT__USE_STRMAP;
25
26#define DEFAULT_NESTING_LEVEL 5
27#define MAX_NESTING_LEVEL 10
28
29enum {
f69db390
VM
30 PACKREF_HAS_PEEL = 1,
31 PACKREF_WAS_LOOSE = 2,
2638a03a
VM
32 PACKREF_CANNOT_PEEL = 4,
33 PACKREF_SHADOWED = 8,
f69db390
VM
34};
35
36enum {
37 PEELING_NONE = 0,
38 PEELING_STANDARD,
39 PEELING_FULL
d00d5464
ET
40};
41
42struct packref {
43 git_oid oid;
44 git_oid peel;
45 char flags;
46 char name[GIT_FLEX_ARRAY];
47};
48
49typedef struct refdb_fs_backend {
50 git_refdb_backend parent;
51
52 git_repository *repo;
bade5194 53 char *path;
d00d5464
ET
54
55 git_refcache refcache;
f69db390 56 int peeling_mode;
d00d5464
ET
57} refdb_fs_backend;
58
59static int reference_read(
60 git_buf *file_content,
61 time_t *mtime,
62 const char *repo_path,
63 const char *ref_name,
64 int *updated)
65{
66 git_buf path = GIT_BUF_INIT;
67 int result;
68
69 assert(file_content && repo_path && ref_name);
70
71 /* Determine the full path of the file */
72 if (git_buf_joinpath(&path, repo_path, ref_name) < 0)
73 return -1;
4dcd8780 74
d00d5464
ET
75 result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, NULL, updated);
76 git_buf_free(&path);
77
78 return result;
79}
80
81static int packed_parse_oid(
82 struct packref **ref_out,
83 const char **buffer_out,
84 const char *buffer_end)
85{
86 struct packref *ref = NULL;
87
88 const char *buffer = *buffer_out;
89 const char *refname_begin, *refname_end;
90
91 size_t refname_len;
92 git_oid id;
93
94 refname_begin = (buffer + GIT_OID_HEXSZ + 1);
95 if (refname_begin >= buffer_end || refname_begin[-1] != ' ')
96 goto corrupt;
97
98 /* Is this a valid object id? */
99 if (git_oid_fromstr(&id, buffer) < 0)
100 goto corrupt;
101
102 refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
103 if (refname_end == NULL)
104 refname_end = buffer_end;
105
106 if (refname_end[-1] == '\r')
107 refname_end--;
108
109 refname_len = refname_end - refname_begin;
110
617bb175 111 ref = git__calloc(1, sizeof(struct packref) + refname_len + 1);
d00d5464
ET
112 GITERR_CHECK_ALLOC(ref);
113
114 memcpy(ref->name, refname_begin, refname_len);
115 ref->name[refname_len] = 0;
116
117 git_oid_cpy(&ref->oid, &id);
118
d00d5464
ET
119 *ref_out = ref;
120 *buffer_out = refname_end + 1;
d00d5464
ET
121 return 0;
122
123corrupt:
124 git__free(ref);
125 giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
126 return -1;
127}
128
129static int packed_parse_peel(
130 struct packref *tag_ref,
131 const char **buffer_out,
132 const char *buffer_end)
133{
134 const char *buffer = *buffer_out + 1;
135
136 assert(buffer[-1] == '^');
137
138 /* Ensure it's not the first entry of the file */
139 if (tag_ref == NULL)
140 goto corrupt;
141
d00d5464
ET
142 if (buffer + GIT_OID_HEXSZ > buffer_end)
143 goto corrupt;
144
145 /* Is this a valid object id? */
146 if (git_oid_fromstr(&tag_ref->peel, buffer) < 0)
147 goto corrupt;
148
149 buffer = buffer + GIT_OID_HEXSZ;
150 if (*buffer == '\r')
151 buffer++;
152
153 if (buffer != buffer_end) {
154 if (*buffer == '\n')
155 buffer++;
156 else
157 goto corrupt;
158 }
159
f69db390 160 tag_ref->flags |= PACKREF_HAS_PEEL;
d00d5464
ET
161 *buffer_out = buffer;
162 return 0;
163
164corrupt:
165 giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
166 return -1;
167}
168
169static int packed_load(refdb_fs_backend *backend)
170{
171 int result, updated;
172 git_buf packfile = GIT_BUF_INIT;
173 const char *buffer_start, *buffer_end;
174 git_refcache *ref_cache = &backend->refcache;
175
176 /* First we make sure we have allocated the hash table */
177 if (ref_cache->packfile == NULL) {
178 ref_cache->packfile = git_strmap_alloc();
179 GITERR_CHECK_ALLOC(ref_cache->packfile);
180 }
4dcd8780 181
69a3c766
CMN
182 if (backend->path == NULL)
183 return 0;
184
d00d5464
ET
185 result = reference_read(&packfile, &ref_cache->packfile_time,
186 backend->path, GIT_PACKEDREFS_FILE, &updated);
187
188 /*
189 * If we couldn't find the file, we need to clear the table and
190 * return. On any other error, we return that error. If everything
191 * went fine and the file wasn't updated, then there's nothing new
192 * for us here, so just return. Anything else means we need to
193 * refresh the packed refs.
194 */
195 if (result == GIT_ENOTFOUND) {
196 git_strmap_clear(ref_cache->packfile);
197 return 0;
198 }
199
200 if (result < 0)
201 return -1;
4dcd8780 202
d00d5464
ET
203 if (!updated)
204 return 0;
205
206 /*
207 * At this point, we want to refresh the packed refs. We already
208 * have the contents in our buffer.
209 */
210 git_strmap_clear(ref_cache->packfile);
211
212 buffer_start = (const char *)packfile.ptr;
213 buffer_end = (const char *)(buffer_start) + packfile.size;
214
f69db390
VM
215 backend->peeling_mode = PEELING_NONE;
216
217 if (buffer_start[0] == '#') {
1fed6b07 218 static const char *traits_header = "# pack-refs with: ";
f69db390
VM
219
220 if (git__prefixcmp(buffer_start, traits_header) == 0) {
822645f6
VM
221 char *traits = (char *)buffer_start + strlen(traits_header);
222 char *traits_end = strchr(traits, '\n');
223
224 if (traits_end == NULL)
225 goto parse_failed;
226
227 *traits_end = '\0';
f69db390 228
1022db2b 229 if (strstr(traits, " fully-peeled ") != NULL) {
f69db390 230 backend->peeling_mode = PEELING_FULL;
1022db2b 231 } else if (strstr(traits, " peeled ") != NULL) {
f69db390
VM
232 backend->peeling_mode = PEELING_STANDARD;
233 }
234
235 buffer_start = traits_end + 1;
236 }
237 }
238
d00d5464
ET
239 while (buffer_start < buffer_end && buffer_start[0] == '#') {
240 buffer_start = strchr(buffer_start, '\n');
241 if (buffer_start == NULL)
242 goto parse_failed;
243
244 buffer_start++;
245 }
246
247 while (buffer_start < buffer_end) {
248 int err;
249 struct packref *ref = NULL;
250
251 if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0)
252 goto parse_failed;
253
254 if (buffer_start[0] == '^') {
255 if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0)
256 goto parse_failed;
a591ed3e 257 } else if (backend->peeling_mode == PEELING_FULL ||
0cb16fe9
L
258 (backend->peeling_mode == PEELING_STANDARD &&
259 git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0)) {
f69db390 260 ref->flags |= PACKREF_CANNOT_PEEL;
d00d5464
ET
261 }
262
263 git_strmap_insert(ref_cache->packfile, ref->name, ref, err);
264 if (err < 0)
265 goto parse_failed;
266 }
267
268 git_buf_free(&packfile);
269 return 0;
270
271parse_failed:
272 git_strmap_free(ref_cache->packfile);
273 ref_cache->packfile = NULL;
274 git_buf_free(&packfile);
275 return -1;
276}
277
a5de9044 278static int loose_parse_oid(git_oid *oid, const char *filename, git_buf *file_content)
d00d5464
ET
279{
280 size_t len;
281 const char *str;
282
283 len = git_buf_len(file_content);
284 if (len < GIT_OID_HEXSZ)
285 goto corrupted;
286
287 /* str is guranteed to be zero-terminated */
288 str = git_buf_cstr(file_content);
289
290 /* we need to get 40 OID characters from the file */
291 if (git_oid_fromstr(oid, git_buf_cstr(file_content)) < 0)
292 goto corrupted;
293
294 /* If the file is longer than 40 chars, the 41st must be a space */
295 str += GIT_OID_HEXSZ;
296 if (*str == '\0' || git__isspace(*str))
297 return 0;
298
299corrupted:
a5de9044 300 giterr_set(GITERR_REFERENCE, "Corrupted loose reference file: %s", filename);
d00d5464
ET
301 return -1;
302}
303
304static int loose_lookup_to_packfile(
305 struct packref **ref_out,
306 refdb_fs_backend *backend,
307 const char *name)
308{
309 git_buf ref_file = GIT_BUF_INIT;
310 struct packref *ref = NULL;
311 size_t name_len;
312
313 *ref_out = NULL;
314
315 if (reference_read(&ref_file, NULL, backend->path, name, NULL) < 0)
316 return -1;
317
318 git_buf_rtrim(&ref_file);
319
320 name_len = strlen(name);
617bb175 321 ref = git__calloc(1, sizeof(struct packref) + name_len + 1);
d00d5464
ET
322 GITERR_CHECK_ALLOC(ref);
323
324 memcpy(ref->name, name, name_len);
325 ref->name[name_len] = 0;
326
a5de9044 327 if (loose_parse_oid(&ref->oid, name, &ref_file) < 0) {
d00d5464
ET
328 git_buf_free(&ref_file);
329 git__free(ref);
330 return -1;
331 }
332
f69db390 333 ref->flags = PACKREF_WAS_LOOSE;
d00d5464
ET
334
335 *ref_out = ref;
336 git_buf_free(&ref_file);
337 return 0;
338}
339
340
341static int _dirent_loose_load(void *data, git_buf *full_path)
342{
343 refdb_fs_backend *backend = (refdb_fs_backend *)data;
344 void *old_ref = NULL;
345 struct packref *ref;
346 const char *file_path;
347 int err;
348
349 if (git_path_isdir(full_path->ptr) == true)
350 return git_path_direach(full_path, _dirent_loose_load, backend);
351
352 file_path = full_path->ptr + strlen(backend->path);
353
354 if (loose_lookup_to_packfile(&ref, backend, file_path) < 0)
355 return -1;
356
357 git_strmap_insert2(
358 backend->refcache.packfile, ref->name, ref, old_ref, err);
359 if (err < 0) {
360 git__free(ref);
361 return -1;
362 }
363
364 git__free(old_ref);
365 return 0;
366}
367
368/*
369 * Load all the loose references from the repository
370 * into the in-memory Packfile, and build a vector with
371 * all the references so it can be written back to
372 * disk.
373 */
374static int packed_loadloose(refdb_fs_backend *backend)
375{
376 git_buf refs_path = GIT_BUF_INIT;
377 int result;
378
379 /* the packfile must have been previously loaded! */
380 assert(backend->refcache.packfile);
381
382 if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)
383 return -1;
384
385 /*
386 * Load all the loose files from disk into the Packfile table.
387 * This will overwrite any old packed entries with their
388 * updated loose versions
389 */
390 result = git_path_direach(&refs_path, _dirent_loose_load, backend);
391 git_buf_free(&refs_path);
392
393 return result;
394}
395
396static int refdb_fs_backend__exists(
397 int *exists,
398 git_refdb_backend *_backend,
399 const char *ref_name)
400{
401 refdb_fs_backend *backend;
402 git_buf ref_path = GIT_BUF_INIT;
403
404 assert(_backend);
405 backend = (refdb_fs_backend *)_backend;
406
407 if (packed_load(backend) < 0)
408 return -1;
409
410 if (git_buf_joinpath(&ref_path, backend->path, ref_name) < 0)
411 return -1;
412
413 if (git_path_isfile(ref_path.ptr) == true ||
414 git_strmap_exists(backend->refcache.packfile, ref_path.ptr))
415 *exists = 1;
416 else
417 *exists = 0;
418
419 git_buf_free(&ref_path);
420 return 0;
421}
422
423static const char *loose_parse_symbolic(git_buf *file_content)
424{
425 const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);
426 const char *refname_start;
427
428 refname_start = (const char *)file_content->ptr;
429
430 if (git_buf_len(file_content) < header_len + 1) {
431 giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
432 return NULL;
433 }
434
435 /*
436 * Assume we have already checked for the header
437 * before calling this function
438 */
439 refname_start += header_len;
440
441 return refname_start;
442}
443
444static int loose_lookup(
445 git_reference **out,
446 refdb_fs_backend *backend,
447 const char *ref_name)
448{
449 const char *target;
450 git_oid oid;
451 git_buf ref_file = GIT_BUF_INIT;
452 int error = 0;
453
454 error = reference_read(&ref_file, NULL, backend->path, ref_name, NULL);
455
456 if (error < 0)
457 goto done;
458
459 if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) {
460 git_buf_rtrim(&ref_file);
461
462 if ((target = loose_parse_symbolic(&ref_file)) == NULL) {
463 error = -1;
464 goto done;
465 }
466
4e4eab52 467 *out = git_reference__alloc_symbolic(ref_name, target);
d00d5464 468 } else {
a5de9044 469 if ((error = loose_parse_oid(&oid, ref_name, &ref_file)) < 0)
d00d5464 470 goto done;
4dcd8780 471
4e4eab52 472 *out = git_reference__alloc(ref_name, &oid, NULL);
d00d5464
ET
473 }
474
475 if (*out == NULL)
476 error = -1;
477
478done:
479 git_buf_free(&ref_file);
480 return error;
481}
482
483static int packed_map_entry(
484 struct packref **entry,
485 khiter_t *pos,
486 refdb_fs_backend *backend,
487 const char *ref_name)
488{
489 git_strmap *packfile_refs;
490
491 if (packed_load(backend) < 0)
492 return -1;
4dcd8780 493
d00d5464
ET
494 /* Look up on the packfile */
495 packfile_refs = backend->refcache.packfile;
496
497 *pos = git_strmap_lookup_index(packfile_refs, ref_name);
4dcd8780 498
d00d5464
ET
499 if (!git_strmap_valid_index(packfile_refs, *pos)) {
500 giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref_name);
501 return GIT_ENOTFOUND;
502 }
503
504 *entry = git_strmap_value_at(packfile_refs, *pos);
4dcd8780 505
d00d5464
ET
506 return 0;
507}
508
509static int packed_lookup(
510 git_reference **out,
511 refdb_fs_backend *backend,
512 const char *ref_name)
513{
514 struct packref *entry;
515 khiter_t pos;
516 int error = 0;
4dcd8780 517
d00d5464
ET
518 if ((error = packed_map_entry(&entry, &pos, backend, ref_name)) < 0)
519 return error;
520
4e4eab52 521 if ((*out = git_reference__alloc(ref_name,
3be933b1 522 &entry->oid, &entry->peel)) == NULL)
d00d5464 523 return -1;
4dcd8780 524
d00d5464
ET
525 return 0;
526}
527
528static int refdb_fs_backend__lookup(
529 git_reference **out,
530 git_refdb_backend *_backend,
531 const char *ref_name)
532{
533 refdb_fs_backend *backend;
534 int result;
535
536 assert(_backend);
537
538 backend = (refdb_fs_backend *)_backend;
539
540 if ((result = loose_lookup(out, backend, ref_name)) == 0)
541 return 0;
542
543 /* only try to lookup this reference on the packfile if it
544 * wasn't found on the loose refs; not if there was a critical error */
545 if (result == GIT_ENOTFOUND) {
546 giterr_clear();
547 result = packed_lookup(out, backend, ref_name);
548 }
549
550 return result;
551}
552
4def7035
CMN
553typedef struct {
554 git_reference_iterator parent;
2638a03a 555
ec24e542 556 char *glob;
2638a03a
VM
557 git_vector loose;
558 unsigned int loose_pos;
559 khiter_t packed_pos;
4def7035
CMN
560} refdb_fs_iter;
561
4def7035 562static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter)
d00d5464 563{
4def7035 564 refdb_fs_iter *iter = (refdb_fs_iter *) _iter;
2638a03a
VM
565 char *loose_path;
566 size_t i;
d00d5464 567
2638a03a
VM
568 git_vector_foreach(&iter->loose, i, loose_path) {
569 free(loose_path);
570 }
571
572 git_vector_free(&iter->loose);
ec24e542
VM
573
574 git__free(iter->glob);
4def7035
CMN
575 git__free(iter);
576}
d00d5464 577
ec24e542 578static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter)
4def7035 579{
2638a03a 580 git_strmap *packfile = backend->refcache.packfile;
2638a03a
VM
581 git_buf path = GIT_BUF_INIT;
582 git_iterator *fsit;
583 const git_index_entry *entry = NULL;
d00d5464 584
2638a03a
VM
585 if (git_buf_printf(&path, "%s/refs", backend->path) < 0)
586 return -1;
d00d5464 587
2638a03a
VM
588 if (git_iterator_for_filesystem(&fsit, git_buf_cstr(&path), 0, NULL, NULL) < 0)
589 return -1;
4dcd8780 590
2638a03a
VM
591 git_vector_init(&iter->loose, 8, NULL);
592 git_buf_sets(&path, GIT_REFS_DIR);
d00d5464 593
2638a03a
VM
594 while (!git_iterator_current(&entry, fsit) && entry) {
595 const char *ref_name;
4def7035 596 khiter_t pos;
d00d5464 597
2638a03a
VM
598 git_buf_truncate(&path, strlen(GIT_REFS_DIR));
599 git_buf_puts(&path, entry->path);
600 ref_name = git_buf_cstr(&path);
4dcd8780 601
ec24e542
VM
602 if (git__suffixcmp(ref_name, ".lock") == 0 ||
603 (iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0)) {
2638a03a
VM
604 git_iterator_advance(NULL, fsit);
605 continue;
606 }
d00d5464 607
2638a03a
VM
608 pos = git_strmap_lookup_index(packfile, ref_name);
609 if (git_strmap_valid_index(packfile, pos)) {
610 struct packref *ref = git_strmap_value_at(packfile, pos);
611 ref->flags |= PACKREF_SHADOWED;
612 }
d00d5464 613
2638a03a
VM
614 git_vector_insert(&iter->loose, git__strdup(ref_name));
615 git_iterator_advance(NULL, fsit);
616 }
d00d5464 617
2638a03a
VM
618 git_iterator_free(fsit);
619 git_buf_free(&path);
4def7035
CMN
620
621 return 0;
622}
623
ec24e542
VM
624static int refdb_fs_backend__iterator_next(
625 git_reference **out, git_reference_iterator *_iter)
4def7035 626{
2638a03a 627 refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
ec24e542 628 refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
2638a03a 629 git_strmap *packfile = backend->refcache.packfile;
4def7035 630
56960b83 631 while (iter->loose_pos < iter->loose.length) {
2638a03a 632 const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
56960b83
VM
633
634 if (loose_lookup(out, backend, path) == 0)
635 return 0;
636
637 giterr_clear();
2638a03a 638 }
d00d5464 639
ec24e542 640 while (iter->packed_pos < kh_end(packfile)) {
2638a03a 641 struct packref *ref = NULL;
d00d5464 642
ec24e542 643 while (!kh_exist(packfile, iter->packed_pos)) {
2638a03a 644 iter->packed_pos++;
ec24e542
VM
645 if (iter->packed_pos == kh_end(packfile))
646 return GIT_ITEROVER;
647 }
648
649 ref = kh_val(packfile, iter->packed_pos);
650 iter->packed_pos++;
651
652 if (ref->flags & PACKREF_SHADOWED)
653 continue;
654
655 if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0)
656 continue;
99d32707 657
56960b83
VM
658 *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel);
659 if (*out == NULL)
660 return -1;
661
2638a03a 662 return 0;
4def7035
CMN
663 }
664
2638a03a 665 return GIT_ITEROVER;
d00d5464
ET
666}
667
ec24e542
VM
668static int refdb_fs_backend__iterator_next_name(
669 const char **out, git_reference_iterator *_iter)
670{
671 refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
672 refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
673 git_strmap *packfile = backend->refcache.packfile;
674
675 while (iter->loose_pos < iter->loose.length) {
676 const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
677
678 if (git_strmap_exists(packfile, path))
679 continue;
680
681 *out = path;
682 return 0;
683 }
684
685 while (iter->packed_pos < kh_end(packfile)) {
686 while (!kh_exist(packfile, iter->packed_pos)) {
687 iter->packed_pos++;
688 if (iter->packed_pos == kh_end(packfile))
689 return GIT_ITEROVER;
690 }
691
692 *out = kh_key(packfile, iter->packed_pos);
693 iter->packed_pos++;
694
695 if (iter->glob && p_fnmatch(iter->glob, *out, 0) != 0)
696 continue;
697
698 return 0;
699 }
700
701 return GIT_ITEROVER;
702}
703
704static int refdb_fs_backend__iterator(
705 git_reference_iterator **out, git_refdb_backend *_backend, const char *glob)
706{
707 refdb_fs_iter *iter;
708 refdb_fs_backend *backend;
709
710 assert(_backend);
711 backend = (refdb_fs_backend *)_backend;
712
713 if (packed_load(backend) < 0)
714 return -1;
715
716 iter = git__calloc(1, sizeof(refdb_fs_iter));
717 GITERR_CHECK_ALLOC(iter);
718
719 if (glob != NULL)
720 iter->glob = git__strdup(glob);
721
722 iter->parent.next = refdb_fs_backend__iterator_next;
723 iter->parent.next_name = refdb_fs_backend__iterator_next_name;
724 iter->parent.free = refdb_fs_backend__iterator_free;
725
726 if (iter_load_loose_paths(backend, iter) < 0) {
727 refdb_fs_backend__iterator_free((git_reference_iterator *)iter);
728 return -1;
729 }
730
731 *out = (git_reference_iterator *)iter;
732 return 0;
733}
734
4e6e2ff2
VM
735static bool ref_is_available(
736 const char *old_ref, const char *new_ref, const char *this_ref)
737{
738 if (old_ref == NULL || strcmp(old_ref, this_ref)) {
739 size_t reflen = strlen(this_ref);
740 size_t newlen = strlen(new_ref);
741 size_t cmplen = reflen < newlen ? reflen : newlen;
742 const char *lead = reflen < newlen ? new_ref : this_ref;
743
744 if (!strncmp(new_ref, this_ref, cmplen) && lead[cmplen] == '/') {
745 return false;
746 }
747 }
748
749 return true;
750}
751
752static int reference_path_available(
753 refdb_fs_backend *backend,
754 const char *new_ref,
755 const char* old_ref,
756 int force)
757{
758 struct packref *this_ref;
759
760 if (packed_load(backend) < 0)
761 return -1;
762
763 if (!force) {
764 int exists;
765
766 if (refdb_fs_backend__exists(&exists, (git_refdb_backend *)backend, new_ref) < 0)
767 return -1;
768
769 if (exists) {
770 giterr_set(GITERR_REFERENCE,
771 "Failed to write reference '%s': a reference with "
772 " that name already exists.", new_ref);
773 return GIT_EEXISTS;
774 }
775 }
776
777 git_strmap_foreach_value(backend->refcache.packfile, this_ref, {
778 if (!ref_is_available(old_ref, new_ref, this_ref->name)) {
779 giterr_set(GITERR_REFERENCE,
780 "The path to reference '%s' collides with an existing one", new_ref);
781 return -1;
782 }
783 });
784
785 return 0;
786}
ec24e542 787
d00d5464
ET
788static int loose_write(refdb_fs_backend *backend, const git_reference *ref)
789{
790 git_filebuf file = GIT_FILEBUF_INIT;
791 git_buf ref_path = GIT_BUF_INIT;
792
793 /* Remove a possibly existing empty directory hierarchy
794 * which name would collide with the reference name
795 */
4e6e2ff2 796 if (git_futils_rmdir_r(ref->name, backend->path, GIT_RMDIR_SKIP_NONEMPTY) < 0)
d00d5464
ET
797 return -1;
798
799 if (git_buf_joinpath(&ref_path, backend->path, ref->name) < 0)
800 return -1;
801
802 if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {
803 git_buf_free(&ref_path);
804 return -1;
805 }
806
807 git_buf_free(&ref_path);
808
809 if (ref->type == GIT_REF_OID) {
810 char oid[GIT_OID_HEXSZ + 1];
811
fedd0f9e 812 git_oid_fmt(oid, &ref->target.oid);
d00d5464
ET
813 oid[GIT_OID_HEXSZ] = '\0';
814
815 git_filebuf_printf(&file, "%s\n", oid);
816
817 } else if (ref->type == GIT_REF_SYMBOLIC) {
818 git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic);
819 } else {
820 assert(0); /* don't let this happen */
821 }
822
823 return git_filebuf_commit(&file, GIT_REFS_FILE_MODE);
824}
825
826static int packed_sort(const void *a, const void *b)
827{
828 const struct packref *ref_a = (const struct packref *)a;
829 const struct packref *ref_b = (const struct packref *)b;
830
831 return strcmp(ref_a->name, ref_b->name);
832}
833
834/*
835 * Find out what object this reference resolves to.
836 *
837 * For references that point to a 'big' tag (e.g. an
838 * actual tag object on the repository), we need to
839 * cache on the packfile the OID of the object to
840 * which that 'big tag' is pointing to.
841 */
842static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref)
843{
844 git_object *object;
845
f69db390 846 if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL)
d00d5464
ET
847 return 0;
848
d00d5464
ET
849 /*
850 * Find the tagged object in the repository
851 */
852 if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJ_ANY) < 0)
853 return -1;
854
855 /*
856 * If the tagged object is a Tag object, we need to resolve it;
857 * if the ref is actually a 'weak' ref, we don't need to resolve
858 * anything.
859 */
860 if (git_object_type(object) == GIT_OBJ_TAG) {
861 git_tag *tag = (git_tag *)object;
862
863 /*
864 * Find the object pointed at by this tag
865 */
866 git_oid_cpy(&ref->peel, git_tag_target_id(tag));
f69db390 867 ref->flags |= PACKREF_HAS_PEEL;
d00d5464
ET
868
869 /*
870 * The reference has now cached the resolved OID, and is
871 * marked at such. When written to the packfile, it'll be
872 * accompanied by this resolved oid
873 */
874 }
875
876 git_object_free(object);
877 return 0;
878}
879
880/*
881 * Write a single reference into a packfile
882 */
883static int packed_write_ref(struct packref *ref, git_filebuf *file)
884{
885 char oid[GIT_OID_HEXSZ + 1];
886
887 git_oid_fmt(oid, &ref->oid);
888 oid[GIT_OID_HEXSZ] = 0;
889
890 /*
891 * For references that peel to an object in the repo, we must
892 * write the resulting peel on a separate line, e.g.
893 *
894 * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
895 * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
896 *
897 * This obviously only applies to tags.
898 * The required peels have already been loaded into `ref->peel_target`.
899 */
f69db390 900 if (ref->flags & PACKREF_HAS_PEEL) {
d00d5464
ET
901 char peel[GIT_OID_HEXSZ + 1];
902 git_oid_fmt(peel, &ref->peel);
903 peel[GIT_OID_HEXSZ] = 0;
904
905 if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0)
906 return -1;
907 } else {
908 if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0)
909 return -1;
910 }
911
912 return 0;
913}
914
915/*
916 * Remove all loose references
917 *
918 * Once we have successfully written a packfile,
919 * all the loose references that were packed must be
920 * removed from disk.
921 *
922 * This is a dangerous method; make sure the packfile
923 * is well-written, because we are destructing references
924 * here otherwise.
925 */
926static int packed_remove_loose(
927 refdb_fs_backend *backend,
928 git_vector *packing_list)
929{
10c06114 930 size_t i;
d00d5464
ET
931 git_buf full_path = GIT_BUF_INIT;
932 int failed = 0;
933
934 for (i = 0; i < packing_list->length; ++i) {
935 struct packref *ref = git_vector_get(packing_list, i);
936
f69db390 937 if ((ref->flags & PACKREF_WAS_LOOSE) == 0)
d00d5464
ET
938 continue;
939
940 if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0)
941 return -1; /* critical; do not try to recover on oom */
942
943 if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) {
944 if (failed)
945 continue;
946
947 giterr_set(GITERR_REFERENCE,
948 "Failed to remove loose reference '%s' after packing: %s",
949 full_path.ptr, strerror(errno));
950
951 failed = 1;
952 }
953
954 /*
955 * if we fail to remove a single file, this is *not* good,
956 * but we should keep going and remove as many as possible.
957 * After we've removed as many files as possible, we return
958 * the error code anyway.
959 */
960 }
961
962 git_buf_free(&full_path);
963 return failed ? -1 : 0;
964}
965
966/*
967 * Write all the contents in the in-memory packfile to disk.
968 */
969static int packed_write(refdb_fs_backend *backend)
970{
971 git_filebuf pack_file = GIT_FILEBUF_INIT;
10c06114 972 size_t i;
d00d5464
ET
973 git_buf pack_file_path = GIT_BUF_INIT;
974 git_vector packing_list;
975 unsigned int total_refs;
976
977 assert(backend && backend->refcache.packfile);
978
979 total_refs =
980 (unsigned int)git_strmap_num_entries(backend->refcache.packfile);
981
982 if (git_vector_init(&packing_list, total_refs, packed_sort) < 0)
983 return -1;
984
985 /* Load all the packfile into a vector */
986 {
987 struct packref *reference;
988
989 /* cannot fail: vector already has the right size */
990 git_strmap_foreach_value(backend->refcache.packfile, reference, {
991 git_vector_insert(&packing_list, reference);
992 });
993 }
994
995 /* sort the vector so the entries appear sorted on the packfile */
996 git_vector_sort(&packing_list);
997
998 /* Now we can open the file! */
999 if (git_buf_joinpath(&pack_file_path,
1000 backend->path, GIT_PACKEDREFS_FILE) < 0)
1001 goto cleanup_memory;
1002
1003 if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0)
1004 goto cleanup_packfile;
1005
1006 /* Packfiles have a header... apparently
1007 * This is in fact not required, but we might as well print it
1008 * just for kicks */
1009 if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0)
1010 goto cleanup_packfile;
1011
1012 for (i = 0; i < packing_list.length; ++i) {
1013 struct packref *ref = (struct packref *)git_vector_get(&packing_list, i);
1014
1015 if (packed_find_peel(backend, ref) < 0)
1016 goto cleanup_packfile;
1017
1018 if (packed_write_ref(ref, &pack_file) < 0)
1019 goto cleanup_packfile;
1020 }
1021
1022 /* if we've written all the references properly, we can commit
1023 * the packfile to make the changes effective */
1024 if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0)
1025 goto cleanup_memory;
1026
1027 /* when and only when the packfile has been properly written,
1028 * we can go ahead and remove the loose refs */
1029 if (packed_remove_loose(backend, &packing_list) < 0)
1030 goto cleanup_memory;
1031
1032 {
1033 struct stat st;
1034 if (p_stat(pack_file_path.ptr, &st) == 0)
1035 backend->refcache.packfile_time = st.st_mtime;
1036 }
1037
1038 git_vector_free(&packing_list);
1039 git_buf_free(&pack_file_path);
1040
1041 /* we're good now */
1042 return 0;
1043
1044cleanup_packfile:
1045 git_filebuf_cleanup(&pack_file);
1046
1047cleanup_memory:
1048 git_vector_free(&packing_list);
1049 git_buf_free(&pack_file_path);
1050
1051 return -1;
1052}
1053
1054static int refdb_fs_backend__write(
1055 git_refdb_backend *_backend,
4e6e2ff2
VM
1056 const git_reference *ref,
1057 int force)
d00d5464
ET
1058{
1059 refdb_fs_backend *backend;
4e6e2ff2 1060 int error;
d00d5464
ET
1061
1062 assert(_backend);
1063 backend = (refdb_fs_backend *)_backend;
1064
4e6e2ff2
VM
1065 error = reference_path_available(backend, ref->name, NULL, force);
1066 if (error < 0)
1067 return error;
1068
d00d5464
ET
1069 return loose_write(backend, ref);
1070}
1071
1072static int refdb_fs_backend__delete(
1073 git_refdb_backend *_backend,
4e6e2ff2 1074 const char *ref_name)
d00d5464
ET
1075{
1076 refdb_fs_backend *backend;
d00d5464
ET
1077 git_buf loose_path = GIT_BUF_INIT;
1078 struct packref *pack_ref;
1079 khiter_t pack_ref_pos;
4e6e2ff2 1080 int error = 0;
038c1654 1081 bool loose_deleted = 0;
d00d5464
ET
1082
1083 assert(_backend);
4e6e2ff2 1084 assert(ref_name);
d00d5464
ET
1085
1086 backend = (refdb_fs_backend *)_backend;
d00d5464
ET
1087
1088 /* If a loose reference exists, remove it from the filesystem */
4e6e2ff2 1089 if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0)
d00d5464
ET
1090 return -1;
1091
1092 if (git_path_isfile(loose_path.ptr)) {
1093 error = p_unlink(loose_path.ptr);
1094 loose_deleted = 1;
1095 }
4dcd8780 1096
d00d5464
ET
1097 git_buf_free(&loose_path);
1098
1099 if (error != 0)
1100 return error;
1101
1102 /* If a packed reference exists, remove it from the packfile and repack */
4e6e2ff2
VM
1103 error = packed_map_entry(&pack_ref, &pack_ref_pos, backend, ref_name);
1104
1105 if (error == GIT_ENOTFOUND)
1106 return loose_deleted ? 0 : GIT_ENOTFOUND;
d00d5464 1107
4e6e2ff2 1108 if (error == 0) {
d00d5464
ET
1109 git_strmap_delete_at(backend->refcache.packfile, pack_ref_pos);
1110 git__free(pack_ref);
d00d5464
ET
1111 error = packed_write(backend);
1112 }
4dcd8780 1113
d00d5464
ET
1114 return error;
1115}
1116
4e6e2ff2
VM
1117static int refdb_fs_backend__rename(
1118 git_reference **out,
1119 git_refdb_backend *_backend,
1120 const char *old_name,
1121 const char *new_name,
1122 int force)
1123{
1124 refdb_fs_backend *backend;
1125 git_reference *old, *new;
1126 int error;
1127
1128 assert(_backend);
1129 backend = (refdb_fs_backend *)_backend;
1130
1131 error = reference_path_available(backend, new_name, old_name, force);
1132 if (error < 0)
1133 return error;
1134
1135 error = refdb_fs_backend__lookup(&old, _backend, old_name);
1136 if (error < 0)
1137 return error;
1138
1139 error = refdb_fs_backend__delete(_backend, old_name);
1140 if (error < 0) {
1141 git_reference_free(old);
1142 return error;
1143 }
1144
1145 new = realloc(old, sizeof(git_reference) + strlen(new_name) + 1);
1146 memcpy(new->name, new_name, strlen(new_name) + 1);
1147
1148 error = loose_write(backend, new);
1149 if (error < 0) {
1150 git_reference_free(new);
1151 return error;
1152 }
1153
1154 if (out) {
1155 *out = new;
1156 } else {
1157 git_reference_free(new);
1158 }
1159
1160 return 0;
1161}
1162
d00d5464
ET
1163static int refdb_fs_backend__compress(git_refdb_backend *_backend)
1164{
1165 refdb_fs_backend *backend;
1166
1167 assert(_backend);
1168 backend = (refdb_fs_backend *)_backend;
1169
1170 if (packed_load(backend) < 0 || /* load the existing packfile */
1171 packed_loadloose(backend) < 0 || /* add all the loose refs */
1172 packed_write(backend) < 0) /* write back to disk */
1173 return -1;
1174
1175 return 0;
1176}
1177
1178static void refcache_free(git_refcache *refs)
1179{
1180 assert(refs);
1181
1182 if (refs->packfile) {
1183 struct packref *reference;
1184
1185 git_strmap_foreach_value(refs->packfile, reference, {
1186 git__free(reference);
1187 });
1188
1189 git_strmap_free(refs->packfile);
1190 }
1191}
1192
1193static void refdb_fs_backend__free(git_refdb_backend *_backend)
1194{
1195 refdb_fs_backend *backend;
1196
1197 assert(_backend);
1198 backend = (refdb_fs_backend *)_backend;
1199
1200 refcache_free(&backend->refcache);
bade5194 1201 git__free(backend->path);
d00d5464
ET
1202 git__free(backend);
1203}
1204
8cddf9b8
VM
1205static int setup_namespace(git_buf *path, git_repository *repo)
1206{
1fed6b07 1207 char *parts, *start, *end;
8cddf9b8 1208
69a3c766
CMN
1209 /* Not all repositories have a path */
1210 if (repo->path_repository == NULL)
1211 return 0;
1212
8cddf9b8
VM
1213 /* Load the path to the repo first */
1214 git_buf_puts(path, repo->path_repository);
1215
1216 /* if the repo is not namespaced, nothing else to do */
1217 if (repo->namespace == NULL)
1218 return 0;
1219
1220 parts = end = git__strdup(repo->namespace);
1221 if (parts == NULL)
1222 return -1;
1223
1224 /**
1225 * From `man gitnamespaces`:
1226 * namespaces which include a / will expand to a hierarchy
1227 * of namespaces; for example, GIT_NAMESPACE=foo/bar will store
1228 * refs under refs/namespaces/foo/refs/namespaces/bar/
1229 */
1230 while ((start = git__strsep(&end, "/")) != NULL) {
1231 git_buf_printf(path, "refs/namespaces/%s/", start);
1232 }
1233
1234 git_buf_printf(path, "refs/namespaces/%s/refs", end);
1235 free(parts);
1236
1237 /* Make sure that the folder with the namespace exists */
1fed6b07 1238 if (git_futils_mkdir_r(git_buf_cstr(path), repo->path_repository, 0777) < 0)
8cddf9b8
VM
1239 return -1;
1240
1241 /* Return the root of the namespaced path, i.e. without the trailing '/refs' */
1242 git_buf_rtruncate_at_char(path, '/');
1243 return 0;
1244}
1245
d00d5464
ET
1246int git_refdb_backend_fs(
1247 git_refdb_backend **backend_out,
4e4eab52 1248 git_repository *repository)
d00d5464 1249{
bade5194 1250 git_buf path = GIT_BUF_INIT;
d00d5464
ET
1251 refdb_fs_backend *backend;
1252
1253 backend = git__calloc(1, sizeof(refdb_fs_backend));
1254 GITERR_CHECK_ALLOC(backend);
1255
1256 backend->repo = repository;
bade5194 1257
8cddf9b8
VM
1258 if (setup_namespace(&path, repository) < 0) {
1259 git__free(backend);
1260 return -1;
1261 }
bade5194
VM
1262
1263 backend->path = git_buf_detach(&path);
d00d5464
ET
1264
1265 backend->parent.exists = &refdb_fs_backend__exists;
1266 backend->parent.lookup = &refdb_fs_backend__lookup;
4def7035 1267 backend->parent.iterator = &refdb_fs_backend__iterator;
d00d5464
ET
1268 backend->parent.write = &refdb_fs_backend__write;
1269 backend->parent.delete = &refdb_fs_backend__delete;
4e6e2ff2 1270 backend->parent.rename = &refdb_fs_backend__rename;
d00d5464
ET
1271 backend->parent.compress = &refdb_fs_backend__compress;
1272 backend->parent.free = &refdb_fs_backend__free;
1273
1274 *backend_out = (git_refdb_backend *)backend;
1275 return 0;
1276}