refdb_fs.c 54.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

#include "refs.h"
#include "hash.h"
#include "repository.h"
11
#include "futils.h"
12
#include "filebuf.h"
13
#include "pack.h"
14
#include "parse.h"
15 16
#include "reflog.h"
#include "refdb.h"
17
#include "iterator.h"
18
#include "sortedcache.h"
19
#include "signature.h"
20
#include "wildmatch.h"
21 22 23 24

#include <git2/tag.h>
#include <git2/object.h>
#include <git2/refdb.h>
25
#include <git2/branch.h>
26
#include <git2/sys/refdb_backend.h>
27
#include <git2/sys/refs.h>
28
#include <git2/sys/reflog.h>
29 30 31 32 33

#define DEFAULT_NESTING_LEVEL	5
#define MAX_NESTING_LEVEL		10

enum {
34 35
	PACKREF_HAS_PEEL = 1,
	PACKREF_WAS_LOOSE = 2,
36 37
	PACKREF_CANNOT_PEEL = 4,
	PACKREF_SHADOWED = 8,
38 39 40 41 42 43
};

enum {
	PEELING_NONE = 0,
	PEELING_STANDARD,
	PEELING_FULL
44 45 46 47 48 49 50 51 52 53 54 55 56
};

struct packref {
	git_oid oid;
	git_oid peel;
	char flags;
	char name[GIT_FLEX_ARRAY];
};

typedef struct refdb_fs_backend {
	git_refdb_backend parent;

	git_repository *repo;
57 58
	/* path to git directory */
	char *gitpath;
59 60
	/* path to common objects' directory */
	char *commonpath;
61

62
	git_sortedcache *refcache;
63
	int peeling_mode;
64 65
	git_iterator_flag_t iterator_flags;
	uint32_t direach_flags;
66
	int fsync;
67 68
} refdb_fs_backend;

69 70
static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name);

71
static int packref_cmp(const void *a_, const void *b_)
72
{
73 74
	const struct packref *a = a_, *b = b_;
	return strcmp(a->name, b->name);
75 76
}

77
static int packed_reload(refdb_fs_backend *backend)
78
{
79 80 81
	int error;
	git_buf packedrefs = GIT_BUF_INIT;
	char *scan, *eof, *eol;
82

83
	if (!backend->gitpath)
84 85
		return 0;

86
	error = git_sortedcache_lockandload(backend->refcache, &packedrefs);
87 88

	/*
89 90 91 92
	 * If we can't find the packed-refs, clear table and return.
	 * Any other error just gets passed through.
	 * If no error, and file wasn't changed, just return.
	 * Anything else means we need to refresh the packed refs.
93
	 */
94 95 96
	if (error <= 0) {
		if (error == GIT_ENOTFOUND) {
			git_sortedcache_clear(backend->refcache, true);
97
			git_error_clear();
98 99 100
			error = 0;
		}
		return error;
101 102
	}

103
	/* At this point, refresh the packed refs from the loaded buffer. */
104

105
	git_sortedcache_clear(backend->refcache, false);
106

107 108
	scan = (char *)packedrefs.ptr;
	eof  = scan + packedrefs.size;
109

110 111
	backend->peeling_mode = PEELING_NONE;

112
	if (*scan == '#') {
nulltoken committed
113
		static const char *traits_header = "# pack-refs with: ";
114

115 116 117
		if (git__prefixcmp(scan, traits_header) == 0) {
			scan += strlen(traits_header);
			eol = strchr(scan, '\n');
118

119
			if (!eol)
120
				goto parse_failed;
121
			*eol = '\0';
122

123
			if (strstr(scan, " fully-peeled ") != NULL) {
124
				backend->peeling_mode = PEELING_FULL;
125
			} else if (strstr(scan, " peeled ") != NULL) {
126 127 128
				backend->peeling_mode = PEELING_STANDARD;
			}

129
			scan = eol + 1;
130 131 132
		}
	}

133 134
	while (scan < eof && *scan == '#') {
		if (!(eol = strchr(scan, '\n')))
135
			goto parse_failed;
136
		scan = eol + 1;
137 138
	}

139 140 141 142 143
	while (scan < eof) {
		struct packref *ref;
		git_oid oid;

		/* parse "<OID> <refname>\n" */
144

145
		if (git_oid_fromstr(&oid, scan) < 0)
146
			goto parse_failed;
147
		scan += GIT_OID_HEXSZ;
148

149 150 151 152 153 154 155
		if (*scan++ != ' ')
			goto parse_failed;
		if (!(eol = strchr(scan, '\n')))
			goto parse_failed;
		*eol = '\0';
		if (eol[-1] == '\r')
			eol[-1] = '\0';
156

157
		if (git_sortedcache_upsert((void **)&ref, backend->refcache, scan) < 0)
158
			goto parse_failed;
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
		scan = eol + 1;

		git_oid_cpy(&ref->oid, &oid);

		/* look for optional "^<OID>\n" */

		if (*scan == '^') {
			if (git_oid_fromstr(&oid, scan + 1) < 0)
				goto parse_failed;
			scan += GIT_OID_HEXSZ + 1;

			if (scan < eof) {
				if (!(eol = strchr(scan, '\n')))
					goto parse_failed;
				scan = eol + 1;
			}

			git_oid_cpy(&ref->peel, &oid);
			ref->flags |= PACKREF_HAS_PEEL;
		}
		else if (backend->peeling_mode == PEELING_FULL ||
				(backend->peeling_mode == PEELING_STANDARD &&
				 git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0))
			ref->flags |= PACKREF_CANNOT_PEEL;
183 184
	}

185
	git_sortedcache_wunlock(backend->refcache);
186
	git_buf_dispose(&packedrefs);
187

188 189 190
	return 0;

parse_failed:
191
	git_error_set(GIT_ERROR_REFERENCE, "corrupted packed references file");
192 193

	git_sortedcache_clear(backend->refcache, false);
194
	git_sortedcache_wunlock(backend->refcache);
195
	git_buf_dispose(&packedrefs);
196

197 198 199
	return -1;
}

200 201
static int loose_parse_oid(
	git_oid *oid, const char *filename, git_buf *file_content)
202
{
203
	const char *str = git_buf_cstr(file_content);
204

205
	if (git_buf_len(file_content) < GIT_OID_HEXSZ)
206 207 208
		goto corrupted;

	/* we need to get 40 OID characters from the file */
209
	if (git_oid_fromstr(oid, str) < 0)
210 211 212 213 214 215 216 217
		goto corrupted;

	/* If the file is longer than 40 chars, the 41st must be a space */
	str += GIT_OID_HEXSZ;
	if (*str == '\0' || git__isspace(*str))
		return 0;

corrupted:
218
	git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file: %s", filename);
219 220 221
	return -1;
}

222 223 224 225 226 227 228
static int loose_readbuffer(git_buf *buf, const char *base, const char *path)
{
	int error;

	/* build full path to file */
	if ((error = git_buf_joinpath(buf, base, path)) < 0 ||
		(error = git_futils_readbuffer(buf, buf->ptr)) < 0)
229
		git_buf_dispose(buf);
230 231 232 233 234

	return error;
}

static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name)
235
{
236
	int error = 0;
237 238
	git_buf ref_file = GIT_BUF_INIT;
	struct packref *ref = NULL;
239
	git_oid oid;
240

241 242 243
	/* if we fail to load the loose reference, assume someone changed
	 * the filesystem under us and skip it...
	 */
244
	if (loose_readbuffer(&ref_file, backend->gitpath, name) < 0) {
245
		git_error_clear();
246 247
		goto done;
	}
248

249
	/* skip symbolic refs */
250 251
	if (!git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF))
		goto done;
252

253 254 255
	/* parse OID from file */
	if ((error = loose_parse_oid(&oid, name, &ref_file)) < 0)
		goto done;
256

257
	git_sortedcache_wlock(backend->refcache);
258

259 260
	if (!(error = git_sortedcache_upsert(
			(void **)&ref, backend->refcache, name))) {
261

262 263
		git_oid_cpy(&ref->oid, &oid);
		ref->flags = PACKREF_WAS_LOOSE;
264 265
	}

266
	git_sortedcache_wunlock(backend->refcache);
267

268
done:
269
	git_buf_dispose(&ref_file);
270
	return error;
271 272
}

273
static int _dirent_loose_load(void *payload, git_buf *full_path)
274
{
275
	refdb_fs_backend *backend = payload;
276 277
	const char *file_path;

278 279 280
	if (git__suffixcmp(full_path->ptr, ".lock") == 0)
		return 0;

281 282
	if (git_path_isdir(full_path->ptr)) {
		int error = git_path_direach(
283
			full_path, backend->direach_flags, _dirent_loose_load, backend);
284 285
		/* Race with the filesystem, ignore it */
		if (error == GIT_ENOTFOUND) {
286
			git_error_clear();
287 288 289 290 291
			return 0;
		}

		return error;
	}
292

293
	file_path = full_path->ptr + strlen(backend->gitpath);
294

295
	return loose_lookup_to_packfile(backend, file_path);
296 297 298 299 300 301 302 303 304 305
}

