]> git.proxmox.com Git - libgit2.git/blame - src/refdb_fs.c
New upstream version 1.3.0+dfsg.1
[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"
22a2d3d5 11#include "futils.h"
114f5a6c 12#include "filebuf.h"
d00d5464 13#include "pack.h"
22a2d3d5 14#include "parse.h"
d00d5464 15#include "reflog.h"
d00d5464 16#include "refdb.h"
4def7035 17#include "iterator.h"
fe372740 18#include "sortedcache.h"
b976f3c2 19#include "signature.h"
22a2d3d5 20#include "wildmatch.h"
d00d5464
ET
21
22#include <git2/tag.h>
23#include <git2/object.h>
24#include <git2/refdb.h>
4b7e1b9e 25#include <git2/branch.h>
4dcd8780 26#include <git2/sys/refdb_backend.h>
21ca0451 27#include <git2/sys/refs.h>
b976f3c2 28#include <git2/sys/reflog.h>
d00d5464 29
d00d5464
ET
30#define DEFAULT_NESTING_LEVEL 5
31#define MAX_NESTING_LEVEL 10
32
33enum {
f69db390
VM
34 PACKREF_HAS_PEEL = 1,
35 PACKREF_WAS_LOOSE = 2,
2638a03a
VM
36 PACKREF_CANNOT_PEEL = 4,
37 PACKREF_SHADOWED = 8,
f69db390
VM
38};
39
40enum {
41 PEELING_NONE = 0,
42 PEELING_STANDARD,
43 PEELING_FULL
d00d5464
ET
44};
45
46struct packref {
47 git_oid oid;
48 git_oid peel;
49 char flags;
50 char name[GIT_FLEX_ARRAY];
51};
52
53typedef struct refdb_fs_backend {
54 git_refdb_backend parent;
55
56 git_repository *repo;
71dd0861
PS
57 /* path to git directory */
58 char *gitpath;
e0a6c28e
PS
59 /* path to common objects' directory */
60 char *commonpath;
d00d5464 61
fe372740 62 git_sortedcache *refcache;
f69db390 63 int peeling_mode;
219d3457
RB
64 git_iterator_flag_t iterator_flags;
65 uint32_t direach_flags;
1c04a96b 66 int fsync;
d00d5464
ET
67} refdb_fs_backend;
68
01d0c02d
CMN
69static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name);
70
c25aa7cd
PP
71GIT_INLINE(int) loose_path(
72 git_buf *out,
73 const char *base,
74 const char *refname)
75{
76 if (git_buf_joinpath(out, base, refname) < 0)
77 return -1;
78
79 return git_path_validate_filesystem_with_suffix(out->ptr, out->size,
80 CONST_STRLEN(".lock"));
81}
82
83GIT_INLINE(int) reflog_path(
84 git_buf *out,
85 git_repository *repo,
86 const char *refname)
87{
88 const char *base;
89 int error;
90
91 base = (strcmp(refname, GIT_HEAD_FILE) == 0) ? repo->gitdir :
92 repo->commondir;
93
94 if ((error = git_buf_joinpath(out, base, GIT_REFLOG_DIR)) < 0)
95 return error;
96
97 return loose_path(out, out->ptr, refname);
98}
99
fe372740 100static int packref_cmp(const void *a_, const void *b_)
d00d5464 101{
fe372740
RB
102 const struct packref *a = a_, *b = b_;
103 return strcmp(a->name, b->name);
d00d5464
ET
104}
105
fe372740 106static int packed_reload(refdb_fs_backend *backend)
d00d5464 107{
fe372740
RB
108 int error;
109 git_buf packedrefs = GIT_BUF_INIT;
110 char *scan, *eof, *eol;
4dcd8780 111
71dd0861 112 if (!backend->gitpath)
69a3c766
CMN
113 return 0;
114
fe372740 115 error = git_sortedcache_lockandload(backend->refcache, &packedrefs);
d00d5464
ET
116
117 /*
fe372740
RB
118 * If we can't find the packed-refs, clear table and return.
119 * Any other error just gets passed through.
120 * If no error, and file wasn't changed, just return.
121 * Anything else means we need to refresh the packed refs.
d00d5464 122 */
fe372740
RB
123 if (error <= 0) {
124 if (error == GIT_ENOTFOUND) {
c25aa7cd 125 GIT_UNUSED(git_sortedcache_clear(backend->refcache, true));
ac3d33df 126 git_error_clear();
fe372740
RB
127 error = 0;
128 }
129 return error;
d00d5464
ET
130 }
131
fe372740 132 /* At this point, refresh the packed refs from the loaded buffer. */
d00d5464 133
c25aa7cd 134 GIT_UNUSED(git_sortedcache_clear(backend->refcache, false));
d00d5464 135
fe372740
RB
136 scan = (char *)packedrefs.ptr;
137 eof = scan + packedrefs.size;
d00d5464 138
f69db390
VM
139 backend->peeling_mode = PEELING_NONE;
140
fe372740 141 if (*scan == '#') {
1fed6b07 142 static const char *traits_header = "# pack-refs with: ";
f69db390 143
fe372740
RB
144 if (git__prefixcmp(scan, traits_header) == 0) {
145 scan += strlen(traits_header);
146 eol = strchr(scan, '\n');
822645f6 147
fe372740 148 if (!eol)
822645f6 149 goto parse_failed;
fe372740 150 *eol = '\0';
822645f6 151
fe372740 152 if (strstr(scan, " fully-peeled ") != NULL) {
f69db390 153 backend->peeling_mode = PEELING_FULL;
fe372740 154 } else if (strstr(scan, " peeled ") != NULL) {
f69db390
VM
155 backend->peeling_mode = PEELING_STANDARD;
156 }
157
fe372740 158 scan = eol + 1;
f69db390
VM
159 }
160 }
161
fe372740
RB
162 while (scan < eof && *scan == '#') {
163 if (!(eol = strchr(scan, '\n')))
d00d5464 164 goto parse_failed;
fe372740 165 scan = eol + 1;
d00d5464
ET
166 }
167
fe372740
RB
168 while (scan < eof) {
169 struct packref *ref;
170 git_oid oid;
171
172 /* parse "<OID> <refname>\n" */
d00d5464 173
fe372740 174 if (git_oid_fromstr(&oid, scan) < 0)
d00d5464 175 goto parse_failed;
fe372740 176 scan += GIT_OID_HEXSZ;
d00d5464 177
fe372740
RB
178 if (*scan++ != ' ')
179 goto parse_failed;
180 if (!(eol = strchr(scan, '\n')))
181 goto parse_failed;
182 *eol = '\0';
183 if (eol[-1] == '\r')
184 eol[-1] = '\0';
d00d5464 185
fe372740 186 if (git_sortedcache_upsert((void **)&ref, backend->refcache, scan) < 0)
d00d5464 187 goto parse_failed;
fe372740
RB
188 scan = eol + 1;
189
190 git_oid_cpy(&ref->oid, &oid);
191
192 /* look for optional "^<OID>\n" */
193
194 if (*scan == '^') {
195 if (git_oid_fromstr(&oid, scan + 1) < 0)
196 goto parse_failed;
197 scan += GIT_OID_HEXSZ + 1;
198
199 if (scan < eof) {
200 if (!(eol = strchr(scan, '\n')))
201 goto parse_failed;
202 scan = eol + 1;
203 }
204
205 git_oid_cpy(&ref->peel, &oid);
206 ref->flags |= PACKREF_HAS_PEEL;
207 }
208 else if (backend->peeling_mode == PEELING_FULL ||
209 (backend->peeling_mode == PEELING_STANDARD &&
210 git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0))
211 ref->flags |= PACKREF_CANNOT_PEEL;
d00d5464
ET
212 }
213
8d9a85d4 214 git_sortedcache_wunlock(backend->refcache);
ac3d33df 215 git_buf_dispose(&packedrefs);
fe372740 216
d00d5464
ET
217 return 0;
218
219parse_failed:
ac3d33df 220 git_error_set(GIT_ERROR_REFERENCE, "corrupted packed references file");
fe372740 221
c25aa7cd 222 GIT_UNUSED(git_sortedcache_clear(backend->refcache, false));
8d9a85d4 223 git_sortedcache_wunlock(backend->refcache);
ac3d33df 224 git_buf_dispose(&packedrefs);
fe372740 225
d00d5464
ET
226 return -1;
227}
228
fe372740
RB
229static int loose_parse_oid(
230 git_oid *oid, const char *filename, git_buf *file_content)
d00d5464 231{
fe372740 232 const char *str = git_buf_cstr(file_content);
d00d5464 233
fe372740 234 if (git_buf_len(file_content) < GIT_OID_HEXSZ)
d00d5464
ET
235 goto corrupted;
236
d00d5464 237 /* we need to get 40 OID characters from the file */
fe372740 238 if (git_oid_fromstr(oid, str) < 0)
d00d5464
ET
239 goto corrupted;
240
241 /* If the file is longer than 40 chars, the 41st must be a space */
242 str += GIT_OID_HEXSZ;
243 if (*str == '\0' || git__isspace(*str))
244 return 0;
245
246corrupted:
ac3d33df 247 git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file: %s", filename);
d00d5464
ET
248 return -1;
249}
250
fe372740
RB
251static int loose_readbuffer(git_buf *buf, const char *base, const char *path)
252{
253 int error;
254
c25aa7cd
PP
255 if ((error = loose_path(buf, base, path)) < 0 ||
256 (error = git_futils_readbuffer(buf, buf->ptr)) < 0)
ac3d33df 257 git_buf_dispose(buf);
fe372740
RB
258
259 return error;
260}
261
262static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name)
d00d5464 263{
fe372740 264 int error = 0;
d00d5464
ET
265 git_buf ref_file = GIT_BUF_INIT;
266 struct packref *ref = NULL;
fe372740 267 git_oid oid;
d00d5464 268
fe372740
RB
269 /* if we fail to load the loose reference, assume someone changed
270 * the filesystem under us and skip it...
271 */
71dd0861 272 if (loose_readbuffer(&ref_file, backend->gitpath, name) < 0) {
ac3d33df 273 git_error_clear();
fe372740
RB
274 goto done;
275 }
d00d5464 276
0f0f5655 277 /* skip symbolic refs */
fe372740
RB
278 if (!git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF))
279 goto done;
0f0f5655 280
fe372740
RB
281 /* parse OID from file */
282 if ((error = loose_parse_oid(&oid, name, &ref_file)) < 0)
283 goto done;
d00d5464 284
c25aa7cd
PP
285 if ((error = git_sortedcache_wlock(backend->refcache)) < 0)
286 goto done;
d00d5464 287
fe372740
RB
288 if (!(error = git_sortedcache_upsert(
289 (void **)&ref, backend->refcache, name))) {
d00d5464 290
fe372740
RB
291 git_oid_cpy(&ref->oid, &oid);
292 ref->flags = PACKREF_WAS_LOOSE;
d00d5464
ET
293 }
294
8d9a85d4 295 git_sortedcache_wunlock(backend->refcache);
d00d5464 296
fe372740 297done:
ac3d33df 298 git_buf_dispose(&ref_file);
fe372740 299 return error;
d00d5464
ET
300}
301
25e0b157 302static int _dirent_loose_load(void *payload, git_buf *full_path)
d00d5464 303{
25e0b157 304 refdb_fs_backend *backend = payload;
d00d5464 305 const char *file_path;
d00d5464 306
fe372740
RB
307 if (git__suffixcmp(full_path->ptr, ".lock") == 0)
308 return 0;
309
a213a7bf
CMN
310 if (git_path_isdir(full_path->ptr)) {
311 int error = git_path_direach(
25e0b157 312 full_path, backend->direach_flags, _dirent_loose_load, backend);
a213a7bf
CMN
313 /* Race with the filesystem, ignore it */
314 if (error == GIT_ENOTFOUND) {
ac3d33df 315 git_error_clear();
a213a7bf
CMN
316 return 0;
317 }
318
319 return error;
320 }
d00d5464 321
71dd0861 322 file_path = full_path->ptr + strlen(backend->gitpath);
d00d5464 323
25e0b157 324 return loose_lookup_to_packfile(backend, file_path);
d00d5464
ET
325}
326
327/*
328 * Load all the loose references from the repository
329 * into the in-memory Packfile, and build a vector with
330 * all the references so it can be written back to
331 * disk.
332 */
333static int packed_loadloose(refdb_fs_backend *backend)
334{
fe372740 335 int error;
d00d5464 336 git_buf refs_path = GIT_BUF_INIT;
d00d5464 337
71dd0861 338 if (git_buf_joinpath(&refs_path, backend->gitpath, GIT_REFS_DIR) < 0)
d00d5464
ET
339 return -1;
340
341 /*
342 * Load all the loose files from disk into the Packfile table.
343 * This will overwrite any old packed entries with their
344 * updated loose versions
345 */
219d3457 346 error = git_path_direach(
25e0b157 347 &refs_path, backend->direach_flags, _dirent_loose_load, backend);
fe372740 348
ac3d33df 349 git_buf_dispose(&refs_path);
d00d5464 350
96869a4e 351 return error;
d00d5464
ET
352}
353
354static int refdb_fs_backend__exists(
355 int *exists,
356 git_refdb_backend *_backend,
357 const char *ref_name)
358{
22a2d3d5 359 refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
d00d5464 360 git_buf ref_path = GIT_BUF_INIT;
ce5553d4 361 int error;
d00d5464 362
c25aa7cd 363 GIT_ASSERT_ARG(backend);
d00d5464 364
22a2d3d5
UG
365 *exists = 0;
366
c25aa7cd 367 if ((error = loose_path(&ref_path, backend->gitpath, ref_name)) < 0)
22a2d3d5 368 goto out;
d00d5464 369
22a2d3d5
UG
370 if (git_path_isfile(ref_path.ptr)) {
371 *exists = 1;
372 goto out;
373 }
374
375 if ((error = packed_reload(backend)) < 0)
376 goto out;
377
378 if (git_sortedcache_lookup(backend->refcache, ref_name) != NULL) {
379 *exists = 1;
380 goto out;
381 }
d00d5464 382
22a2d3d5 383out:
ac3d33df 384 git_buf_dispose(&ref_path);
22a2d3d5 385 return error;
d00d5464
ET
386}
387
388static const char *loose_parse_symbolic(git_buf *file_content)
389{
390 const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);
391 const char *refname_start;
392
393 refname_start = (const char *)file_content->ptr;
394
395 if (git_buf_len(file_content) < header_len + 1) {
ac3d33df 396 git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file");
d00d5464
ET
397 return NULL;
398 }
399
400 /*
401 * Assume we have already checked for the header
402 * before calling this function
403 */
404 refname_start += header_len;
405
406 return refname_start;
407}
408
6da6b425
PS
409/*
410 * Returns whether a reference is stored per worktree or not.
411 * Per-worktree references are:
412 *
413 * - all pseudorefs, e.g. HEAD and MERGE_HEAD
414 * - all references stored inside of "refs/bisect/"
415 */
e0a6c28e
PS
416static bool is_per_worktree_ref(const char *ref_name)
417{
6da6b425
PS
418 return git__prefixcmp(ref_name, "refs/") != 0 ||
419 git__prefixcmp(ref_name, "refs/bisect/") == 0;
e0a6c28e
PS
420}
421
d00d5464
ET
422static int loose_lookup(
423 git_reference **out,
424 refdb_fs_backend *backend,
425 const char *ref_name)
426{
d00d5464
ET
427 git_buf ref_file = GIT_BUF_INIT;
428 int error = 0;
e0a6c28e 429 const char *ref_dir;
d00d5464 430
b7107131
RB
431 if (out)
432 *out = NULL;
433
e0a6c28e
PS
434 if (is_per_worktree_ref(ref_name))
435 ref_dir = backend->gitpath;
436 else
437 ref_dir = backend->commonpath;
438
439 if ((error = loose_readbuffer(&ref_file, ref_dir, ref_name)) < 0)
fe372740
RB
440 /* cannot read loose ref file - gah */;
441 else if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) {
442 const char *target;
d00d5464 443
d00d5464
ET
444 git_buf_rtrim(&ref_file);
445
fe372740 446 if (!(target = loose_parse_symbolic(&ref_file)))
d00d5464 447 error = -1;
fe372740 448 else if (out != NULL)
b7107131 449 *out = git_reference__alloc_symbolic(ref_name, target);
d00d5464 450 } else {
fe372740 451 git_oid oid;
4dcd8780 452
fe372740
RB
453 if (!(error = loose_parse_oid(&oid, ref_name, &ref_file)) &&
454 out != NULL)
b7107131 455 *out = git_reference__alloc(ref_name, &oid, NULL);
d00d5464
ET
456 }
457
ac3d33df 458 git_buf_dispose(&ref_file);
d00d5464
ET
459 return error;
460}
461
fe372740 462static int ref_error_notfound(const char *name)
d00d5464 463{
ac3d33df 464 git_error_set(GIT_ERROR_REFERENCE, "reference '%s' not found", name);
fe372740 465 return GIT_ENOTFOUND;
d00d5464
ET
466}
467
468static int packed_lookup(
469 git_reference **out,
470 refdb_fs_backend *backend,
471 const char *ref_name)
472{
d00d5464 473 int error = 0;
fe372740 474 struct packref *entry;
4dcd8780 475
ce5553d4
CMN
476 if ((error = packed_reload(backend)) < 0)
477 return error;
4dcd8780 478
8d9a85d4
RB
479 if (git_sortedcache_rlock(backend->refcache) < 0)
480 return -1;
fe372740
RB
481
482 entry = git_sortedcache_lookup(backend->refcache, ref_name);
483 if (!entry) {
484 error = ref_error_notfound(ref_name);
485 } else {
486 *out = git_reference__alloc(ref_name, &entry->oid, &entry->peel);
487 if (!*out)
488 error = -1;
489 }
490
8d9a85d4
RB
491 git_sortedcache_runlock(backend->refcache);
492
fe372740 493 return error;
d00d5464
ET
494}
495
496static int refdb_fs_backend__lookup(
497 git_reference **out,
498 git_refdb_backend *_backend,
499 const char *ref_name)
500{
22a2d3d5 501 refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
fe372740 502 int error;
d00d5464 503
c25aa7cd 504 GIT_ASSERT_ARG(backend);
d00d5464 505
fe372740 506 if (!(error = loose_lookup(out, backend, ref_name)))
d00d5464
ET
507 return 0;
508
509 /* only try to lookup this reference on the packfile if it
510 * wasn't found on the loose refs; not if there was a critical error */
fe372740 511 if (error == GIT_ENOTFOUND) {
ac3d33df 512 git_error_clear();
fe372740 513 error = packed_lookup(out, backend, ref_name);
d00d5464
ET
514 }
515
fe372740 516 return error;
d00d5464
ET
517}
518
4def7035
CMN
519typedef struct {
520 git_reference_iterator parent;
2638a03a 521
ec24e542 522 char *glob;
c77342ef
RB
523
524 git_pool pool;
2638a03a 525 git_vector loose;
c77342ef 526
2d945f82 527 git_sortedcache *cache;
fe372740
RB
528 size_t loose_pos;
529 size_t packed_pos;
4def7035
CMN
530} refdb_fs_iter;
531
4def7035 532static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter)
d00d5464 533{
22a2d3d5 534 refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent);
2638a03a
VM
535
536 git_vector_free(&iter->loose);
c77342ef 537 git_pool_clear(&iter->pool);
2d945f82 538 git_sortedcache_free(iter->cache);
4def7035
CMN
539 git__free(iter);
540}
d00d5464 541
ec24e542 542static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter)
4def7035 543{
219d3457 544 int error = 0;
2638a03a 545 git_buf path = GIT_BUF_INIT;
c77342ef 546 git_iterator *fsit = NULL;
ed1c6446 547 git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT;
2638a03a 548 const git_index_entry *entry = NULL;
ac3d33df
JK
549 const char *ref_prefix = GIT_REFS_DIR;
550 size_t ref_prefix_len = strlen(ref_prefix);
d00d5464 551
e0a6c28e 552 if (!backend->commonpath) /* do nothing if no commonpath for loose refs */
cee695ae
RB
553 return 0;
554
ed1c6446
ET
555 fsit_opts.flags = backend->iterator_flags;
556
ac3d33df
JK
557 if (iter->glob) {
558 const char *last_sep = NULL;
559 const char *pos;
560 for (pos = iter->glob; *pos; ++pos) {
561 switch (*pos) {
562 case '?':
563 case '*':
564 case '[':
565 case '\\':
566 break;
567 case '/':
568 last_sep = pos;
569 /* FALLTHROUGH */
570 default:
571 continue;
572 }
573 break;
574 }
575 if (last_sep) {
576 ref_prefix = iter->glob;
577 ref_prefix_len = (last_sep - ref_prefix) + 1;
578 }
579 }
580
c25aa7cd 581 if ((error = git_buf_puts(&path, backend->commonpath)) < 0 ||
ac3d33df
JK
582 (error = git_buf_put(&path, ref_prefix, ref_prefix_len)) < 0) {
583 git_buf_dispose(&path);
fe372740
RB
584 return error;
585 }
d00d5464 586
ac3d33df
JK
587 if ((error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) {
588 git_buf_dispose(&path);
589 return (iter->glob && error == GIT_ENOTFOUND)? 0 : error;
590 }
591
592 error = git_buf_sets(&path, ref_prefix);
d00d5464 593
fe372740 594 while (!error && !git_iterator_advance(&entry, fsit)) {
2638a03a 595 const char *ref_name;
c77342ef 596 char *ref_dup;
d00d5464 597
ac3d33df 598 git_buf_truncate(&path, ref_prefix_len);
2638a03a
VM
599 git_buf_puts(&path, entry->path);
600 ref_name = git_buf_cstr(&path);
4dcd8780 601
ec24e542 602 if (git__suffixcmp(ref_name, ".lock") == 0 ||
22a2d3d5 603 (iter->glob && wildmatch(iter->glob, ref_name, 0) != 0))
2638a03a 604 continue;
d00d5464 605
fe372740
RB
606 ref_dup = git_pool_strdup(&iter->pool, ref_name);
607 if (!ref_dup)
c77342ef 608 error = -1;
fe372740
RB
609 else
610 error = git_vector_insert(&iter->loose, ref_dup);
2638a03a 611 }
d00d5464 612
2638a03a 613 git_iterator_free(fsit);
ac3d33df 614 git_buf_dispose(&path);
4def7035 615
fe372740 616 return error;
4def7035
CMN
617}
618
ec24e542
VM
619static int refdb_fs_backend__iterator_next(
620 git_reference **out, git_reference_iterator *_iter)
4def7035 621{
8d9a85d4 622 int error = GIT_ITEROVER;
22a2d3d5
UG
623 refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent);
624 refdb_fs_backend *backend = GIT_CONTAINER_OF(iter->parent.db->backend, refdb_fs_backend, parent);
fe372740 625 struct packref *ref;
4def7035 626
56960b83 627 while (iter->loose_pos < iter->loose.length) {
2638a03a 628 const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
56960b83 629
22a2d3d5
UG
630 if (loose_lookup(out, backend, path) == 0) {
631 ref = git_sortedcache_lookup(iter->cache, path);
632 if (ref)
633 ref->flags |= PACKREF_SHADOWED;
634
56960b83 635 return 0;
22a2d3d5 636 }
56960b83 637
ac3d33df 638 git_error_clear();
2638a03a 639 }
d00d5464 640
2d945f82
CMN
641 error = GIT_ITEROVER;
642 while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) {
643 ref = git_sortedcache_entry(iter->cache, iter->packed_pos++);
fe372740
RB
644 if (!ref) /* stop now if another thread deleted refs and we past end */
645 break;
ec24e542
VM
646
647 if (ref->flags & PACKREF_SHADOWED)
648 continue;
22a2d3d5 649 if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0)
ec24e542 650 continue;
99d32707 651
56960b83 652 *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel);
8d9a85d4
RB
653 error = (*out != NULL) ? 0 : -1;
654 break;
4def7035
CMN
655 }
656
8d9a85d4 657 return error;
d00d5464
ET
658}
659
ec24e542
VM
660static int refdb_fs_backend__iterator_next_name(
661 const char **out, git_reference_iterator *_iter)
662{
8d9a85d4 663 int error = GIT_ITEROVER;
22a2d3d5
UG
664 refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent);
665 refdb_fs_backend *backend = GIT_CONTAINER_OF(iter->parent.db->backend, refdb_fs_backend, parent);
8d9a85d4 666 struct packref *ref;
ec24e542
VM
667
668 while (iter->loose_pos < iter->loose.length) {
669 const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
22a2d3d5 670 struct packref *ref;
ec24e542 671
8d9a85d4 672 if (loose_lookup(NULL, backend, path) == 0) {
22a2d3d5
UG
673 ref = git_sortedcache_lookup(iter->cache, path);
674 if (ref)
675 ref->flags |= PACKREF_SHADOWED;
676
8d9a85d4
RB
677 *out = path;
678 return 0;
b7107131
RB
679 }
680
ac3d33df 681 git_error_clear();
ec24e542
VM
682 }
683
2d945f82
CMN
684 error = GIT_ITEROVER;
685 while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) {
686 ref = git_sortedcache_entry(iter->cache, iter->packed_pos++);
8d9a85d4
RB
687 if (!ref) /* stop now if another thread deleted refs and we past end */
688 break;
fe372740
RB
689
690 if (ref->flags & PACKREF_SHADOWED)
691 continue;
22a2d3d5 692 if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0)
ec24e542
VM
693 continue;
694
8d9a85d4
RB
695 *out = ref->name;
696 error = 0;
697 break;
ec24e542
VM
698 }
699
8d9a85d4 700 return error;
ec24e542
VM
701}
702
703static int refdb_fs_backend__iterator(
704 git_reference_iterator **out, git_refdb_backend *_backend, const char *glob)
705{
22a2d3d5
UG
706 refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
707 refdb_fs_iter *iter = NULL;
ce5553d4 708 int error;
ec24e542 709
c25aa7cd 710 GIT_ASSERT_ARG(backend);
ec24e542 711
ec24e542 712 iter = git__calloc(1, sizeof(refdb_fs_iter));
ac3d33df 713 GIT_ERROR_CHECK_ALLOC(iter);
ec24e542 714
22a2d3d5
UG
715 if ((error = git_pool_init(&iter->pool, 1)) < 0)
716 goto out;
1e5e02b4 717
22a2d3d5
UG
718 if ((error = git_vector_init(&iter->loose, 8, NULL)) < 0)
719 goto out;
c77342ef
RB
720
721 if (glob != NULL &&
22a2d3d5
UG
722 (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL) {
723 error = GIT_ERROR_NOMEMORY;
724 goto out;
725 }
726
727 if ((error = iter_load_loose_paths(backend, iter)) < 0)
728 goto out;
729
730 if ((error = packed_reload(backend)) < 0)
731 goto out;
732
733 if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0)
734 goto out;
ec24e542
VM
735
736 iter->parent.next = refdb_fs_backend__iterator_next;
737 iter->parent.next_name = refdb_fs_backend__iterator_next_name;
738 iter->parent.free = refdb_fs_backend__iterator_free;
739
ec24e542 740 *out = (git_reference_iterator *)iter;
22a2d3d5
UG
741out:
742 if (error)
743 refdb_fs_backend__iterator_free((git_reference_iterator *)iter);
744 return error;
ec24e542
VM
745}
746
4e6e2ff2
VM
747static bool ref_is_available(
748 const char *old_ref, const char *new_ref, const char *this_ref)
749{
750 if (old_ref == NULL || strcmp(old_ref, this_ref)) {
751 size_t reflen = strlen(this_ref);
752 size_t newlen = strlen(new_ref);
753 size_t cmplen = reflen < newlen ? reflen : newlen;
754 const char *lead = reflen < newlen ? new_ref : this_ref;
755
756 if (!strncmp(new_ref, this_ref, cmplen) && lead[cmplen] == '/') {
757 return false;
758 }
759 }
760
761 return true;
762}
763
764static int reference_path_available(
765 refdb_fs_backend *backend,
766 const char *new_ref,
c25aa7cd 767 const char *old_ref,
4e6e2ff2
VM
768 int force)
769{
fe372740 770 size_t i;
ce5553d4 771 int error;
4e6e2ff2 772
ce5553d4
CMN
773 if ((error = packed_reload(backend)) < 0)
774 return error;
4e6e2ff2
VM
775
776 if (!force) {
777 int exists;
778
ce5553d4
CMN
779 if ((error = refdb_fs_backend__exists(
780 &exists, (git_refdb_backend *)backend, new_ref)) < 0) {
781 return error;
782 }
4e6e2ff2
VM
783
784 if (exists) {
ac3d33df 785 git_error_set(GIT_ERROR_REFERENCE,
909d5494 786 "failed to write reference '%s': a reference with "
8d9a85d4 787 "that name already exists.", new_ref);
4e6e2ff2
VM
788 return GIT_EEXISTS;
789 }
790 }
791
c25aa7cd
PP
792 if ((error = git_sortedcache_rlock(backend->refcache)) < 0)
793 return error;
fe372740
RB
794
795 for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) {
8d9a85d4 796 struct packref *ref = git_sortedcache_entry(backend->refcache, i);
fe372740 797
8d9a85d4
RB
798 if (ref && !ref_is_available(old_ref, new_ref, ref->name)) {
799 git_sortedcache_runlock(backend->refcache);
ac3d33df 800 git_error_set(GIT_ERROR_REFERENCE,
909d5494 801 "path to reference '%s' collides with existing one", new_ref);
4e6e2ff2
VM
802 return -1;
803 }
fe372740 804 }
c77342ef 805
8d9a85d4 806 git_sortedcache_runlock(backend->refcache);
4e6e2ff2
VM
807 return 0;
808}
ec24e542 809
7ee8c7e6 810static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name)
d00d5464 811{
af3dcb0e 812 int error, filebuf_flags;
d00d5464 813 git_buf ref_path = GIT_BUF_INIT;
097f0105 814 const char *basedir;
d00d5464 815
c25aa7cd
PP
816 GIT_ASSERT_ARG(file);
817 GIT_ASSERT_ARG(backend);
818 GIT_ASSERT_ARG(name);
9b148098 819
c25aa7cd 820 if (!git_path_validate(backend->repo, name, 0, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
ac3d33df 821 git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", name);
a64119e3
ET
822 return GIT_EINVALIDSPEC;
823 }
824
097f0105
PS
825 if (is_per_worktree_ref(name))
826 basedir = backend->gitpath;
827 else
828 basedir = backend->commonpath;
829
d00d5464
ET
830 /* Remove a possibly existing empty directory hierarchy
831 * which name would collide with the reference name
832 */
097f0105 833 if ((error = git_futils_rmdir_r(name, basedir, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
6ab65b80 834 return error;
d00d5464 835
c25aa7cd
PP
836 if ((error = loose_path(&ref_path, basedir, name)) < 0)
837 return error;
d00d5464 838
22a2d3d5 839 filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS;
1c04a96b 840 if (backend->fsync)
af3dcb0e
ET
841 filebuf_flags |= GIT_FILEBUF_FSYNC;
842
843 error = git_filebuf_open(file, ref_path.ptr, filebuf_flags, GIT_REFS_FILE_MODE);
d00d5464 844
b46c7ee5 845 if (error == GIT_EDIRECTORY)
ac3d33df 846 git_error_set(GIT_ERROR_REFERENCE, "cannot lock ref '%s', there are refs beneath that folder", name);
b46c7ee5 847
ac3d33df 848 git_buf_dispose(&ref_path);
b46c7ee5 849 return error;
a57dd3b7 850}
d00d5464 851
a57dd3b7
CMN
852static int loose_commit(git_filebuf *file, const git_reference *ref)
853{
c25aa7cd
PP
854 GIT_ASSERT_ARG(file);
855 GIT_ASSERT_ARG(ref);
9b148098 856
ac3d33df 857 if (ref->type == GIT_REFERENCE_DIRECT) {
d00d5464 858 char oid[GIT_OID_HEXSZ + 1];
fe372740 859 git_oid_nfmt(oid, sizeof(oid), &ref->target.oid);
d00d5464 860
a57dd3b7 861 git_filebuf_printf(file, "%s\n", oid);
ac3d33df 862 } else if (ref->type == GIT_REFERENCE_SYMBOLIC) {
a57dd3b7 863 git_filebuf_printf(file, GIT_SYMREF "%s\n", ref->target.symbolic);
d00d5464 864 } else {
c25aa7cd 865 GIT_ASSERT(0);
d00d5464
ET
866 }
867
a57dd3b7 868 return git_filebuf_commit(file);
d00d5464
ET
869}
870
ab8d9242
CMN
871static int refdb_fs_backend__lock(void **out, git_refdb_backend *_backend, const char *refname)
872{
873 int error;
874 git_filebuf *lock;
22a2d3d5 875 refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
ab8d9242
CMN
876
877 lock = git__calloc(1, sizeof(git_filebuf));
ac3d33df 878 GIT_ERROR_CHECK_ALLOC(lock);
ab8d9242
CMN
879
880 if ((error = loose_lock(lock, backend, refname)) < 0) {
881 git__free(lock);
882 return error;
883 }
884
885 *out = lock;
886 return 0;
887}
888
889static int refdb_fs_backend__write_tail(
890 git_refdb_backend *_backend,
891 const git_reference *ref,
892 git_filebuf *file,
893 int update_reflog,
ab8d9242 894 const git_oid *old_id,
22a2d3d5
UG
895 const char *old_target,
896 const git_signature *who,
897 const char *message);
ab8d9242
CMN
898
899static int refdb_fs_backend__delete_tail(
900 git_refdb_backend *_backend,
901 git_filebuf *file,
902 const char *ref_name,
22a2d3d5
UG
903 const git_oid *old_id,
904 const char *old_target);
ab8d9242
CMN
905
906static int refdb_fs_backend__unlock(git_refdb_backend *backend, void *payload, int success, int update_reflog,
907 const git_reference *ref, const git_signature *sig, const char *message)
908{
909 git_filebuf *lock = (git_filebuf *) payload;
910 int error = 0;
911
912 if (success == 2)
913 error = refdb_fs_backend__delete_tail(backend, lock, ref->name, NULL, NULL);
914 else if (success)
22a2d3d5 915 error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, NULL, NULL, sig, message);
ab8d9242
CMN
916 else
917 git_filebuf_cleanup(lock);
918
919 git__free(lock);
920 return error;
921}
922
d00d5464
ET
923/*
924 * Find out what object this reference resolves to.
925 *
926 * For references that point to a 'big' tag (e.g. an
927 * actual tag object on the repository), we need to
928 * cache on the packfile the OID of the object to
929 * which that 'big tag' is pointing to.
930 */
931static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref)
932{
933 git_object *object;
934
f69db390 935 if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL)
d00d5464
ET
936 return 0;
937
d00d5464
ET
938 /*
939 * Find the tagged object in the repository
940 */
ac3d33df 941 if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJECT_ANY) < 0)
d00d5464
ET
942 return -1;
943
944 /*
945 * If the tagged object is a Tag object, we need to resolve it;
946 * if the ref is actually a 'weak' ref, we don't need to resolve
947 * anything.
948 */
ac3d33df 949 if (git_object_type(object) == GIT_OBJECT_TAG) {
d00d5464
ET
950 git_tag *tag = (git_tag *)object;
951
952 /*
953 * Find the object pointed at by this tag
954 */
955 git_oid_cpy(&ref->peel, git_tag_target_id(tag));
f69db390 956 ref->flags |= PACKREF_HAS_PEEL;
d00d5464
ET
957
958 /*
959 * The reference has now cached the resolved OID, and is
960 * marked at such. When written to the packfile, it'll be
961 * accompanied by this resolved oid
962 */
963 }
964
965 git_object_free(object);
966 return 0;
967}
968
969/*
970 * Write a single reference into a packfile
971 */
972static int packed_write_ref(struct packref *ref, git_filebuf *file)
973{
974 char oid[GIT_OID_HEXSZ + 1];
fe372740 975 git_oid_nfmt(oid, sizeof(oid), &ref->oid);
d00d5464
ET
976
977 /*
978 * For references that peel to an object in the repo, we must
979 * write the resulting peel on a separate line, e.g.
980 *
981 * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
982 * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
983 *
984 * This obviously only applies to tags.
985 * The required peels have already been loaded into `ref->peel_target`.
986 */
f69db390 987 if (ref->flags & PACKREF_HAS_PEEL) {
d00d5464 988 char peel[GIT_OID_HEXSZ + 1];
fe372740 989 git_oid_nfmt(peel, sizeof(peel), &ref->peel);
d00d5464
ET
990
991 if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0)
992 return -1;
993 } else {
994 if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0)
995 return -1;
996 }
997
998 return 0;
999}
1000
1001/*
1002 * Remove all loose references
1003 *
1004 * Once we have successfully written a packfile,
1005 * all the loose references that were packed must be
1006 * removed from disk.
1007 *
1008 * This is a dangerous method; make sure the packfile
1009 * is well-written, because we are destructing references
1010 * here otherwise.
1011 */
fe372740 1012static int packed_remove_loose(refdb_fs_backend *backend)
d00d5464 1013{
10c06114 1014 size_t i;
dd1ca6f1 1015 git_filebuf lock = GIT_FILEBUF_INIT;
2d9aec99 1016 git_buf ref_content = GIT_BUF_INIT;
7ea4710a 1017 int error = 0;
d00d5464 1018
fe372740
RB
1019 /* backend->refcache is already locked when this is called */
1020
1021 for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) {
1022 struct packref *ref = git_sortedcache_entry(backend->refcache, i);
2d9aec99 1023 git_oid current_id;
d00d5464 1024
8d9a85d4 1025 if (!ref || !(ref->flags & PACKREF_WAS_LOOSE))
d00d5464
ET
1026 continue;
1027
dd1ca6f1
CMN
1028 git_filebuf_cleanup(&lock);
1029
2d9aec99
CMN
1030 /* We need to stop anybody from updating the ref while we try to do a safe delete */
1031 error = loose_lock(&lock, backend, ref->name);
1032 /* If someone else is updating it, let them do it */
7ea4710a 1033 if (error == GIT_EEXISTS || error == GIT_ENOTFOUND)
2d9aec99
CMN
1034 continue;
1035
1036 if (error < 0) {
ac3d33df
JK
1037 git_buf_dispose(&ref_content);
1038 git_error_set(GIT_ERROR_REFERENCE, "failed to lock loose reference '%s'", ref->name);
7ea4710a 1039 return error;
2d9aec99
CMN
1040 }
1041
1042 error = git_futils_readbuffer(&ref_content, lock.path_original);
1043 /* Someone else beat us to cleaning up the ref, let's simply continue */
dd1ca6f1 1044 if (error == GIT_ENOTFOUND)
2d9aec99 1045 continue;
2d9aec99
CMN
1046
1047 /* This became a symref between us packing and trying to delete it, so ignore it */
dd1ca6f1 1048 if (!git__prefixcmp(ref_content.ptr, GIT_SYMREF))
2d9aec99 1049 continue;
2d9aec99 1050
dd1ca6f1
CMN
1051 /* Figure out the current id; if we find a bad ref file, skip it so we can do the rest */
1052 if (loose_parse_oid(&current_id, lock.path_original, &ref_content) < 0)
2d9aec99 1053 continue;
d00d5464 1054
2d9aec99 1055 /* If the ref moved since we packed it, we must not delete it */
dd1ca6f1 1056 if (!git_oid_equal(&current_id, &ref->oid))
2d9aec99 1057 continue;
2d9aec99 1058
d00d5464
ET
1059 /*
1060 * if we fail to remove a single file, this is *not* good,
1061 * but we should keep going and remove as many as possible.
7ea4710a
CMN
1062 * If we fail to remove, the ref is still in the old state, so
1063 * we haven't lost information.
d00d5464 1064 */
7ea4710a 1065 p_unlink(lock.path_original);
d00d5464
ET
1066 }
1067
ac3d33df 1068 git_buf_dispose(&ref_content);
dd1ca6f1 1069 git_filebuf_cleanup(&lock);
7ea4710a 1070 return 0;
d00d5464
ET
1071}
1072
1073/*
1074 * Write all the contents in the in-memory packfile to disk.
1075 */
1076static int packed_write(refdb_fs_backend *backend)
1077{
8d9a85d4 1078 git_sortedcache *refcache = backend->refcache;
d00d5464 1079 git_filebuf pack_file = GIT_FILEBUF_INIT;
eb56ed81 1080 int error, open_flags = 0;
10c06114 1081 size_t i;
d00d5464 1082
fe372740 1083 /* lock the cache to updates while we do this */
9914efec
CMN
1084 if ((error = git_sortedcache_wlock(refcache)) < 0)
1085 return error;
d00d5464 1086
1c04a96b 1087 if (backend->fsync)
eb56ed81
ET
1088 open_flags = GIT_FILEBUF_FSYNC;
1089
fe372740 1090 /* Open the file! */
eb56ed81 1091 if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), open_flags, GIT_PACKEDREFS_FILE_MODE)) < 0)
8d9a85d4 1092 goto fail;
d00d5464
ET
1093
1094 /* Packfiles have a header... apparently
1095 * This is in fact not required, but we might as well print it
1096 * just for kicks */
9914efec 1097 if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < 0)
8d9a85d4 1098 goto fail;
d00d5464 1099
8d9a85d4
RB
1100 for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) {
1101 struct packref *ref = git_sortedcache_entry(refcache, i);
c25aa7cd 1102 GIT_ASSERT(ref);
d00d5464 1103
9914efec 1104 if ((error = packed_find_peel(backend, ref)) < 0)
8d9a85d4 1105 goto fail;
d00d5464 1106
9914efec 1107 if ((error = packed_write_ref(ref, &pack_file)) < 0)
8d9a85d4 1108 goto fail;
d00d5464
ET
1109 }
1110
1111 /* if we've written all the references properly, we can commit
1112 * the packfile to make the changes effective */
9914efec 1113 if ((error = git_filebuf_commit(&pack_file)) < 0)
8d9a85d4 1114 goto fail;
d00d5464
ET
1115
1116 /* when and only when the packfile has been properly written,
1117 * we can go ahead and remove the loose refs */
9914efec 1118 if ((error = packed_remove_loose(backend)) < 0)
8d9a85d4 1119 goto fail;
d00d5464 1120
8d9a85d4
RB
1121 git_sortedcache_updated(refcache);
1122 git_sortedcache_wunlock(refcache);
d00d5464
ET
1123
1124 /* we're good now */
1125 return 0;
1126
8d9a85d4 1127fail:
d00d5464 1128 git_filebuf_cleanup(&pack_file);
8d9a85d4 1129 git_sortedcache_wunlock(refcache);
d00d5464 1130
9914efec 1131 return error;
d00d5464
ET
1132}
1133
22a2d3d5 1134static int packed_delete(refdb_fs_backend *backend, const char *ref_name)
6f13a305 1135{
22a2d3d5
UG
1136 size_t pack_pos;
1137 int error, found = 0;
6f13a305 1138
22a2d3d5
UG
1139 if ((error = packed_reload(backend)) < 0)
1140 goto cleanup;
83504371 1141
22a2d3d5
UG
1142 if ((error = git_sortedcache_wlock(backend->refcache)) < 0)
1143 goto cleanup;
83504371 1144
22a2d3d5
UG
1145 /* If a packed reference exists, remove it from the packfile and repack if necessary */
1146 error = git_sortedcache_lookup_index(&pack_pos, backend->refcache, ref_name);
1147 if (error == 0) {
1148 error = git_sortedcache_remove(backend->refcache, pack_pos);
1149 found = 1;
1150 }
1151 if (error == GIT_ENOTFOUND)
1152 error = 0;
ac3d33df 1153
22a2d3d5 1154 git_sortedcache_wunlock(backend->refcache);
ac3d33df 1155
22a2d3d5
UG
1156 if (found)
1157 error = packed_write(backend);
6f13a305 1158
22a2d3d5
UG
1159cleanup:
1160 return error;
6f13a305 1161}
a57dd3b7 1162
22a2d3d5
UG
1163static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *author, const char *message);
1164
7ee8c7e6 1165static int cmp_old_ref(int *cmp, git_refdb_backend *backend, const char *name,
f8621dde
CMN
1166 const git_oid *old_id, const char *old_target)
1167{
1168 int error = 0;
1169 git_reference *old_ref = NULL;
1170
1171 *cmp = 0;
b7ae71ec
CMN
1172 /* It "matches" if there is no old value to compare against */
1173 if (!old_id && !old_target)
1174 return 0;
1175
c25aa7cd
PP
1176 if ((error = refdb_fs_backend__lookup(&old_ref, backend, name)) < 0) {
1177 if (error == GIT_ENOTFOUND && old_id && git_oid_is_zero(old_id))
1178 return 0;
b7ae71ec 1179 goto out;
c25aa7cd 1180 }
b7ae71ec
CMN
1181
1182 /* If the types don't match, there's no way the values do */
ac3d33df 1183 if (old_id && old_ref->type != GIT_REFERENCE_DIRECT) {
b7ae71ec
CMN
1184 *cmp = -1;
1185 goto out;
1186 }
ac3d33df 1187 if (old_target && old_ref->type != GIT_REFERENCE_SYMBOLIC) {
b7ae71ec
CMN
1188 *cmp = 1;
1189 goto out;
f8621dde
CMN
1190 }
1191
ac3d33df 1192 if (old_id && old_ref->type == GIT_REFERENCE_DIRECT)
f8621dde
CMN
1193 *cmp = git_oid_cmp(old_id, &old_ref->target.oid);
1194
ac3d33df 1195 if (old_target && old_ref->type == GIT_REFERENCE_SYMBOLIC)
f8621dde
CMN
1196 *cmp = git__strcmp(old_target, old_ref->target.symbolic);
1197
1198out:
1199 git_reference_free(old_ref);
1200
1201 return error;
1202}
1203
4b7e1b9e
CMN
1204/*
1205 * The git.git comment regarding this, for your viewing pleasure:
1206 *
1207 * Special hack: If a branch is updated directly and HEAD
1208 * points to it (may happen on the remote side of a push
1209 * for example) then logically the HEAD reflog should be
1210 * updated too.
1211 * A generic solution implies reverse symref information,
1212 * but finding all symrefs pointing to the given branch
1213 * would be rather costly for this rare event (the direct
1214 * update of a branch) to be worth it. So let's cheat and
1215 * check with HEAD only which should cover 99% of all usage
1216 * scenarios (even 100% of the default ones).
1217 */
1218static int maybe_append_head(refdb_fs_backend *backend, const git_reference *ref, const git_signature *who, const char *message)
1219{
22a2d3d5
UG
1220 git_reference *head = NULL;
1221 git_refdb *refdb = NULL;
1222 int error, write_reflog;
90388aa8 1223 git_oid old_id;
99797c96 1224
22a2d3d5
UG
1225 if ((error = git_repository_refdb(&refdb, backend->repo)) < 0 ||
1226 (error = git_refdb_should_write_head_reflog(&write_reflog, refdb, ref)) < 0)
1227 goto out;
1228 if (!write_reflog)
1229 goto out;
4b7e1b9e 1230
6aaae94a 1231 /* if we can't resolve, we use {0}*40 as old id */
90388aa8
PS
1232 if (git_reference_name_to_id(&old_id, backend->repo, ref->name) < 0)
1233 memset(&old_id, 0, sizeof(old_id));
4b7e1b9e 1234
22a2d3d5
UG
1235 if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0 ||
1236 (error = reflog_append(backend, head, &old_id, git_reference_target(ref), who, message)) < 0)
1237 goto out;
4b7e1b9e 1238
22a2d3d5 1239out:
4b7e1b9e 1240 git_reference_free(head);
22a2d3d5 1241 git_refdb_free(refdb);
4b7e1b9e
CMN
1242 return error;
1243}
1244
d00d5464
ET
1245static int refdb_fs_backend__write(
1246 git_refdb_backend *_backend,
4e6e2ff2 1247 const git_reference *ref,
110df893 1248 int force,
a57dd3b7 1249 const git_signature *who,
9b148098 1250 const char *message,
91123661
CMN
1251 const git_oid *old_id,
1252 const char *old_target)
d00d5464 1253{
22a2d3d5 1254 refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
a57dd3b7 1255 git_filebuf file = GIT_FILEBUF_INIT;
ab8d9242 1256 int error = 0;
d00d5464 1257
c25aa7cd 1258 GIT_ASSERT_ARG(backend);
d00d5464 1259
ce5553d4 1260 if ((error = reference_path_available(backend, ref->name, NULL, force)) < 0)
4e6e2ff2
VM
1261 return error;
1262
9b148098 1263 /* We need to perform the reflog append and old value check under the ref's lock */
7ee8c7e6 1264 if ((error = loose_lock(&file, backend, ref->name)) < 0)
a57dd3b7
CMN
1265 return error;
1266
22a2d3d5 1267 return refdb_fs_backend__write_tail(_backend, ref, &file, true, old_id, old_target, who, message);
ab8d9242
CMN
1268}
1269
1270static int refdb_fs_backend__write_tail(
1271 git_refdb_backend *_backend,
1272 const git_reference *ref,
1273 git_filebuf *file,
1274 int update_reflog,
ab8d9242 1275 const git_oid *old_id,
22a2d3d5
UG
1276 const char *old_target,
1277 const git_signature *who,
1278 const char *message)
ab8d9242 1279{
22a2d3d5 1280 refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
ab8d9242
CMN
1281 int error = 0, cmp = 0, should_write;
1282 const char *new_target = NULL;
1283 const git_oid *new_id = NULL;
1284
7ee8c7e6 1285 if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0)
f8621dde 1286 goto on_error;
91123661
CMN
1287
1288 if (cmp) {
ac3d33df 1289 git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match");
91123661
CMN
1290 error = GIT_EMODIFIED;
1291 goto on_error;
9b148098
CMN
1292 }
1293
ac3d33df 1294 if (ref->type == GIT_REFERENCE_SYMBOLIC)
1afe1400
CMN
1295 new_target = ref->target.symbolic;
1296 else
1297 new_id = &ref->target.oid;
1298
1299 error = cmp_old_ref(&cmp, _backend, ref->name, new_id, new_target);
1300 if (error < 0 && error != GIT_ENOTFOUND)
1301 goto on_error;
1302
1303 /* Don't update if we have the same value */
1304 if (!error && !cmp) {
1305 error = 0;
1306 goto on_error; /* not really error */
1307 }
1308
ab8d9242 1309 if (update_reflog) {
22a2d3d5
UG
1310 git_refdb *refdb;
1311
1312 if ((error = git_repository_refdb__weakptr(&refdb, backend->repo)) < 0 ||
1313 (error = git_refdb_should_write_reflog(&should_write, refdb, ref)) < 0)
4b7e1b9e 1314 goto on_error;
ab8d9242
CMN
1315
1316 if (should_write) {
1317 if ((error = reflog_append(backend, ref, NULL, NULL, who, message)) < 0)
1318 goto on_error;
1319 if ((error = maybe_append_head(backend, ref, who, message)) < 0)
1320 goto on_error;
1321 }
a57dd3b7
CMN
1322 }
1323
ab8d9242 1324 return loose_commit(file, ref);
9b148098
CMN
1325
1326on_error:
ab8d9242 1327 git_filebuf_cleanup(file);
fc4728e3 1328 return error;
d00d5464
ET
1329}
1330
c25aa7cd 1331static int refdb_fs_backend__prune_refs(
ac3d33df
JK
1332 refdb_fs_backend *backend,
1333 const char *ref_name,
22a2d3d5 1334 const char *prefix)
ac3d33df
JK
1335{
1336 git_buf relative_path = GIT_BUF_INIT;
1337 git_buf base_path = GIT_BUF_INIT;
1338 size_t commonlen;
c25aa7cd 1339 int error;
ac3d33df 1340
c25aa7cd
PP
1341 GIT_ASSERT_ARG(backend);
1342 GIT_ASSERT_ARG(ref_name);
ac3d33df 1343
c25aa7cd 1344 if ((error = git_buf_sets(&relative_path, ref_name)) < 0)
ac3d33df
JK
1345 goto cleanup;
1346
1347 git_path_squash_slashes(&relative_path);
1348 if ((commonlen = git_path_common_dirlen("refs/heads/", git_buf_cstr(&relative_path))) == strlen("refs/heads/") ||
1349 (commonlen = git_path_common_dirlen("refs/tags/", git_buf_cstr(&relative_path))) == strlen("refs/tags/") ||
1350 (commonlen = git_path_common_dirlen("refs/remotes/", git_buf_cstr(&relative_path))) == strlen("refs/remotes/")) {
1351
1352 git_buf_truncate(&relative_path, commonlen);
1353
c25aa7cd
PP
1354 if (prefix)
1355 error = git_buf_join3(&base_path, '/',
1356 backend->commonpath, prefix,
1357 git_buf_cstr(&relative_path));
1358 else
1359 error = git_buf_joinpath(&base_path,
1360 backend->commonpath,
1361 git_buf_cstr(&relative_path));
1362
1363 if (!error)
1364 error = git_path_validate_filesystem(base_path.ptr, base_path.size);
1365
1366 if (error < 0)
1367 goto cleanup;
1368
1369 error = git_futils_rmdir_r(ref_name + commonlen,
1370 git_buf_cstr(&base_path),
1371 GIT_RMDIR_EMPTY_PARENTS | GIT_RMDIR_SKIP_ROOT);
ac3d33df 1372
c25aa7cd
PP
1373 if (error == GIT_ENOTFOUND)
1374 error = 0;
ac3d33df
JK
1375 }
1376
1377cleanup:
1378 git_buf_dispose(&relative_path);
1379 git_buf_dispose(&base_path);
c25aa7cd 1380 return error;
ac3d33df
JK
1381}
1382
d00d5464
ET
1383static int refdb_fs_backend__delete(
1384 git_refdb_backend *_backend,
7ee8c7e6
CMN
1385 const char *ref_name,
1386 const git_oid *old_id, const char *old_target)
d00d5464 1387{
22a2d3d5 1388 refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
77ad6754 1389 git_filebuf file = GIT_FILEBUF_INIT;
ab8d9242 1390 int error = 0;
d00d5464 1391
c25aa7cd
PP
1392 GIT_ASSERT_ARG(backend);
1393 GIT_ASSERT_ARG(ref_name);
d00d5464 1394
7ee8c7e6
CMN
1395 if ((error = loose_lock(&file, backend, ref_name)) < 0)
1396 return error;
1397
01d0c02d
CMN
1398 if ((error = refdb_reflog_fs__delete(_backend, ref_name)) < 0) {
1399 git_filebuf_cleanup(&file);
1400 return error;
1401 }
1402
ab8d9242
CMN
1403 return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target);
1404}
1405
22a2d3d5
UG
1406static int loose_delete(refdb_fs_backend *backend, const char *ref_name)
1407{
c25aa7cd 1408 git_buf path = GIT_BUF_INIT;
22a2d3d5
UG
1409 int error = 0;
1410
c25aa7cd
PP
1411 if ((error = loose_path(&path, backend->commonpath, ref_name)) < 0)
1412 return error;
22a2d3d5 1413
c25aa7cd 1414 error = p_unlink(path.ptr);
22a2d3d5
UG
1415 if (error < 0 && errno == ENOENT)
1416 error = GIT_ENOTFOUND;
1417 else if (error != 0)
1418 error = -1;
1419
c25aa7cd 1420 git_buf_dispose(&path);
22a2d3d5
UG
1421
1422 return error;
1423}
1424
ab8d9242
CMN
1425static int refdb_fs_backend__delete_tail(
1426 git_refdb_backend *_backend,
1427 git_filebuf *file,
1428 const char *ref_name,
1429 const git_oid *old_id, const char *old_target)
1430{
22a2d3d5 1431 refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
ab8d9242 1432 int error = 0, cmp = 0;
22a2d3d5 1433 bool packed_deleted = 0;
ab8d9242 1434
7ee8c7e6 1435 error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target);
7ee8c7e6
CMN
1436 if (error < 0)
1437 goto cleanup;
1438
1439 if (cmp) {
ac3d33df 1440 git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match");
7ee8c7e6
CMN
1441 error = GIT_EMODIFIED;
1442 goto cleanup;
1443 }
1444
22a2d3d5
UG
1445 /*
1446 * To ensure that an external observer will see either the current ref value
1447 * (because the loose ref still exists), or a missing ref (after the packed-file is
1448 * unlocked, there will be nothing left), we must ensure things happen in the
1449 * following order:
1450 *
1451 * - the packed-ref file is locked and loaded, as well as a loose one, if it exists
1452 * - we optimistically delete a packed ref, keeping track of whether it existed
1453 * - we delete the loose ref, note that we have its .lock
1454 * - the loose ref is "unlocked", then the packed-ref file is rewritten and unlocked
1455 * - we should prune the path components if a loose ref was deleted
1456 *
1457 * Note that, because our packed backend doesn't expose its filesystem lock,
1458 * we might not be able to guarantee that this is what actually happens (ie.
1459 * as our current code never write packed-refs.lock, nothing stops observers
1460 * from grabbing a "stale" value from there).
1461 */
1462 if ((error = packed_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND)
7ee8c7e6 1463 goto cleanup;
d00d5464 1464
22a2d3d5
UG
1465 if (error == 0)
1466 packed_deleted = 1;
fe372740 1467
22a2d3d5 1468 if ((error = loose_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND)
7ee8c7e6 1469 goto cleanup;
4e6e2ff2 1470
7ee8c7e6 1471 if (error == GIT_ENOTFOUND) {
22a2d3d5 1472 error = packed_deleted ? 0 : ref_error_notfound(ref_name);
7ee8c7e6
CMN
1473 goto cleanup;
1474 }
1475
7ee8c7e6 1476cleanup:
ab8d9242 1477 git_filebuf_cleanup(file);
22a2d3d5 1478 if (error == 0)
c25aa7cd 1479 error = refdb_fs_backend__prune_refs(backend, ref_name, "");
7ee8c7e6 1480 return error;
d00d5464
ET
1481}
1482
a57dd3b7
CMN
1483static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name);
1484
4e6e2ff2
VM
1485static int refdb_fs_backend__rename(
1486 git_reference **out,
1487 git_refdb_backend *_backend,
1488 const char *old_name,
1489 const char *new_name,
110df893 1490 int force,
a57dd3b7 1491 const git_signature *who,
110df893 1492 const char *message)
4e6e2ff2 1493{
22a2d3d5
UG
1494 refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1495 git_reference *old, *new = NULL;
a57dd3b7 1496 git_filebuf file = GIT_FILEBUF_INIT;
4e6e2ff2
VM
1497 int error;
1498
c25aa7cd 1499 GIT_ASSERT_ARG(backend);
4e6e2ff2 1500
fe372740
RB
1501 if ((error = reference_path_available(
1502 backend, new_name, old_name, force)) < 0 ||
1503 (error = refdb_fs_backend__lookup(&old, _backend, old_name)) < 0)
4e6e2ff2
VM
1504 return error;
1505
7ee8c7e6 1506 if ((error = refdb_fs_backend__delete(_backend, old_name, NULL, NULL)) < 0) {
4e6e2ff2
VM
1507 git_reference_free(old);
1508 return error;
1509 }
1510
22a2d3d5 1511 new = git_reference__realloc(&old, new_name);
fe372740
RB
1512 if (!new) {
1513 git_reference_free(old);
1514 return -1;
4e6e2ff2
VM
1515 }
1516
7ee8c7e6 1517 if ((error = loose_lock(&file, backend, new->name)) < 0) {
a57dd3b7
CMN
1518 git_reference_free(new);
1519 return error;
1520 }
1521
1522 /* Try to rename the refog; it's ok if the old doesn't exist */
1523 error = refdb_reflog_fs__rename(_backend, old_name, new_name);
1524 if (((error == 0) || (error == GIT_ENOTFOUND)) &&
d578b45f 1525 ((error = reflog_append(backend, new, git_reference_target(new), NULL, who, message)) < 0)) {
a57dd3b7
CMN
1526 git_reference_free(new);
1527 git_filebuf_cleanup(&file);
1528 return error;
1529 }
1530
1531 if (error < 0) {
1532 git_reference_free(new);
1533 git_filebuf_cleanup(&file);
1534 return error;
1535 }
1536
1537
1538 if ((error = loose_commit(&file, new)) < 0 || out == NULL) {
4e6e2ff2 1539 git_reference_free(new);
fe372740 1540 return error;
4e6e2ff2
VM
1541 }
1542
fe372740 1543 *out = new;
4e6e2ff2
VM
1544 return 0;
1545}
1546
d00d5464
ET
1547static int refdb_fs_backend__compress(git_refdb_backend *_backend)
1548{
2e09106e 1549 int error;
22a2d3d5 1550 refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
d00d5464 1551
c25aa7cd 1552 GIT_ASSERT_ARG(backend);
d00d5464 1553
2e09106e
CMN
1554 if ((error = packed_reload(backend)) < 0 || /* load the existing packfile */
1555 (error = packed_loadloose(backend)) < 0 || /* add all the loose refs */
1556 (error = packed_write(backend)) < 0) /* write back to disk */
1557 return error;
d00d5464
ET
1558
1559 return 0;
1560}
1561
d00d5464
ET
1562static void refdb_fs_backend__free(git_refdb_backend *_backend)
1563{
22a2d3d5 1564 refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
d00d5464 1565
c25aa7cd
PP
1566 if (!backend)
1567 return;
d00d5464 1568
fe372740 1569 git_sortedcache_free(backend->refcache);
71dd0861 1570 git__free(backend->gitpath);
e0a6c28e 1571 git__free(backend->commonpath);
d00d5464
ET
1572 git__free(backend);
1573}
1574
83580562 1575static char *setup_namespace(git_repository *repo, const char *in)
8cddf9b8 1576{
83580562
ET
1577 git_buf path = GIT_BUF_INIT;
1578 char *parts, *start, *end, *out = NULL;
8cddf9b8 1579
83580562
ET
1580 if (!in)
1581 goto done;
69a3c766 1582
83580562 1583 git_buf_puts(&path, in);
8cddf9b8
VM
1584
1585 /* if the repo is not namespaced, nothing else to do */
83580562
ET
1586 if (repo->namespace == NULL) {
1587 out = git_buf_detach(&path);
1588 goto done;
1589 }
8cddf9b8
VM
1590
1591 parts = end = git__strdup(repo->namespace);
1592 if (parts == NULL)
83580562 1593 goto done;
8cddf9b8 1594
fe372740 1595 /*
8cddf9b8
VM
1596 * From `man gitnamespaces`:
1597 * namespaces which include a / will expand to a hierarchy
1598 * of namespaces; for example, GIT_NAMESPACE=foo/bar will store
1599 * refs under refs/namespaces/foo/refs/namespaces/bar/
1600 */
83580562
ET
1601 while ((start = git__strsep(&end, "/")) != NULL)
1602 git_buf_printf(&path, "refs/namespaces/%s/", start);
8cddf9b8 1603
83580562 1604 git_buf_printf(&path, "refs/namespaces/%s/refs", end);
1ed356dc 1605 git__free(parts);
8cddf9b8
VM
1606
1607 /* Make sure that the folder with the namespace exists */
83580562
ET
1608 if (git_futils_mkdir_relative(git_buf_cstr(&path), in, 0777,
1609 GIT_MKDIR_PATH, NULL) < 0)
1610 goto done;
8cddf9b8 1611
c25aa7cd 1612 /* Return root of the namespaced gitpath, i.e. without the trailing 'refs' */
83580562 1613 git_buf_rtruncate_at_char(&path, '/');
c25aa7cd 1614 git_buf_putc(&path, '/');
83580562
ET
1615 out = git_buf_detach(&path);
1616
1617done:
ac3d33df 1618 git_buf_dispose(&path);
83580562 1619 return out;
8cddf9b8
VM
1620}
1621
b976f3c2
CMN
1622static int reflog_alloc(git_reflog **reflog, const char *name)
1623{
1624 git_reflog *log;
1625
1626 *reflog = NULL;
1627
1628 log = git__calloc(1, sizeof(git_reflog));
ac3d33df 1629 GIT_ERROR_CHECK_ALLOC(log);
b976f3c2
CMN
1630
1631 log->ref_name = git__strdup(name);
ac3d33df 1632 GIT_ERROR_CHECK_ALLOC(log->ref_name);
b976f3c2
CMN
1633
1634 if (git_vector_init(&log->entries, 0, NULL) < 0) {
1635 git__free(log->ref_name);
1636 git__free(log);
1637 return -1;
1638 }
1639
1640 *reflog = log;
1641
1642 return 0;
1643}
1644
1645static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
1646{
22a2d3d5 1647 git_parse_ctx parser = GIT_PARSE_CTX_INIT;
b976f3c2 1648
22a2d3d5
UG
1649 if ((git_parse_ctx_init(&parser, buf, buf_size)) < 0)
1650 return -1;
b976f3c2 1651
22a2d3d5
UG
1652 for (; parser.remain_len; git_parse_advance_line(&parser)) {
1653 git_reflog_entry *entry;
1654 const char *sig;
1655 char c;
b976f3c2 1656
22a2d3d5
UG
1657 entry = git__calloc(1, sizeof(*entry));
1658 GIT_ERROR_CHECK_ALLOC(entry);
1659 entry->committer = git__calloc(1, sizeof(*entry->committer));
1660 GIT_ERROR_CHECK_ALLOC(entry->committer);
b976f3c2 1661
22a2d3d5
UG
1662 if (git_parse_advance_oid(&entry->oid_old, &parser) < 0 ||
1663 git_parse_advance_expected(&parser, " ", 1) < 0 ||
1664 git_parse_advance_oid(&entry->oid_cur, &parser) < 0)
1665 goto next;
b976f3c2 1666
22a2d3d5
UG
1667 sig = parser.line;
1668 while (git_parse_peek(&c, &parser, 0) == 0 && c != '\t' && c != '\n')
1669 git_parse_advance_chars(&parser, 1);
b976f3c2 1670
22a2d3d5
UG
1671 if (git_signature__parse(entry->committer, &sig, parser.line, NULL, 0) < 0)
1672 goto next;
b976f3c2 1673
22a2d3d5
UG
1674 if (c == '\t') {
1675 size_t len;
1676 git_parse_advance_chars(&parser, 1);
b976f3c2 1677
22a2d3d5
UG
1678 len = parser.line_len;
1679 if (parser.line[len - 1] == '\n')
1680 len--;
b976f3c2 1681
22a2d3d5 1682 entry->msg = git__strndup(parser.line, len);
ac3d33df 1683 GIT_ERROR_CHECK_ALLOC(entry->msg);
22a2d3d5 1684 }
b976f3c2 1685
22a2d3d5
UG
1686 if ((git_vector_insert(&log->entries, entry)) < 0) {
1687 git_reflog_entry__free(entry);
1688 return -1;
1689 }
b976f3c2 1690
22a2d3d5
UG
1691 continue;
1692
1693next:
1694 git_reflog_entry__free(entry);
b976f3c2
CMN
1695 }
1696
1697 return 0;
b976f3c2
CMN
1698}
1699
1700static int create_new_reflog_file(const char *filepath)
1701{
1702 int fd, error;
1703
1704 if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0)
1705 return error;
1706
1707 if ((fd = p_open(filepath,
8d5ec910 1708 O_WRONLY | O_CREAT,
b976f3c2
CMN
1709 GIT_REFLOG_FILE_MODE)) < 0)
1710 return -1;
1711
1712 return p_close(fd);
1713}
1714
8d5ec910
CMN
1715static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name)
1716{
1717 refdb_fs_backend *backend;
1718 git_repository *repo;
1719 git_buf path = GIT_BUF_INIT;
1720 int error;
1721
c25aa7cd 1722 GIT_ASSERT_ARG(_backend && name);
8d5ec910 1723
22a2d3d5 1724 backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
8d5ec910
CMN
1725 repo = backend->repo;
1726
c25aa7cd 1727 if ((error = reflog_path(&path, repo, name)) < 0)
8d5ec910
CMN
1728 return error;
1729
ae32c54e 1730 error = create_new_reflog_file(git_buf_cstr(&path));
ac3d33df 1731 git_buf_dispose(&path);
ae32c54e
CMN
1732
1733 return error;
8d5ec910
CMN
1734}
1735
6f13a305
CMN
1736static int has_reflog(git_repository *repo, const char *name)
1737{
1738 int ret = 0;
1739 git_buf path = GIT_BUF_INIT;
1740
c25aa7cd 1741 if (reflog_path(&path, repo, name) < 0)
6f13a305
CMN
1742 goto cleanup;
1743
1744 ret = git_path_isfile(git_buf_cstr(&path));
1745
1746cleanup:
ac3d33df 1747 git_buf_dispose(&path);
6f13a305
CMN
1748 return ret;
1749}
1750
f2105129
CMN
1751static int refdb_reflog_fs__has_log(git_refdb_backend *_backend, const char *name)
1752{
1753 refdb_fs_backend *backend;
1754
c25aa7cd
PP
1755 GIT_ASSERT_ARG(_backend);
1756 GIT_ASSERT_ARG(name);
f2105129 1757
22a2d3d5 1758 backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
f2105129
CMN
1759
1760 return has_reflog(backend->repo, name);
1761}
1762
b976f3c2
CMN
1763static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend, const char *name)
1764{
1765 int error = -1;
1766 git_buf log_path = GIT_BUF_INIT;
1767 git_buf log_file = GIT_BUF_INIT;
1768 git_reflog *log = NULL;
1769 git_repository *repo;
1770 refdb_fs_backend *backend;
1771
c25aa7cd
PP
1772 GIT_ASSERT_ARG(out);
1773 GIT_ASSERT_ARG(_backend);
1774 GIT_ASSERT_ARG(name);
b976f3c2 1775
22a2d3d5 1776 backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
b976f3c2
CMN
1777 repo = backend->repo;
1778
1779 if (reflog_alloc(&log, name) < 0)
1780 return -1;
1781
c25aa7cd 1782 if (reflog_path(&log_path, repo, name) < 0)
b976f3c2
CMN
1783 goto cleanup;
1784
1785 error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path));
1786 if (error < 0 && error != GIT_ENOTFOUND)
1787 goto cleanup;
1788
1789 if ((error == GIT_ENOTFOUND) &&
1790 ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0))
1791 goto cleanup;
ac3d33df 1792
b976f3c2
CMN
1793 if ((error = reflog_parse(log,
1794 git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0)
1795 goto cleanup;
1796
1797 *out = log;
1798 goto success;
1799
1800cleanup:
1801 git_reflog_free(log);
1802
1803success:
ac3d33df
JK
1804 git_buf_dispose(&log_file);
1805 git_buf_dispose(&log_path);
b976f3c2
CMN
1806
1807 return error;
1808}
1809
1810static int serialize_reflog_entry(
1811 git_buf *buf,
1812 const git_oid *oid_old,
1813 const git_oid *oid_new,
1814 const git_signature *committer,
1815 const char *msg)
1816{
1817 char raw_old[GIT_OID_HEXSZ+1];
1818 char raw_new[GIT_OID_HEXSZ+1];
1819
1820 git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old);
1821 git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new);
1822
1823 git_buf_clear(buf);
1824
1825 git_buf_puts(buf, raw_old);
1826 git_buf_putc(buf, ' ');
1827 git_buf_puts(buf, raw_new);
1828
1829 git_signature__writebuf(buf, " ", committer);
1830
1831 /* drop trailing LF */
1832 git_buf_rtrim(buf);
1833
1834 if (msg) {
22a2d3d5
UG
1835 size_t i;
1836
b976f3c2
CMN
1837 git_buf_putc(buf, '\t');
1838 git_buf_puts(buf, msg);
22a2d3d5
UG
1839
1840 for (i = 0; i < buf->size - 2; i++)
1841 if (buf->ptr[i] == '\n')
1842 buf->ptr[i] = ' ';
1843 git_buf_rtrim(buf);
b976f3c2
CMN
1844 }
1845
1846 git_buf_putc(buf, '\n');
1847
1848 return git_buf_oom(buf);
1849}
1850
110df893
CMN
1851static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char *refname)
1852{
1853 git_repository *repo;
1854 git_buf log_path = GIT_BUF_INIT;
1855 int error;
1856
1857 repo = backend->repo;
1858
c25aa7cd 1859 if (!git_path_validate(backend->repo, refname, 0, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
ac3d33df 1860 git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", refname);
a64119e3
ET
1861 return GIT_EINVALIDSPEC;
1862 }
1863
c25aa7cd 1864 if (reflog_path(&log_path, repo, refname) < 0)
110df893
CMN
1865 return -1;
1866
1867 if (!git_path_isfile(git_buf_cstr(&log_path))) {
ac3d33df 1868 git_error_set(GIT_ERROR_INVALID,
909d5494 1869 "log file for reference '%s' doesn't exist", refname);
110df893
CMN
1870 error = -1;
1871 goto cleanup;
1872 }
1873
1874 error = git_filebuf_open(file, git_buf_cstr(&log_path), 0, GIT_REFLOG_FILE_MODE);
1875
1876cleanup:
ac3d33df 1877 git_buf_dispose(&log_path);
110df893
CMN
1878
1879 return error;
1880}
1881
b976f3c2
CMN
1882static int refdb_reflog_fs__write(git_refdb_backend *_backend, git_reflog *reflog)
1883{
1884 int error = -1;
1885 unsigned int i;
1886 git_reflog_entry *entry;
b976f3c2 1887 refdb_fs_backend *backend;
b976f3c2
CMN
1888 git_buf log = GIT_BUF_INIT;
1889 git_filebuf fbuf = GIT_FILEBUF_INIT;
1890
c25aa7cd
PP
1891 GIT_ASSERT_ARG(_backend);
1892 GIT_ASSERT_ARG(reflog);
b976f3c2 1893
22a2d3d5 1894 backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
b976f3c2 1895
110df893 1896 if ((error = lock_reflog(&fbuf, backend, reflog->ref_name)) < 0)
b976f3c2
CMN
1897 return -1;
1898
b976f3c2
CMN
1899 git_vector_foreach(&reflog->entries, i, entry) {
1900 if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0)
1901 goto cleanup;
1902
1903 if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0)
1904 goto cleanup;
1905 }
1906
1d3a8aeb 1907 error = git_filebuf_commit(&fbuf);
b976f3c2
CMN
1908 goto success;
1909
1910cleanup:
1911 git_filebuf_cleanup(&fbuf);
1912
1913success:
ac3d33df 1914 git_buf_dispose(&log);
110df893 1915
b976f3c2
CMN
1916 return error;
1917}
1918
a57dd3b7 1919/* Append to the reflog, must be called under reference lock */
4b7e1b9e 1920static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *who, const char *message)
a57dd3b7 1921{
af3dcb0e 1922 int error, is_symbolic, open_flags;
4b7e1b9e 1923 git_oid old_id = {{0}}, new_id = {{0}};
a57dd3b7
CMN
1924 git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
1925 git_repository *repo = backend->repo;
1926
ac3d33df 1927 is_symbolic = ref->type == GIT_REFERENCE_SYMBOLIC;
4b7e1b9e
CMN
1928
1929 /* "normal" symbolic updates do not write */
1930 if (is_symbolic &&
1931 strcmp(ref->name, GIT_HEAD_FILE) &&
1932 !(old && new))
a57dd3b7
CMN
1933 return 0;
1934
22a2d3d5 1935 /* From here on is_symbolic also means that it's HEAD */
4b7e1b9e
CMN
1936
1937 if (old) {
1938 git_oid_cpy(&old_id, old);
afc57eb4 1939 } else {
4b7e1b9e 1940 error = git_reference_name_to_id(&old_id, repo, ref->name);
afc57eb4 1941 if (error < 0 && error != GIT_ENOTFOUND)
4b7e1b9e
CMN
1942 return error;
1943 }
1944
6aaae94a
CMN
1945 if (new) {
1946 git_oid_cpy(&new_id, new);
1947 } else {
1948 if (!is_symbolic) {
1949 git_oid_cpy(&new_id, git_reference_target(ref));
1950 } else {
1951 error = git_reference_name_to_id(&new_id, repo, git_reference_symbolic_target(ref));
1952 if (error < 0 && error != GIT_ENOTFOUND)
1953 return error;
1954 /* detaching HEAD does not create an entry */
1955 if (error == GIT_ENOTFOUND)
1956 return 0;
4b7e1b9e 1957
ac3d33df 1958 git_error_clear();
6aaae94a 1959 }
7f058b86 1960 }
4b7e1b9e 1961
a57dd3b7
CMN
1962 if ((error = serialize_reflog_entry(&buf, &old_id, &new_id, who, message)) < 0)
1963 goto cleanup;
1964
c25aa7cd 1965 if ((error = reflog_path(&path, repo, ref->name)) < 0)
a57dd3b7
CMN
1966 goto cleanup;
1967
1968 if (((error = git_futils_mkpath2file(git_buf_cstr(&path), 0777)) < 0) &&
1969 (error != GIT_EEXISTS)) {
1970 goto cleanup;
1971 }
1972
1589a93a
JH
1973 /* If the new branch matches part of the namespace of a previously deleted branch,
1974 * there maybe an obsolete/unused directory (or directory hierarchy) in the way.
1975 */
0a700ee3 1976 if (git_path_isdir(git_buf_cstr(&path))) {
ce5553d4
CMN
1977 if ((error = git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY)) < 0) {
1978 if (error == GIT_ENOTFOUND)
1979 error = 0;
1980 } else if (git_path_isdir(git_buf_cstr(&path))) {
ac3d33df 1981 git_error_set(GIT_ERROR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder",
0a700ee3
ET
1982 ref->name);
1983 error = GIT_EDIRECTORY;
1984 }
1985
1986 if (error != 0)
1987 goto cleanup;
1589a93a
JH
1988 }
1989
af3dcb0e
ET
1990 open_flags = O_WRONLY | O_CREAT | O_APPEND;
1991
1c04a96b 1992 if (backend->fsync)
af3dcb0e
ET
1993 open_flags |= O_FSYNC;
1994
1995 error = git_futils_writebuffer(&buf, git_buf_cstr(&path), open_flags, GIT_REFLOG_FILE_MODE);
a57dd3b7
CMN
1996
1997cleanup:
ac3d33df
JK
1998 git_buf_dispose(&buf);
1999 git_buf_dispose(&path);
a57dd3b7 2000
b976f3c2
CMN
2001 return error;
2002}
2003
b976f3c2
CMN
2004static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name)
2005{
2006 int error = 0, fd;
2007 git_buf old_path = GIT_BUF_INIT;
2008 git_buf new_path = GIT_BUF_INIT;
2009 git_buf temp_path = GIT_BUF_INIT;
2010 git_buf normalized = GIT_BUF_INIT;
2011 git_repository *repo;
2012 refdb_fs_backend *backend;
2013
c25aa7cd
PP
2014 GIT_ASSERT_ARG(_backend);
2015 GIT_ASSERT_ARG(old_name);
2016 GIT_ASSERT_ARG(new_name);
b976f3c2 2017
22a2d3d5 2018 backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
b976f3c2
CMN
2019 repo = backend->repo;
2020
2021 if ((error = git_reference__normalize_name(
ac3d33df 2022 &normalized, new_name, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL)) < 0)
b976f3c2
CMN
2023 return error;
2024
84f56cb0 2025 if (git_buf_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0)
b976f3c2
CMN
2026 return -1;
2027
c25aa7cd
PP
2028 if ((error = loose_path(&old_path, git_buf_cstr(&temp_path), old_name)) < 0)
2029 return error;
b976f3c2 2030
c25aa7cd
PP
2031 if ((error = loose_path(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized))) < 0)
2032 return error;
b976f3c2 2033
a57dd3b7
CMN
2034 if (!git_path_exists(git_buf_cstr(&old_path))) {
2035 error = GIT_ENOTFOUND;
2036 goto cleanup;
2037 }
2038
b976f3c2
CMN
2039 /*
2040 * Move the reflog to a temporary place. This two-phase renaming is required
2041 * in order to cope with funny renaming use cases when one tries to move a reference
2042 * to a partially colliding namespace:
2043 * - a/b -> a/b/c
2044 * - a/b/c/d -> a/b/c
2045 */
c25aa7cd
PP
2046 if ((error = loose_path(&temp_path, git_buf_cstr(&temp_path), "temp_reflog")) < 0)
2047 return error;
b976f3c2 2048
1d3a8aeb 2049 if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) {
b976f3c2
CMN
2050 error = -1;
2051 goto cleanup;
2052 }
2053
2054 p_close(fd);
2055
2056 if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) {
ac3d33df 2057 git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name);
b976f3c2
CMN
2058 error = -1;
2059 goto cleanup;
2060 }
2061
ac3d33df 2062 if (git_path_isdir(git_buf_cstr(&new_path)) &&
b976f3c2
CMN
2063 (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) {
2064 error = -1;
2065 goto cleanup;
2066 }
2067
2068 if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) {
2069 error = -1;
2070 goto cleanup;
2071 }
2072
2073 if (p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path)) < 0) {
ac3d33df 2074 git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name);
b976f3c2
CMN
2075 error = -1;
2076 }
2077
2078cleanup:
ac3d33df
JK
2079 git_buf_dispose(&temp_path);
2080 git_buf_dispose(&old_path);
2081 git_buf_dispose(&new_path);
2082 git_buf_dispose(&normalized);
b976f3c2
CMN
2083
2084 return error;
2085}
2086
b976f3c2
CMN
2087static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name)
2088{
22a2d3d5 2089 refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
b976f3c2 2090 git_buf path = GIT_BUF_INIT;
ac3d33df 2091 int error;
b976f3c2 2092
c25aa7cd
PP
2093 GIT_ASSERT_ARG(_backend);
2094 GIT_ASSERT_ARG(name);
b976f3c2 2095
c25aa7cd 2096 if ((error = reflog_path(&path, backend->repo, name)) < 0)
ac3d33df
JK
2097 goto out;
2098
2099 if (!git_path_exists(path.ptr))
2100 goto out;
b976f3c2 2101
ac3d33df
JK
2102 if ((error = p_unlink(path.ptr)) < 0)
2103 goto out;
b976f3c2 2104
c25aa7cd 2105 error = refdb_fs_backend__prune_refs(backend, name, GIT_REFLOG_DIR);
b976f3c2 2106
ac3d33df
JK
2107out:
2108 git_buf_dispose(&path);
b976f3c2
CMN
2109
2110 return error;
b976f3c2
CMN
2111}
2112
d00d5464
ET
2113int git_refdb_backend_fs(
2114 git_refdb_backend **backend_out,
4e4eab52 2115 git_repository *repository)
d00d5464 2116{
219d3457 2117 int t = 0;
71dd0861 2118 git_buf gitpath = GIT_BUF_INIT;
d00d5464
ET
2119 refdb_fs_backend *backend;
2120
2121 backend = git__calloc(1, sizeof(refdb_fs_backend));
ac3d33df 2122 GIT_ERROR_CHECK_ALLOC(backend);
d00d5464 2123
22a2d3d5
UG
2124 if (git_refdb_init_backend(&backend->parent, GIT_REFDB_BACKEND_VERSION) < 0)
2125 goto fail;
2126
d00d5464 2127 backend->repo = repository;
bade5194 2128
83580562
ET
2129 if (repository->gitdir) {
2130 backend->gitpath = setup_namespace(repository, repository->gitdir);
bade5194 2131
83580562
ET
2132 if (backend->gitpath == NULL)
2133 goto fail;
2134 }
2135
2136 if (repository->commondir) {
2137 backend->commonpath = setup_namespace(repository, repository->commondir);
2138
2139 if (backend->commonpath == NULL)
2140 goto fail;
2141 }
d00d5464 2142
e0a6c28e 2143 if (git_buf_joinpath(&gitpath, backend->commonpath, GIT_PACKEDREFS_FILE) < 0 ||
fe372740
RB
2144 git_sortedcache_new(
2145 &backend->refcache, offsetof(struct packref, name),
71dd0861 2146 NULL, NULL, packref_cmp, git_buf_cstr(&gitpath)) < 0)
fe372740
RB
2147 goto fail;
2148
ac3d33df 2149 git_buf_dispose(&gitpath);
fe372740 2150
22a2d3d5 2151 if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_IGNORECASE) && t) {
219d3457
RB
2152 backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE;
2153 backend->direach_flags |= GIT_PATH_DIR_IGNORE_CASE;
2154 }
22a2d3d5 2155 if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_PRECOMPOSE) && t) {
219d3457
RB
2156 backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
2157 backend->direach_flags |= GIT_PATH_DIR_PRECOMPOSE_UNICODE;
2158 }
22a2d3d5 2159 if ((!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_FSYNCOBJECTFILES) && t) ||
6c23704d 2160 git_repository__fsync_gitdir)
1c04a96b 2161 backend->fsync = 1;
eae0bfdc 2162 backend->iterator_flags |= GIT_ITERATOR_DESCEND_SYMLINKS;
219d3457 2163
d00d5464
ET
2164 backend->parent.exists = &refdb_fs_backend__exists;
2165 backend->parent.lookup = &refdb_fs_backend__lookup;
4def7035 2166 backend->parent.iterator = &refdb_fs_backend__iterator;
d00d5464 2167 backend->parent.write = &refdb_fs_backend__write;
e3f3868a 2168 backend->parent.del = &refdb_fs_backend__delete;
4e6e2ff2 2169 backend->parent.rename = &refdb_fs_backend__rename;
d00d5464 2170 backend->parent.compress = &refdb_fs_backend__compress;
ab8d9242
CMN
2171 backend->parent.lock = &refdb_fs_backend__lock;
2172 backend->parent.unlock = &refdb_fs_backend__unlock;
f2105129 2173 backend->parent.has_log = &refdb_reflog_fs__has_log;
8d5ec910 2174 backend->parent.ensure_log = &refdb_reflog_fs__ensure_log;
d00d5464 2175 backend->parent.free = &refdb_fs_backend__free;
b976f3c2
CMN
2176 backend->parent.reflog_read = &refdb_reflog_fs__read;
2177 backend->parent.reflog_write = &refdb_reflog_fs__write;
b976f3c2 2178 backend->parent.reflog_rename = &refdb_reflog_fs__rename;
b976f3c2 2179 backend->parent.reflog_delete = &refdb_reflog_fs__delete;
d00d5464
ET
2180
2181 *backend_out = (git_refdb_backend *)backend;
2182 return 0;
fe372740
RB
2183
2184fail:
ac3d33df 2185 git_buf_dispose(&gitpath);
71dd0861 2186 git__free(backend->gitpath);
e0a6c28e 2187 git__free(backend->commonpath);
fe372740
RB
2188 git__free(backend);
2189 return -1;
d00d5464 2190}