/*
 * Load all the loose references from the repository
 * into the in-memory Packfile, and build a vector with
 * all the references so it can be written back to
 * disk.
 */
static int packed_loadloose(refdb_fs_backend *backend)
{
306
	int error;
307 308
	git_buf refs_path = GIT_BUF_INIT;

309
	if (git_buf_joinpath(&refs_path, backend->gitpath, GIT_REFS_DIR) < 0)
310 311 312 313 314 315 316
		return -1;

	/*
	 * Load all the loose files from disk into the Packfile table.
	 * This will overwrite any old packed entries with their
	 * updated loose versions
	 */
317
	error = git_path_direach(
318
		&refs_path, backend->direach_flags, _dirent_loose_load, backend);
319

320
	git_buf_dispose(&refs_path);
321

322
	return error;
323 324 325 326 327 328 329
}

static int refdb_fs_backend__exists(
	int *exists,
	git_refdb_backend *_backend,
	const char *ref_name)
{
330
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
331
	git_buf ref_path = GIT_BUF_INIT;
332
	int error;
333

334
	assert(backend);
335

336 337 338 339 340 341 342 343 344
	*exists = 0;

	if ((error = git_buf_joinpath(&ref_path, backend->gitpath, ref_name)) < 0)
		goto out;

	if (git_path_isfile(ref_path.ptr)) {
		*exists = 1;
		goto out;
	}
345

346 347
	if ((error = packed_reload(backend)) < 0)
		goto out;
348

349 350 351 352 353 354
	if (git_sortedcache_lookup(backend->refcache, ref_name) != NULL) {
		*exists = 1;
		goto out;
	}

out:
355
	git_buf_dispose(&ref_path);
356
	return error;
357 358 359 360 361 362 363 364 365 366
}

static const char *loose_parse_symbolic(git_buf *file_content)
{
	const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);
	const char *refname_start;

	refname_start = (const char *)file_content->ptr;

	if (git_buf_len(file_content) < header_len + 1) {
367
		git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file");
368 369 370 371 372 373 374 375 376 377 378 379
		return NULL;
	}

	/*
	 * Assume we have already checked for the header
	 * before calling this function
	 */
	refname_start += header_len;

	return refname_start;
}

380 381 382 383 384 385 386
/*
 * Returns whether a reference is stored per worktree or not.
 * Per-worktree references are:
 *
 * - all pseudorefs, e.g. HEAD and MERGE_HEAD
 * - all references stored inside of "refs/bisect/"
 */
387 388
static bool is_per_worktree_ref(const char *ref_name)
{
389 390
	return git__prefixcmp(ref_name, "refs/") != 0 ||
	    git__prefixcmp(ref_name, "refs/bisect/") == 0;
391 392
}

393 394 395 396 397 398 399
static int loose_lookup(
	git_reference **out,
	refdb_fs_backend *backend,
	const char *ref_name)
{
	git_buf ref_file = GIT_BUF_INIT;
	int error = 0;
400
	const char *ref_dir;
401

402 403 404
	if (out)
		*out = NULL;

405 406 407 408 409 410
	if (is_per_worktree_ref(ref_name))
		ref_dir = backend->gitpath;
	else
		ref_dir = backend->commonpath;

	if ((error = loose_readbuffer(&ref_file, ref_dir, ref_name)) < 0)
411 412 413
		/* cannot read loose ref file - gah */;
	else if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) {
		const char *target;
414 415 416

		git_buf_rtrim(&ref_file);

417
		if (!(target = loose_parse_symbolic(&ref_file)))
418
			error = -1;
419
		else if (out != NULL)
420
			*out = git_reference__alloc_symbolic(ref_name, target);
421
	} else {
422
		git_oid oid;
423

424 425
		if (!(error = loose_parse_oid(&oid, ref_name, &ref_file)) &&
			out != NULL)
426
			*out = git_reference__alloc(ref_name, &oid, NULL);
427 428
	}

429
	git_buf_dispose(&ref_file);
430 431 432
	return error;
}

433
static int ref_error_notfound(const char *name)
434
{
435
	git_error_set(GIT_ERROR_REFERENCE, "reference '%s' not found", name);
436
	return GIT_ENOTFOUND;
437 438 439 440 441 442 443 444
}

static int packed_lookup(
	git_reference **out,
	refdb_fs_backend *backend,
	const char *ref_name)
{
	int error = 0;
445
	struct packref *entry;
446

447 448
	if ((error = packed_reload(backend)) < 0)
		return error;
449

450 451
	if (git_sortedcache_rlock(backend->refcache) < 0)
		return -1;
452 453 454 455 456 457 458 459 460 461

	entry = git_sortedcache_lookup(backend->refcache, ref_name);
	if (!entry) {
		error = ref_error_notfound(ref_name);
	} else {
		*out = git_reference__alloc(ref_name, &entry->oid, &entry->peel);
		if (!*out)
			error = -1;
	}

462 463
	git_sortedcache_runlock(backend->refcache);

464
	return error;
465 466 467 468 469 470 471
}

static int refdb_fs_backend__lookup(
	git_reference **out,
	git_refdb_backend *_backend,
	const char *ref_name)
{
472
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
473
	int error;
474

475
	assert(backend);
476

477
	if (!(error = loose_lookup(out, backend, ref_name)))
478 479 480 481
		return 0;

	/* only try to lookup this reference on the packfile if it
	 * wasn't found on the loose refs; not if there was a critical error */
482
	if (error == GIT_ENOTFOUND) {
483
		git_error_clear();
484
		error = packed_lookup(out, backend, ref_name);
485 486
	}

487
	return error;
488 489
}

490 491
typedef struct {
	git_reference_iterator parent;
492

493
	char *glob;
494 495

	git_pool pool;
496
	git_vector loose;
497

498
	git_sortedcache *cache;
499 500
	size_t loose_pos;
	size_t packed_pos;
501 502 503
} refdb_fs_iter;

static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter)
504
{
505
	refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent);
506 507

	git_vector_free(&iter->loose);
508
	git_pool_clear(&iter->pool);
509
	git_sortedcache_free(iter->cache);
510 511
	git__free(iter);
}
512

513
static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter)
514
{
515
	int error = 0;
516
	git_buf path = GIT_BUF_INIT;
517
	git_iterator *fsit = NULL;
518
	git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT;
519
	const git_index_entry *entry = NULL;
520 521
	const char *ref_prefix = GIT_REFS_DIR;
	size_t ref_prefix_len = strlen(ref_prefix);
522

523
	if (!backend->commonpath) /* do nothing if no commonpath for loose refs */
524 525
		return 0;

526 527
	fsit_opts.flags = backend->iterator_flags;

528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
	if (iter->glob) {
		const char *last_sep = NULL;
		const char *pos;
		for (pos = iter->glob; *pos; ++pos) {
			switch (*pos) {
			case '?':
			case '*':
			case '[':
			case '\\':
				break;
			case '/':
				last_sep = pos;
				/* FALLTHROUGH */
			default:
				continue;
			}
			break;
		}
		if (last_sep) {
			ref_prefix = iter->glob;
			ref_prefix_len = (last_sep - ref_prefix) + 1;
		}
	}

552
	if ((error = git_buf_printf(&path, "%s/", backend->commonpath)) < 0 ||
553
		(error = git_buf_put(&path, ref_prefix, ref_prefix_len)) < 0) {
554
		git_buf_dispose(&path);
555 556
		return error;
	}
557

558
	if ((error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) {
559
		git_buf_dispose(&path);
560 561 562
		return (iter->glob && error == GIT_ENOTFOUND)? 0 : error;
	}

563
	error = git_buf_sets(&path, ref_prefix);
564

565
	while (!error && !git_iterator_advance(&entry, fsit)) {
566
		const char *ref_name;
567
		char *ref_dup;
568

569
		git_buf_truncate(&path, ref_prefix_len);
570 571
		git_buf_puts(&path, entry->path);
		ref_name = git_buf_cstr(&path);
572

573
		if (git__suffixcmp(ref_name, ".lock") == 0 ||
574
			(iter->glob && wildmatch(iter->glob, ref_name, 0) != 0))
575
			continue;
576

577 578
		ref_dup = git_pool_strdup(&iter->pool, ref_name);
		if (!ref_dup)
579
			error = -1;
580 581
		else
			error = git_vector_insert(&iter->loose, ref_dup);
582
	}
583

584
	git_iterator_free(fsit);
585
	git_buf_dispose(&path);
586

587
	return error;
588 589
}

590 591
static int refdb_fs_backend__iterator_next(
	git_reference **out, git_reference_iterator *_iter)
592
{
593
	int error = GIT_ITEROVER;
594 595
	refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent);
	refdb_fs_backend *backend = GIT_CONTAINER_OF(iter->parent.db->backend, refdb_fs_backend, parent);
596
	struct packref *ref;
597

Vicent Marti committed
598
	while (iter->loose_pos < iter->loose.length) {
599
		const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
Vicent Marti committed
600

601 602 603 604 605
		if (loose_lookup(out, backend, path) == 0) {
			ref = git_sortedcache_lookup(iter->cache, path);
			if (ref)
				ref->flags |= PACKREF_SHADOWED;

Vicent Marti committed
606
			return 0;
607
		}
Vicent Marti committed
608

609
		git_error_clear();
610
	}
611

612 613 614
	error = GIT_ITEROVER;
	while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) {
		ref = git_sortedcache_entry(iter->cache, iter->packed_pos++);
615 616
		if (!ref) /* stop now if another thread deleted refs and we past end */
			break;
617 618 619

		if (ref->flags & PACKREF_SHADOWED)
			continue;
620
		if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0)
621
			continue;
622

Vicent Marti committed
623
		*out = git_reference__alloc(ref->name, &ref->oid, &ref->peel);
624 625
		error = (*out != NULL) ? 0 : -1;
		break;
626 627
	}

628
	return error;
629 630
}

631 632 633
static int refdb_fs_backend__iterator_next_name(
	const char **out, git_reference_iterator *_iter)
{
634
	int error = GIT_ITEROVER;
635 636
	refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent);
	refdb_fs_backend *backend = GIT_CONTAINER_OF(iter->parent.db->backend, refdb_fs_backend, parent);
637
	struct packref *ref;
638 639 640

	while (iter->loose_pos < iter->loose.length) {
		const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
641
		struct packref *ref;
642

643
		if (loose_lookup(NULL, backend, path) == 0) {
644 645 646 647
			ref = git_sortedcache_lookup(iter->cache, path);
			if (ref)
				ref->flags |= PACKREF_SHADOWED;

648 649
			*out = path;
			return 0;
650 651
		}

652
		git_error_clear();
653 654
	}

655 656 657
	error = GIT_ITEROVER;
	while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) {
		ref = git_sortedcache_entry(iter->cache, iter->packed_pos++);
658 659
		if (!ref) /* stop now if another thread deleted refs and we past end */
			break;
660 661 662

		if (ref->flags & PACKREF_SHADOWED)
			continue;
663
		if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0)
664 665
			continue;

666 667 668
		*out = ref->name;
		error = 0;
		break;
669 670
	}

671
	return error;
672 673 674 675 676
}

static int refdb_fs_backend__iterator(
	git_reference_iterator **out, git_refdb_backend *_backend, const char *glob)
{
677
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
678 679
	refdb_fs_iter *iter = NULL;
	int error;
680

681
	assert(backend);
682 683

	iter = git__calloc(1, sizeof(refdb_fs_iter));
684
	GIT_ERROR_CHECK_ALLOC(iter);
685

686 687
	if ((error = git_pool_init(&iter->pool, 1)) < 0)
		goto out;
688

689 690
	if ((error = git_vector_init(&iter->loose, 8, NULL)) < 0)
		goto out;
691 692

	if (glob != NULL &&
693 694 695 696 697
	    (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL) {
		error = GIT_ERROR_NOMEMORY;
		goto out;
	}

698
	if ((error = iter_load_loose_paths(backend, iter)) < 0)
699 700
		goto out;

701
	if ((error = packed_reload(backend)) < 0)
702
		goto out;
703 704 705

	if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0)
		goto out;
706 707 708 709 710 711

	iter->parent.next = refdb_fs_backend__iterator_next;
	iter->parent.next_name = refdb_fs_backend__iterator_next_name;
	iter->parent.free = refdb_fs_backend__iterator_free;

	*out = (git_reference_iterator *)iter;
712 713 714 715
out:
	if (error)
		refdb_fs_backend__iterator_free((git_reference_iterator *)iter);
	return error;
716 717
}

Vicent Marti committed
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740
static bool ref_is_available(
	const char *old_ref, const char *new_ref, const char *this_ref)
{
	if (old_ref == NULL || strcmp(old_ref, this_ref)) {
		size_t reflen = strlen(this_ref);
		size_t newlen = strlen(new_ref);
		size_t cmplen = reflen < newlen ? reflen : newlen;
		const char *lead = reflen < newlen ? new_ref : this_ref;

		if (!strncmp(new_ref, this_ref, cmplen) && lead[cmplen] == '/') {
			return false;
		}
	}

	return true;
}

static int reference_path_available(
	refdb_fs_backend *backend,
	const char *new_ref,
	const char* old_ref,
	int force)
{
741
	size_t i;
742
	int error;
Vicent Marti committed
743

744 745
	if ((error = packed_reload(backend)) < 0)
		return error;
Vicent Marti committed
746 747 748 749

	if (!force) {
		int exists;

750 751 752 753
		if ((error = refdb_fs_backend__exists(
			&exists, (git_refdb_backend *)backend, new_ref)) < 0) {
			return error;
		}
Vicent Marti committed
754 755

		if (exists) {
756
			git_error_set(GIT_ERROR_REFERENCE,
757
				"failed to write reference '%s': a reference with "
758
				"that name already exists.", new_ref);
Vicent Marti committed
759 760 761 762
			return GIT_EEXISTS;
		}
	}

763
	git_sortedcache_rlock(backend->refcache);
764 765

	for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) {
766
		struct packref *ref = git_sortedcache_entry(backend->refcache, i);
767

768 769
		if (ref && !ref_is_available(old_ref, new_ref, ref->name)) {
			git_sortedcache_runlock(backend->refcache);
770
			git_error_set(GIT_ERROR_REFERENCE,
771
				"path to reference '%s' collides with existing one", new_ref);
Vicent Marti committed
772 773
			return -1;
		}
774
	}
775

776
	git_sortedcache_runlock(backend->refcache);
Vicent Marti committed
777 778
	return 0;
}
779

780
static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name)
781
{
782
	int error, filebuf_flags;
783
	git_buf ref_path = GIT_BUF_INIT;
784
	const char *basedir;
785

786
	assert(file && backend && name);
787

788
	if (!git_path_isvalid(backend->repo, name, 0, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
789
		git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", name);
790 791 792
		return GIT_EINVALIDSPEC;
	}

793 794 795 796 797
	if (is_per_worktree_ref(name))
		basedir = backend->gitpath;
	else
		basedir = backend->commonpath;

798 799 800
	/* Remove a possibly existing empty directory hierarchy
	 * which name would collide with the reference name
	 */
801
	if ((error = git_futils_rmdir_r(name, basedir, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
802
		return error;
803

804
	if (git_buf_joinpath(&ref_path, basedir, name) < 0)
805 806
		return -1;

807
	filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS;
808
	if (backend->fsync)
809 810 811
		filebuf_flags |= GIT_FILEBUF_FSYNC;

	error = git_filebuf_open(file, ref_path.ptr, filebuf_flags, GIT_REFS_FILE_MODE);
812

813
	if (error == GIT_EDIRECTORY)
814
		git_error_set(GIT_ERROR_REFERENCE, "cannot lock ref '%s', there are refs beneath that folder", name);
815

816
	git_buf_dispose(&ref_path);
817
	return error;
818
}
819

820 821
static int loose_commit(git_filebuf *file, const git_reference *ref)
{
822 823
	assert(file && ref);

824
	if (ref->type == GIT_REFERENCE_DIRECT) {
825
		char oid[GIT_OID_HEXSZ + 1];
826
		git_oid_nfmt(oid, sizeof(oid), &ref->target.oid);
827

828
		git_filebuf_printf(file, "%s\n", oid);
829
	} else if (ref->type == GIT_REFERENCE_SYMBOLIC) {
830
		git_filebuf_printf(file, GIT_SYMREF "%s\n", ref->target.symbolic);
831 832 833 834
	} else {
		assert(0); /* don't let this happen */
	}

835
	return git_filebuf_commit(file);
836 837
}

838 839 840 841
static int refdb_fs_backend__lock(void **out, git_refdb_backend *_backend, const char *refname)
{
	int error;
	git_filebuf *lock;
842
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
843 844

	lock = git__calloc(1, sizeof(git_filebuf));
845
	GIT_ERROR_CHECK_ALLOC(lock);
846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861

	if ((error = loose_lock(lock, backend, refname)) < 0) {
		git__free(lock);
		return error;
	}

	*out = lock;
	return 0;
}

static int refdb_fs_backend__write_tail(
	git_refdb_backend *_backend,
	const git_reference *ref,
	git_filebuf *file,
	int update_reflog,
	const git_oid *old_id,
862 863 864
	const char *old_target,
	const git_signature *who,
	const char *message);
865 866 867 868 869

static int refdb_fs_backend__delete_tail(
	git_refdb_backend *_backend,
	git_filebuf *file,
	const char *ref_name,
870 871
	const git_oid *old_id,
	const char *old_target);
872 873 874 875 876 877 878 879 880 881

static int refdb_fs_backend__unlock(git_refdb_backend *backend, void *payload, int success, int update_reflog,
				    const git_reference *ref, const git_signature *sig, const char *message)
{
	git_filebuf *lock = (git_filebuf *) payload;
	int error = 0;

	if (success == 2)
		error = refdb_fs_backend__delete_tail(backend, lock, ref->name, NULL, NULL);
	else if (success)
882
		error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, NULL, NULL, sig, message);
883 884 885 886 887 888 889
	else
		git_filebuf_cleanup(lock);

	git__free(lock);
	return error;
}

890 891 892 893 894 895 896 897 898 899 900 901
/*
 * Find out what object this reference resolves to.
 *
 * For references that point to a 'big' tag (e.g. an
 * actual tag object on the repository), we need to
 * cache on the packfile the OID of the object to
 * which that 'big tag' is pointing to.
 */
static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref)
{
	git_object *object;

902
	if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL)
903 904 905 906 907
		return 0;

	/*
	 * Find the tagged object in the repository
	 */
908
	if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJECT_ANY) < 0)
909 910 911 912 913 914 915
		return -1;

	/*
	 * If the tagged object is a Tag object, we need to resolve it;
	 * if the ref is actually a 'weak' ref, we don't need to resolve
	 * anything.
	 */
916
	if (git_object_type(object) == GIT_OBJECT_TAG) {
917 918 919 920 921 922
		git_tag *tag = (git_tag *)object;

		/*
		 * Find the object pointed at by this tag
		 */
		git_oid_cpy(&ref->peel, git_tag_target_id(tag));
923
		ref->flags |= PACKREF_HAS_PEEL;
924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941

		/*
		 * The reference has now cached the resolved OID, and is
		 * marked at such. When written to the packfile, it'll be
		 * accompanied by this resolved oid
		 */
	}

	git_object_free(object);
	return 0;
}

/*
 * Write a single reference into a packfile
 */
static int packed_write_ref(struct packref *ref, git_filebuf *file)
{
	char oid[GIT_OID_HEXSZ + 1];
942
	git_oid_nfmt(oid, sizeof(oid), &ref->oid);
943 944 945 946 947 948 949 950 951 952 953

	/*
	 * For references that peel to an object in the repo, we must
	 * write the resulting peel on a separate line, e.g.
	 *
	 *	6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
	 *	^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
	 *
	 * This obviously only applies to tags.
	 * The required peels have already been loaded into `ref->peel_target`.
	 */
954
	if (ref->flags & PACKREF_HAS_PEEL) {
955
		char peel[GIT_OID_HEXSZ + 1];
956
		git_oid_nfmt(peel, sizeof(peel), &ref->peel);
957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978

		if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0)
			return -1;
	} else {
		if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0)
			return -1;
	}

	return 0;
}

/*
 * Remove all loose references
 *
 * Once we have successfully written a packfile,
 * all the loose references that were packed must be
 * removed from disk.
 *
 * This is a dangerous method; make sure the packfile
 * is well-written, because we are destructing references
 * here otherwise.
 */
979
static int packed_remove_loose(refdb_fs_backend *backend)
980
{
981
	size_t i;
982
	git_filebuf lock = GIT_FILEBUF_INIT;
983
	git_buf ref_content = GIT_BUF_INIT;
984
	int error = 0;
985

986 987 988 989
	/* backend->refcache is already locked when this is called */

	for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) {
		struct packref *ref = git_sortedcache_entry(backend->refcache, i);
990
		git_oid current_id;
991

992
		if (!ref || !(ref->flags & PACKREF_WAS_LOOSE))
993 994
			continue;

995 996
		git_filebuf_cleanup(&lock);

997 998 999
		/* We need to stop anybody from updating the ref while we try to do a safe delete */
		error = loose_lock(&lock, backend, ref->name);
		/* If someone else is updating it, let them do it */
1000
		if (error == GIT_EEXISTS || error == GIT_ENOTFOUND)
1001 1002 1003
			continue;

		if (error < 0) {
1004
			git_buf_dispose(&ref_content);
1005
			git_error_set(GIT_ERROR_REFERENCE, "failed to lock loose reference '%s'", ref->name);
1006
			return error;
1007 1008 1009 1010
		}

		error = git_futils_readbuffer(&ref_content, lock.path_original);
		/* Someone else beat us to cleaning up the ref, let's simply continue */
1011
		if (error == GIT_ENOTFOUND)
1012 1013 1014
			continue;

		/* This became a symref between us packing and trying to delete it, so ignore it */
1015
		if (!git__prefixcmp(ref_content.ptr, GIT_SYMREF))
1016 1017
			continue;

1018 1019
		/* Figure out the current id; if we find a bad ref file, skip it so we can do the rest */
		if (loose_parse_oid(&current_id, lock.path_original, &ref_content) < 0)
1020
			continue;
1021

1022
		/* If the ref moved since we packed it, we must not delete it */
1023
		if (!git_oid_equal(&current_id, &ref->oid))
1024 1025
			continue;

1026 1027 1028
		/*
		 * if we fail to remove a single file, this is *not* good,
		 * but we should keep going and remove as many as possible.
1029 1030
		 * If we fail to remove, the ref is still in the old state, so
		 * we haven't lost information.
1031
		 */
1032
		p_unlink(lock.path_original);
1033 1034
	}

1035
	git_buf_dispose(&ref_content);
1036
	git_filebuf_cleanup(&lock);
1037
	return 0;
1038 1039 1040 1041 1042 1043 1044
}

/*
 * Write all the contents in the in-memory packfile to disk.
 */
static int packed_write(refdb_fs_backend *backend)
{
1045
	git_sortedcache *refcache = backend->refcache;
1046
	git_filebuf pack_file = GIT_FILEBUF_INIT;
1047
	int error, open_flags = 0;
1048
	size_t i;
1049

1050
	/* lock the cache to updates while we do this */
1051 1052
	if ((error = git_sortedcache_wlock(refcache)) < 0)
		return error;
1053

1054
	if (backend->fsync)
1055 1056
		open_flags = GIT_FILEBUF_FSYNC;

1057
	/* Open the file! */
1058
	if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), open_flags, GIT_PACKEDREFS_FILE_MODE)) < 0)
1059
		goto fail;
1060 1061 1062 1063

	/* Packfiles have a header... apparently
	 * This is in fact not required, but we might as well print it
	 * just for kicks */
1064
	if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < 0)
1065
		goto fail;
1066

1067 1068
	for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) {
		struct packref *ref = git_sortedcache_entry(refcache, i);
1069
		assert(ref);
1070

1071
		if ((error = packed_find_peel(backend, ref)) < 0)
1072
			goto fail;
1073

1074
		if ((error = packed_write_ref(ref, &pack_file)) < 0)
1075
			goto fail;
1076 1077 1078 1079
	}

	/* if we've written all the references properly, we can commit
	 * the packfile to make the changes effective */
1080
	if ((error = git_filebuf_commit(&pack_file)) < 0)
1081
		goto fail;
1082 1083 1084

	/* when and only when the packfile has been properly written,
	 * we can go ahead and remove the loose refs */
1085
	if ((error = packed_remove_loose(backend)) < 0)
1086
		goto fail;
1087

1088 1089
	git_sortedcache_updated(refcache);
	git_sortedcache_wunlock(refcache);
1090 1091 1092 1093

	/* we're good now */
	return 0;

1094
fail:
1095
	git_filebuf_cleanup(&pack_file);
1096
	git_sortedcache_wunlock(refcache);
1097

1098
	return error;
1099 1100
}

1101 1102 1103
static int packed_delete(refdb_fs_backend *backend, const char *ref_name)
{
	size_t pack_pos;
1104
	int error, found = 0;
1105 1106 1107 1108 1109 1110 1111

	if ((error = packed_reload(backend)) < 0)
		goto cleanup;

	if ((error = git_sortedcache_wlock(backend->refcache)) < 0)
		goto cleanup;

1112 1113 1114
	/* If a packed reference exists, remove it from the packfile and repack if necessary */
	error = git_sortedcache_lookup_index(&pack_pos, backend->refcache, ref_name);
	if (error == 0) {
1115
		error = git_sortedcache_remove(backend->refcache, pack_pos);
1116 1117 1118 1119
		found = 1;
	}
	if (error == GIT_ENOTFOUND)
		error = 0;
1120 1121 1122

	git_sortedcache_wunlock(backend->refcache);

1123 1124
	if (found)
		error = packed_write(backend);
1125 1126 1127 1128 1129

cleanup:
	return error;
}

1130
static 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);
1131 1132
static int has_reflog(git_repository *repo, const char *name);

1133
static int should_write_reflog(int *write, git_repository *repo, const char *name)
1134
{
1135
	int error, logall;
1136

1137
	error = git_repository__configmap_lookup(&logall, repo, GIT_CONFIGMAP_LOGALLREFUPDATES);
1138
	if (error < 0)
1139 1140
		return error;

1141 1142 1143
	/* Defaults to the opposite of the repo being bare */
	if (logall == GIT_LOGALLREFUPDATES_UNSET)
		logall = !git_repository_is_bare(repo);
1144

1145 1146 1147
	*write = 0;
	switch (logall) {
	case GIT_LOGALLREFUPDATES_FALSE:
1148
		*write = 0;
1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162
		break;

	case GIT_LOGALLREFUPDATES_TRUE:
		/* Only write if it already has a log,
		 * or if it's under heads/, remotes/ or notes/
		 */
		*write = has_reflog(repo, name) ||
			!git__prefixcmp(name, GIT_REFS_HEADS_DIR) ||
			!git__strcmp(name, GIT_HEAD_FILE) ||
			!git__prefixcmp(name, GIT_REFS_REMOTES_DIR) ||
			!git__prefixcmp(name, GIT_REFS_NOTES_DIR);
		break;

	case GIT_LOGALLREFUPDATES_ALWAYS:
1163
		*write = 1;
1164
		break;
1165
	}
1166 1167 1168

	return 0;
}
1169

1170
static int cmp_old_ref(int *cmp, git_refdb_backend *backend, const char *name,
1171 1172 1173 1174 1175 1176
	const git_oid *old_id, const char *old_target)
{
	int error = 0;
	git_reference *old_ref = NULL;

	*cmp = 0;
1177 1178 1179 1180 1181 1182 1183 1184
	/* It "matches" if there is no old value to compare against */
	if (!old_id && !old_target)
		return 0;

	if ((error = refdb_fs_backend__lookup(&old_ref, backend, name)) < 0)
		goto out;

	/* If the types don't match, there's no way the values do */
1185
	if (old_id && old_ref->type != GIT_REFERENCE_DIRECT) {
1186 1187 1188
		*cmp = -1;
		goto out;
	}
1189
	if (old_target && old_ref->type != GIT_REFERENCE_SYMBOLIC) {
1190 1191
		*cmp = 1;
		goto out;
1192 1193
	}

1194
	if (old_id && old_ref->type == GIT_REFERENCE_DIRECT)
1195 1196
		*cmp = git_oid_cmp(old_id, &old_ref->target.oid);

1197
	if (old_target && old_ref->type == GIT_REFERENCE_SYMBOLIC)
1198 1199 1200 1201 1202 1203 1204 1205
		*cmp = git__strcmp(old_target, old_ref->target.symbolic);

out:
	git_reference_free(old_ref);

	return error;
}

1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222
/*
 * The git.git comment regarding this, for your viewing pleasure:
 *
 * Special hack: If a branch is updated directly and HEAD
 * points to it (may happen on the remote side of a push
 * for example) then logically the HEAD reflog should be
 * updated too.
 * A generic solution implies reverse symref information,
 * but finding all symrefs pointing to the given branch
 * would be rather costly for this rare event (the direct
 * update of a branch) to be worth it.  So let's cheat and
 * check with HEAD only which should cover 99% of all usage
 * scenarios (even 100% of the default ones).
 */
static int maybe_append_head(refdb_fs_backend *backend, const git_reference *ref, const git_signature *who, const char *message)
{
	int error;
1223
	git_oid old_id;
1224 1225 1226
	git_reference *tmp = NULL, *head = NULL, *peeled = NULL;
	const char *name;

1227
	if (ref->type == GIT_REFERENCE_SYMBOLIC)
1228
		return 0;
1229

1230
	/* if we can't resolve, we use {0}*40 as old id */
1231 1232
	if (git_reference_name_to_id(&old_id, backend->repo, ref->name) < 0)
		memset(&old_id, 0, sizeof(old_id));
1233

1234
	if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0)
1235 1236
		return error;

1237
	if (git_reference_type(head) == GIT_REFERENCE_DIRECT)
1238 1239
		goto cleanup;

1240 1241 1242 1243
	if ((error = git_reference_lookup(&tmp, backend->repo, GIT_HEAD_FILE)) < 0)
		goto cleanup;

	/* Go down the symref chain until we find the branch */
1244
	while (git_reference_type(tmp) == GIT_REFERENCE_SYMBOLIC) {
1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262
		error = git_reference_lookup(&peeled, backend->repo, git_reference_symbolic_target(tmp));
		if (error < 0)
			break;

		git_reference_free(tmp);
		tmp = peeled;
	}

	if (error == GIT_ENOTFOUND) {
		error = 0;
		name = git_reference_symbolic_target(tmp);
	} else if (error < 0) {
		goto cleanup;
	} else {
		name = git_reference_name(tmp);
	}

	if (strcmp(name, ref->name))
1263 1264
		goto cleanup;

1265 1266
	error = reflog_append(backend, head, &old_id, git_reference_target(ref), who, message);

1267
cleanup:
1268
	git_reference_free(tmp);
1269 1270 1271 1272
	git_reference_free(head);
	return error;
}

1273 1274
static int refdb_fs_backend__write(
	git_refdb_backend *_backend,
Vicent Marti committed
1275
	const git_reference *ref,
1276
	int force,
1277
	const git_signature *who,
1278
	const char *message,
1279 1280
	const git_oid *old_id,
	const char *old_target)
1281
{
1282
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1283
	git_filebuf file = GIT_FILEBUF_INIT;
1284
	int error = 0;
1285

1286
	assert(backend);
1287

1288
	if ((error = reference_path_available(backend, ref->name, NULL, force)) < 0)
Vicent Marti committed
1289 1290
		return error;

1291
	/* We need to perform the reflog append and old value check under the ref's lock */
1292
	if ((error = loose_lock(&file, backend, ref->name)) < 0)
1293 1294
		return error;

1295
	return refdb_fs_backend__write_tail(_backend, ref, &file, true, old_id, old_target, who, message);
1296 1297 1298 1299 1300 1301 1302 1303
}

static int refdb_fs_backend__write_tail(
	git_refdb_backend *_backend,
	const git_reference *ref,
	git_filebuf *file,
	int update_reflog,
	const git_oid *old_id,
1304 1305 1306
	const char *old_target,
	const git_signature *who,
	const char *message)
1307
{
1308
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1309 1310 1311 1312
	int error = 0, cmp = 0, should_write;
	const char *new_target = NULL;
	const git_oid *new_id = NULL;

1313
	if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0)
1314
		goto on_error;
1315 1316

	if (cmp) {
1317
		git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match");
1318 1319
		error = GIT_EMODIFIED;
		goto on_error;
1320 1321
	}

1322
	if (ref->type == GIT_REFERENCE_SYMBOLIC)
1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336
		new_target = ref->target.symbolic;
	else
		new_id = &ref->target.oid;

	error = cmp_old_ref(&cmp, _backend, ref->name, new_id, new_target);
	if (error < 0 && error != GIT_ENOTFOUND)
		goto on_error;

	/* Don't update if we have the same value */
	if (!error && !cmp) {
		error = 0;
		goto on_error; /* not really error */
	}

1337 1338
	if (update_reflog) {
		if ((error = should_write_reflog(&should_write, backend->repo, ref->name)) < 0)
1339
			goto on_error;
1340 1341 1342 1343 1344 1345 1346

		if (should_write) {
			if ((error = reflog_append(backend, ref, NULL, NULL, who, message)) < 0)
				goto on_error;
			if ((error = maybe_append_head(backend, ref, who, message)) < 0)
				goto on_error;
		}
1347 1348
	}

1349
	return loose_commit(file, ref);
1350 1351

on_error:
1352
        git_filebuf_cleanup(file);
1353
        return error;
1354 1355
}

1356
static void refdb_fs_backend__prune_refs(
1357 1358
	refdb_fs_backend *backend,
	const char *ref_name,
1359
	const char *prefix)
1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376
{
	git_buf relative_path = GIT_BUF_INIT;
	git_buf base_path = GIT_BUF_INIT;
	size_t commonlen;

	assert(backend && ref_name);

	if (git_buf_sets(&relative_path, ref_name) < 0)
		goto cleanup;

	git_path_squash_slashes(&relative_path);
	if ((commonlen = git_path_common_dirlen("refs/heads/", git_buf_cstr(&relative_path))) == strlen("refs/heads/") ||
		(commonlen = git_path_common_dirlen("refs/tags/", git_buf_cstr(&relative_path))) == strlen("refs/tags/") ||
		(commonlen = git_path_common_dirlen("refs/remotes/", git_buf_cstr(&relative_path))) == strlen("refs/remotes/")) {

		git_buf_truncate(&relative_path, commonlen);

1377 1378
		if (prefix) {
			if (git_buf_join3(&base_path, '/', backend->commonpath, prefix, git_buf_cstr(&relative_path)) < 0)
1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392
				goto cleanup;
		} else {
			if (git_buf_joinpath(&base_path, backend->commonpath, git_buf_cstr(&relative_path)) < 0)
				goto cleanup;
		}

		git_futils_rmdir_r(ref_name + commonlen, git_buf_cstr(&base_path), GIT_RMDIR_EMPTY_PARENTS | GIT_RMDIR_SKIP_ROOT);
	}

cleanup:
	git_buf_dispose(&relative_path);
	git_buf_dispose(&base_path);
}

1393 1394
static int refdb_fs_backend__delete(
	git_refdb_backend *_backend,
1395 1396
	const char *ref_name,
	const git_oid *old_id, const char *old_target)
1397
{
1398
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1399
	git_filebuf file = GIT_FILEBUF_INIT;
1400
	int error = 0;
1401

1402
	assert(backend && ref_name);
1403

1404 1405 1406
	if ((error = loose_lock(&file, backend, ref_name)) < 0)
		return error;

1407 1408 1409 1410 1411
	if ((error = refdb_reflog_fs__delete(_backend, ref_name)) < 0) {
		git_filebuf_cleanup(&file);
		return error;
	}

1412 1413 1414
	return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target);
}

1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433
static int loose_delete(refdb_fs_backend *backend, const char *ref_name)
{
	git_buf loose_path = GIT_BUF_INIT;
	int error = 0;

	if (git_buf_joinpath(&loose_path, backend->commonpath, ref_name) < 0)
		return -1;

	error = p_unlink(loose_path.ptr);
	if (error < 0 && errno == ENOENT)
		error = GIT_ENOTFOUND;
	else if (error != 0)
		error = -1;

	git_buf_dispose(&loose_path);

	return error;
}

1434 1435 1436 1437 1438 1439
static int refdb_fs_backend__delete_tail(
	git_refdb_backend *_backend,
	git_filebuf *file,
	const char *ref_name,
	const git_oid *old_id, const char *old_target)
{
1440
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1441
	int error = 0, cmp = 0;
1442
	bool packed_deleted = 0;
1443

1444 1445 1446 1447 1448
	error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target);
	if (error < 0)
		goto cleanup;

	if (cmp) {
1449
		git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match");
1450 1451 1452 1453
		error = GIT_EMODIFIED;
		goto cleanup;
	}

1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471
	/*
	 * To ensure that an external observer will see either the current ref value
	 * (because the loose ref still exists), or a missing ref (after the packed-file is
	 * unlocked, there will be nothing left), we must ensure things happen in the
	 * following order:
	 *
	 * - the packed-ref file is locked and loaded, as well as a loose one, if it exists
	 * - we optimistically delete a packed ref, keeping track of whether it existed
	 * - we delete the loose ref, note that we have its .lock
	 * - the loose ref is "unlocked", then the packed-ref file is rewritten and unlocked
	 * - we should prune the path components if a loose ref was deleted
	 *
	 * Note that, because our packed backend doesn't expose its filesystem lock,
	 * we might not be able to guarantee that this is what actually happens (ie.
	 * as our current code never write packed-refs.lock, nothing stops observers
	 * from grabbing a "stale" value from there).
	 */
	if ((error = packed_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND)
1472
		goto cleanup;
1473

1474 1475
	if (error == 0)
		packed_deleted = 1;
1476

1477
	if ((error = loose_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND)
1478
		goto cleanup;
Vicent Marti committed
1479

1480
	if (error == GIT_ENOTFOUND) {
1481
		error = packed_deleted ? 0 : ref_error_notfound(ref_name);
1482 1483 1484 1485
		goto cleanup;
	}

cleanup:
1486
	git_filebuf_cleanup(file);
1487
	if (error == 0)
1488
		refdb_fs_backend__prune_refs(backend, ref_name, "");
1489
	return error;
1490 1491
}

1492 1493
static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name);

Vicent Marti committed
1494 1495 1496 1497 1498
static int refdb_fs_backend__rename(
	git_reference **out,
	git_refdb_backend *_backend,
	const char *old_name,
	const char *new_name,
1499
	int force,
1500
	const git_signature *who,
1501
	const char *message)
Vicent Marti committed
1502
{
1503
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1504
	git_reference *old, *new = NULL;
1505
	git_filebuf file = GIT_FILEBUF_INIT;
Vicent Marti committed
1506 1507
	int error;

1508
	assert(backend);
Vicent Marti committed
1509

1510 1511 1512
	if ((error = reference_path_available(
			backend, new_name, old_name, force)) < 0 ||
		(error = refdb_fs_backend__lookup(&old, _backend, old_name)) < 0)
Vicent Marti committed
1513 1514
		return error;

1515
	if ((error = refdb_fs_backend__delete(_backend, old_name, NULL, NULL)) < 0) {
Vicent Marti committed
1516 1517 1518 1519
		git_reference_free(old);
		return error;
	}

1520
	new = git_reference__realloc(&old, new_name);
1521 1522 1523
	if (!new) {
		git_reference_free(old);
		return -1;
Vicent Marti committed
1524 1525
	}

1526
	if ((error = loose_lock(&file, backend, new->name)) < 0) {
1527 1528 1529 1530 1531 1532 1533
		git_reference_free(new);
		return error;
	}

	/* Try to rename the refog; it's ok if the old doesn't exist */
	error = refdb_reflog_fs__rename(_backend, old_name, new_name);
	if (((error == 0) || (error == GIT_ENOTFOUND)) &&
1534
	    ((error = reflog_append(backend, new, git_reference_target(new), NULL, who, message)) < 0)) {
1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547
		git_reference_free(new);
		git_filebuf_cleanup(&file);
		return error;
	}

	if (error < 0) {
		git_reference_free(new);
		git_filebuf_cleanup(&file);
		return error;
	}


	if ((error = loose_commit(&file, new)) < 0 || out == NULL) {
Vicent Marti committed
1548
		git_reference_free(new);
1549
		return error;
Vicent Marti committed
1550 1551
	}

1552
	*out = new;
Vicent Marti committed
1553 1554 1555
	return 0;
}

1556 1557
static int refdb_fs_backend__compress(git_refdb_backend *_backend)
{
1558
	int error;
1559
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1560

1561
	assert(backend);
1562

1563 1564 1565 1566
	if ((error = packed_reload(backend)) < 0 || /* load the existing packfile */
	    (error = packed_loadloose(backend)) < 0 || /* add all the loose refs */
	    (error = packed_write(backend)) < 0) /* write back to disk */
		return error;
1567 1568 1569 1570 1571 1572

	return 0;
}

static void refdb_fs_backend__free(git_refdb_backend *_backend)
{
1573
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1574

1575
	assert(backend);
1576

1577
	git_sortedcache_free(backend->refcache);
1578
	git__free(backend->gitpath);
1579
	git__free(backend->commonpath);
1580 1581 1582
	git__free(backend);
}

1583
static char *setup_namespace(git_repository *repo, const char *in)
1584
{
1585 1586
	git_buf path = GIT_BUF_INIT;
	char *parts, *start, *end, *out = NULL;
1587

1588 1589
	if (!in)
		goto done;
1590

1591
	git_buf_puts(&path, in);
1592 1593

	/* if the repo is not namespaced, nothing else to do */
1594 1595 1596 1597
	if (repo->namespace == NULL) {
		out = git_buf_detach(&path);
		goto done;
	}
1598 1599 1600

	parts = end = git__strdup(repo->namespace);
	if (parts == NULL)
1601
		goto done;
1602

1603
	/*
1604 1605 1606 1607 1608
	 * From `man gitnamespaces`:
	 *  namespaces which include a / will expand to a hierarchy
	 *  of namespaces; for example, GIT_NAMESPACE=foo/bar will store
	 *  refs under refs/namespaces/foo/refs/namespaces/bar/
	 */
1609 1610
	while ((start = git__strsep(&end, "/")) != NULL)
		git_buf_printf(&path, "refs/namespaces/%s/", start);
1611

1612
	git_buf_printf(&path, "refs/namespaces/%s/refs", end);
Vicent Marti committed
1613
	git__free(parts);
1614 1615

	/* Make sure that the folder with the namespace exists */
1616 1617 1618
	if (git_futils_mkdir_relative(git_buf_cstr(&path), in, 0777,
			GIT_MKDIR_PATH, NULL) < 0)
		goto done;
1619

1620
	/* Return root of the namespaced gitpath, i.e. without the trailing '/refs' */
1621 1622 1623 1624
	git_buf_rtruncate_at_char(&path, '/');
	out = git_buf_detach(&path);

done:
1625
	git_buf_dispose(&path);
1626
	return out;
1627 1628
}

1629 1630 1631 1632 1633 1634 1635
static int reflog_alloc(git_reflog **reflog, const char *name)
{
	git_reflog *log;

	*reflog = NULL;

	log = git__calloc(1, sizeof(git_reflog));
1636
	GIT_ERROR_CHECK_ALLOC(log);
1637 1638

	log->ref_name = git__strdup(name);
1639
	GIT_ERROR_CHECK_ALLOC(log->ref_name);
1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653

	if (git_vector_init(&log->entries, 0, NULL) < 0) {
		git__free(log->ref_name);
		git__free(log);
		return -1;
	}

	*reflog = log;

	return 0;
}

static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
{
1654
	git_parse_ctx parser = GIT_PARSE_CTX_INIT;
1655

1656 1657
	if ((git_parse_ctx_init(&parser, buf, buf_size)) < 0)
		return -1;
1658

1659
	for (; parser.remain_len; git_parse_advance_line(&parser)) {
1660
		git_reflog_entry *entry;
1661 1662
		const char *sig;
		char c;
1663

1664 1665 1666 1667
		entry = git__calloc(1, sizeof(*entry));
		GIT_ERROR_CHECK_ALLOC(entry);
		entry->committer = git__calloc(1, sizeof(*entry->committer));
		GIT_ERROR_CHECK_ALLOC(entry->committer);
1668

1669 1670 1671
		if (git_parse_advance_oid(&entry->oid_old, &parser) < 0 ||
		    git_parse_advance_expected(&parser, " ", 1) < 0 ||
		    git_parse_advance_oid(&entry->oid_cur, &parser) < 0)
1672
			goto next;
1673

1674 1675 1676
		sig = parser.line;
		while (git_parse_peek(&c, &parser, 0) == 0 && c != '\t' && c != '\n')
			git_parse_advance_chars(&parser, 1);
1677

1678
		if (git_signature__parse(entry->committer, &sig, parser.line, NULL, 0) < 0)
1679
			goto next;
1680

1681 1682 1683
		if (c == '\t') {
			size_t len;
			git_parse_advance_chars(&parser, 1);
1684

1685 1686 1687
			len = parser.line_len;
			if (parser.line[len - 1] == '\n')
				len--;
1688

1689
			entry->msg = git__strndup(parser.line, len);
1690
			GIT_ERROR_CHECK_ALLOC(entry->msg);
1691
		}
1692

1693 1694 1695 1696
		if ((git_vector_insert(&log->entries, entry)) < 0) {
			git_reflog_entry__free(entry);
			return -1;
		}
1697

1698
		continue;
1699

1700 1701 1702
next:
		git_reflog_entry__free(entry);
	}
1703

1704
	return 0;
1705 1706 1707 1708 1709 1710 1711 1712 1713 1714
}

static int create_new_reflog_file(const char *filepath)
{
	int fd, error;

	if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0)
		return error;

	if ((fd = p_open(filepath,
1715
			O_WRONLY | O_CREAT,
1716 1717 1718 1719 1720 1721 1722 1723
			GIT_REFLOG_FILE_MODE)) < 0)
		return -1;

	return p_close(fd);
}

GIT_INLINE(int) retrieve_reflog_path(git_buf *path, git_repository *repo, const char *name)
{
1724 1725
	if (strcmp(name, GIT_HEAD_FILE) == 0)
		return git_buf_join3(path, '/', repo->gitdir, GIT_REFLOG_DIR, name);
1726
	return git_buf_join3(path, '/', repo->commondir, GIT_REFLOG_DIR, name);
1727 1728
}

1729 1730 1731 1732 1733 1734 1735 1736 1737
static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name)
{
	refdb_fs_backend *backend;
	git_repository *repo;
	git_buf path = GIT_BUF_INIT;
	int error;

	assert(_backend && name);

1738
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1739 1740 1741 1742 1743
	repo = backend->repo;

	if ((error = retrieve_reflog_path(&path, repo, name)) < 0)
		return error;

1744
	error = create_new_reflog_file(git_buf_cstr(&path));
1745
	git_buf_dispose(&path);
1746 1747

	return error;
1748 1749
}

1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760
static int has_reflog(git_repository *repo, const char *name)
{
	int ret = 0;
	git_buf path = GIT_BUF_INIT;

	if (retrieve_reflog_path(&path, repo, name) < 0)
		goto cleanup;

	ret = git_path_isfile(git_buf_cstr(&path));

cleanup:
1761
	git_buf_dispose(&path);
1762 1763 1764
	return ret;
}

1765 1766 1767 1768 1769 1770
static int refdb_reflog_fs__has_log(git_refdb_backend *_backend, const char *name)
{
	refdb_fs_backend *backend;

	assert(_backend && name);

1771
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1772 1773 1774 1775

	return has_reflog(backend->repo, name);
}

1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786
static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend, const char *name)
{
	int error = -1;
	git_buf log_path = GIT_BUF_INIT;
	git_buf log_file = GIT_BUF_INIT;
	git_reflog *log = NULL;
	git_repository *repo;
	refdb_fs_backend *backend;

	assert(out && _backend && name);

1787
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802
	repo = backend->repo;

	if (reflog_alloc(&log, name) < 0)
		return -1;

	if (retrieve_reflog_path(&log_path, repo, name) < 0)
		goto cleanup;

	error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path));
	if (error < 0 && error != GIT_ENOTFOUND)
		goto cleanup;

	if ((error == GIT_ENOTFOUND) &&
		((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0))
		goto cleanup;
1803

1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814
	if ((error = reflog_parse(log,
		git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0)
		goto cleanup;

	*out = log;
	goto success;

cleanup:
	git_reflog_free(log);

success:
1815 1816
	git_buf_dispose(&log_file);
	git_buf_dispose(&log_path);
1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845

	return error;
}

static int serialize_reflog_entry(
	git_buf *buf,
	const git_oid *oid_old,
	const git_oid *oid_new,
	const git_signature *committer,
	const char *msg)
{
	char raw_old[GIT_OID_HEXSZ+1];
	char raw_new[GIT_OID_HEXSZ+1];

	git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old);
	git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new);

	git_buf_clear(buf);

	git_buf_puts(buf, raw_old);
	git_buf_putc(buf, ' ');
	git_buf_puts(buf, raw_new);

	git_signature__writebuf(buf, " ", committer);

	/* drop trailing LF */
	git_buf_rtrim(buf);

	if (msg) {
1846 1847
		size_t i;

1848 1849
		git_buf_putc(buf, '\t');
		git_buf_puts(buf, msg);
1850 1851 1852 1853 1854

		for (i = 0; i < buf->size - 2; i++)
			if (buf->ptr[i] == '\n')
				buf->ptr[i] = ' ';
		git_buf_rtrim(buf);
1855 1856 1857 1858 1859 1860 1861
	}

	git_buf_putc(buf, '\n');

	return git_buf_oom(buf);
}

1862 1863 1864 1865 1866 1867 1868 1869
static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char *refname)
{
	git_repository *repo;
	git_buf log_path = GIT_BUF_INIT;
	int error;

	repo = backend->repo;

1870
	if (!git_path_isvalid(backend->repo, refname, 0, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
1871
		git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", refname);
1872 1873 1874
		return GIT_EINVALIDSPEC;
	}

1875 1876 1877 1878
	if (retrieve_reflog_path(&log_path, repo, refname) < 0)
		return -1;

	if (!git_path_isfile(git_buf_cstr(&log_path))) {
1879
		git_error_set(GIT_ERROR_INVALID,
1880
			"log file for reference '%s' doesn't exist", refname);
1881 1882 1883 1884 1885 1886 1887
		error = -1;
		goto cleanup;
	}

	error = git_filebuf_open(file, git_buf_cstr(&log_path), 0, GIT_REFLOG_FILE_MODE);

cleanup:
1888
	git_buf_dispose(&log_path);
1889 1890 1891 1892

	return error;
}

1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903
static int refdb_reflog_fs__write(git_refdb_backend *_backend, git_reflog *reflog)
{
	int error = -1;
	unsigned int i;
	git_reflog_entry *entry;
	refdb_fs_backend *backend;
	git_buf log = GIT_BUF_INIT;
	git_filebuf fbuf = GIT_FILEBUF_INIT;

	assert(_backend && reflog);

1904
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1905

1906
	if ((error = lock_reflog(&fbuf, backend, reflog->ref_name)) < 0)
1907 1908 1909 1910 1911 1912 1913 1914 1915 1916
		return -1;

	git_vector_foreach(&reflog->entries, i, entry) {
		if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0)
			goto cleanup;

		if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0)
			goto cleanup;
	}

1917
	error = git_filebuf_commit(&fbuf);
1918 1919 1920 1921 1922 1923
	goto success;

cleanup:
	git_filebuf_cleanup(&fbuf);

success:
1924
	git_buf_dispose(&log);
1925

1926 1927 1928
	return error;
}

1929
/* Append to the reflog, must be called under reference lock */
1930
static 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)
1931
{
1932
	int error, is_symbolic, open_flags;
1933
	git_oid old_id = {{0}}, new_id = {{0}};
1934 1935 1936
	git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
	git_repository *repo = backend->repo;

1937
	is_symbolic = ref->type == GIT_REFERENCE_SYMBOLIC;
1938 1939 1940 1941 1942

	/* "normal" symbolic updates do not write */
	if (is_symbolic &&
	    strcmp(ref->name, GIT_HEAD_FILE) &&
	    !(old && new))
1943 1944
		return 0;

Etienne Samson committed
1945
	/* From here on is_symbolic also means that it's HEAD */
1946 1947 1948

	if (old) {
		git_oid_cpy(&old_id, old);
1949
	} else {
1950
		error = git_reference_name_to_id(&old_id, repo, ref->name);
1951
		if (error < 0 && error != GIT_ENOTFOUND)
1952 1953 1954
			return error;
	}

1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966
	if (new) {
		git_oid_cpy(&new_id, new);
	} else {
		if (!is_symbolic) {
			git_oid_cpy(&new_id, git_reference_target(ref));
		} else {
			error = git_reference_name_to_id(&new_id, repo, git_reference_symbolic_target(ref));
			if (error < 0 && error != GIT_ENOTFOUND)
				return error;
			/* detaching HEAD does not create an entry */
			if (error == GIT_ENOTFOUND)
				return 0;
1967

1968
			git_error_clear();
1969
		}
1970
	}
1971

1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982
	if ((error = serialize_reflog_entry(&buf, &old_id, &new_id, who, message)) < 0)
		goto cleanup;

	if ((error = retrieve_reflog_path(&path, repo, ref->name)) < 0)
		goto cleanup;

	if (((error = git_futils_mkpath2file(git_buf_cstr(&path), 0777)) < 0) &&
	    (error != GIT_EEXISTS)) {
		goto cleanup;
	}

1983 1984 1985
	/* If the new branch matches part of the namespace of a previously deleted branch,
	 * there maybe an obsolete/unused directory (or directory hierarchy) in the way.
	 */
1986
	if (git_path_isdir(git_buf_cstr(&path))) {
1987 1988 1989 1990
		if ((error = git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY)) < 0) {
			if (error == GIT_ENOTFOUND)
				error = 0;
		} else if (git_path_isdir(git_buf_cstr(&path))) {
1991
			git_error_set(GIT_ERROR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder",
1992 1993 1994 1995 1996 1997
				ref->name);
			error = GIT_EDIRECTORY;
		}

		if (error != 0)
			goto cleanup;
1998 1999
	}

2000 2001
	open_flags = O_WRONLY | O_CREAT | O_APPEND;

2002
	if (backend->fsync)
2003 2004 2005
		open_flags |= O_FSYNC;

	error = git_futils_writebuffer(&buf, git_buf_cstr(&path), open_flags, GIT_REFLOG_FILE_MODE);
2006 2007

cleanup:
2008 2009
	git_buf_dispose(&buf);
	git_buf_dispose(&path);
2010

2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025
	return error;
}

static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name)
{
	int error = 0, fd;
	git_buf old_path = GIT_BUF_INIT;
	git_buf new_path = GIT_BUF_INIT;
	git_buf temp_path = GIT_BUF_INIT;
	git_buf normalized = GIT_BUF_INIT;
	git_repository *repo;
	refdb_fs_backend *backend;

	assert(_backend && old_name && new_name);

2026
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
2027 2028 2029
	repo = backend->repo;

	if ((error = git_reference__normalize_name(
2030
		&normalized, new_name, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL)) < 0)
2031 2032
			return error;

2033
	if (git_buf_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0)
2034 2035 2036 2037 2038 2039 2040 2041
		return -1;

	if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0)
		return -1;

	if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0)
		return -1;

2042 2043 2044 2045 2046
	if (!git_path_exists(git_buf_cstr(&old_path))) {
		error = GIT_ENOTFOUND;
		goto cleanup;
	}

2047 2048 2049 2050 2051 2052 2053 2054 2055 2056
	/*
	 * Move the reflog to a temporary place. This two-phase renaming is required
	 * in order to cope with funny renaming use cases when one tries to move a reference
	 * to a partially colliding namespace:
	 *  - a/b -> a/b/c
	 *  - a/b/c/d -> a/b/c
	 */
	if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0)
		return -1;

2057
	if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) {
2058 2059 2060 2061 2062 2063 2064
		error = -1;
		goto cleanup;
	}

	p_close(fd);

	if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) {
2065
		git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name);
2066 2067 2068 2069
		error = -1;
		goto cleanup;
	}

2070
	if (git_path_isdir(git_buf_cstr(&new_path)) &&
2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081
		(git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) {
		error = -1;
		goto cleanup;
	}

	if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) {
		error = -1;
		goto cleanup;
	}

	if (p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path)) < 0) {
2082
		git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name);
2083 2084 2085 2086
		error = -1;
	}

cleanup:
2087 2088 2089 2090
	git_buf_dispose(&temp_path);
	git_buf_dispose(&old_path);
	git_buf_dispose(&new_path);
	git_buf_dispose(&normalized);
2091 2092 2093 2094 2095 2096

	return error;
}

static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name)
{
2097
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
2098
	git_buf path = GIT_BUF_INIT;
2099
	int error;
2100 2101 2102

	assert(_backend && name);

2103 2104
	if ((error = retrieve_reflog_path(&path, backend->repo, name)) < 0)
		goto out;
2105

2106 2107
	if (!git_path_exists(path.ptr))
		goto out;
2108

2109 2110
	if ((error = p_unlink(path.ptr)) < 0)
		goto out;
2111

2112
	refdb_fs_backend__prune_refs(backend, name, GIT_REFLOG_DIR);
2113 2114

out:
2115
	git_buf_dispose(&path);
2116 2117 2118 2119

	return error;
}

2120 2121
int git_refdb_backend_fs(
	git_refdb_backend **backend_out,
2122
	git_repository *repository)
2123
{
2124
	int t = 0;
2125
	git_buf gitpath = GIT_BUF_INIT;
2126 2127 2128
	refdb_fs_backend *backend;

	backend = git__calloc(1, sizeof(refdb_fs_backend));
2129
	GIT_ERROR_CHECK_ALLOC(backend);
2130

2131 2132 2133
	if (git_refdb_init_backend(&backend->parent, GIT_REFDB_BACKEND_VERSION) < 0)
		goto fail;

2134
	backend->repo = repository;
Vicent Marti committed
2135

2136 2137
	if (repository->gitdir) {
		backend->gitpath = setup_namespace(repository, repository->gitdir);
Vicent Marti committed
2138

2139 2140 2141 2142 2143 2144 2145 2146 2147 2148
		if (backend->gitpath == NULL)
			goto fail;
	}

	if (repository->commondir) {
		backend->commonpath = setup_namespace(repository, repository->commondir);

		if (backend->commonpath == NULL)
			goto fail;
	}
2149

2150
	if (git_buf_joinpath(&gitpath, backend->commonpath, GIT_PACKEDREFS_FILE) < 0 ||
2151 2152
		git_sortedcache_new(
			&backend->refcache, offsetof(struct packref, name),
2153
			NULL, NULL, packref_cmp, git_buf_cstr(&gitpath)) < 0)
2154 2155
		goto fail;

2156
	git_buf_dispose(&gitpath);
2157

2158
	if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_IGNORECASE) && t) {
2159 2160 2161
		backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE;
		backend->direach_flags  |= GIT_PATH_DIR_IGNORE_CASE;
	}
2162
	if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_PRECOMPOSE) && t) {
2163 2164 2165
		backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
		backend->direach_flags  |= GIT_PATH_DIR_PRECOMPOSE_UNICODE;
	}
2166
	if ((!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_FSYNCOBJECTFILES) && t) ||
2167
		git_repository__fsync_gitdir)
2168
		backend->fsync = 1;
2169
	backend->iterator_flags |= GIT_ITERATOR_DESCEND_SYMLINKS;
2170

2171 2172
	backend->parent.exists = &refdb_fs_backend__exists;
	backend->parent.lookup = &refdb_fs_backend__lookup;
2173
	backend->parent.iterator = &refdb_fs_backend__iterator;
2174
	backend->parent.write = &refdb_fs_backend__write;
2175
	backend->parent.del = &refdb_fs_backend__delete;
Vicent Marti committed
2176
	backend->parent.rename = &refdb_fs_backend__rename;
2177
	backend->parent.compress = &refdb_fs_backend__compress;
2178 2179
	backend->parent.lock = &refdb_fs_backend__lock;
	backend->parent.unlock = &refdb_fs_backend__unlock;
2180
	backend->parent.has_log = &refdb_reflog_fs__has_log;
2181
	backend->parent.ensure_log = &refdb_reflog_fs__ensure_log;
2182
	backend->parent.free = &refdb_fs_backend__free;
2183 2184 2185 2186
	backend->parent.reflog_read = &refdb_reflog_fs__read;
	backend->parent.reflog_write = &refdb_reflog_fs__write;
	backend->parent.reflog_rename = &refdb_reflog_fs__rename;
	backend->parent.reflog_delete = &refdb_reflog_fs__delete;
2187 2188 2189

	*backend_out = (git_refdb_backend *)backend;
	return 0;
2190 2191

fail:
2192
	git_buf_dispose(&gitpath);
2193
	git__free(backend->gitpath);
2194
	git__free(backend->commonpath);
2195 2196
	git__free(backend);
	return -1;
2197
}