refdb_fs.c 48.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
/*
 * 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"
#include "fileops.h"
12
#include "filebuf.h"
13 14 15 16
#include "pack.h"
#include "reflog.h"
#include "refdb.h"
#include "refdb_fs.h"
17
#include "iterator.h"
18
#include "sortedcache.h"
19
#include "signature.h"
20 21 22 23

#include <git2/tag.h>
#include <git2/object.h>
#include <git2/refdb.h>
24
#include <git2/branch.h>
25
#include <git2/sys/refdb_backend.h>
26
#include <git2/sys/refs.h>
27
#include <git2/sys/reflog.h>
28

29
GIT__USE_STRMAP
30 31 32 33 34

#define DEFAULT_NESTING_LEVEL	5
#define MAX_NESTING_LEVEL		10

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

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

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;
Vicent Marti committed
58
	char *path;
59

60
	git_sortedcache *refcache;
61
	int peeling_mode;
62 63
	git_iterator_flag_t iterator_flags;
	uint32_t direach_flags;
64 65
} refdb_fs_backend;

66 67
static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name);

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

74
static int packed_reload(refdb_fs_backend *backend)
75
{
76 77 78
	int error;
	git_buf packedrefs = GIT_BUF_INIT;
	char *scan, *eof, *eol;
79

80
	if (!backend->path)
81 82
		return 0;

83
	error = git_sortedcache_lockandload(backend->refcache, &packedrefs);
84 85

	/*
86 87 88 89
	 * 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.
90
	 */
91 92 93 94 95 96 97
	if (error <= 0) {
		if (error == GIT_ENOTFOUND) {
			git_sortedcache_clear(backend->refcache, true);
			giterr_clear();
			error = 0;
		}
		return error;
98 99
	}

100
	/* At this point, refresh the packed refs from the loaded buffer. */
101

102
	git_sortedcache_clear(backend->refcache, false);
103

104 105
	scan = (char *)packedrefs.ptr;
	eof  = scan + packedrefs.size;
106

107 108
	backend->peeling_mode = PEELING_NONE;

109
	if (*scan == '#') {
nulltoken committed
110
		static const char *traits_header = "# pack-refs with: ";
111

112 113 114
		if (git__prefixcmp(scan, traits_header) == 0) {
			scan += strlen(traits_header);
			eol = strchr(scan, '\n');
115

116
			if (!eol)
117
				goto parse_failed;
118
			*eol = '\0';
119

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

126
			scan = eol + 1;
127 128 129
		}
	}

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

136 137 138 139 140
	while (scan < eof) {
		struct packref *ref;
		git_oid oid;

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

142
		if (git_oid_fromstr(&oid, scan) < 0)
143
			goto parse_failed;
144
		scan += GIT_OID_HEXSZ;
145

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

154
		if (git_sortedcache_upsert((void **)&ref, backend->refcache, scan) < 0)
155
			goto parse_failed;
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
		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;
180 181
	}

182
	git_sortedcache_wunlock(backend->refcache);
183 184
	git_buf_free(&packedrefs);

185 186 187
	return 0;

parse_failed:
188
	giterr_set(GITERR_REFERENCE, "corrupted packed references file");
189 190

	git_sortedcache_clear(backend->refcache, false);
191
	git_sortedcache_wunlock(backend->refcache);
192 193
	git_buf_free(&packedrefs);

194 195 196
	return -1;
}

197 198
static int loose_parse_oid(
	git_oid *oid, const char *filename, git_buf *file_content)
199
{
200
	const char *str = git_buf_cstr(file_content);
201

202
	if (git_buf_len(file_content) < GIT_OID_HEXSZ)
203 204 205
		goto corrupted;

	/* we need to get 40 OID characters from the file */
206
	if (git_oid_fromstr(oid, str) < 0)
207 208 209 210 211 212 213 214
		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:
215
	giterr_set(GITERR_REFERENCE, "corrupted loose reference file: %s", filename);
216 217 218
	return -1;
}

219 220 221 222 223 224 225 226 227 228 229 230 231
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)
		git_buf_free(buf);

	return error;
}

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

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

246
	/* skip symbolic refs */
247 248
	if (!git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF))
		goto done;
249

250 251 252
	/* parse OID from file */
	if ((error = loose_parse_oid(&oid, name, &ref_file)) < 0)
		goto done;
253

254
	git_sortedcache_wlock(backend->refcache);
255

256 257
	if (!(error = git_sortedcache_upsert(
			(void **)&ref, backend->refcache, name))) {
258

259 260
		git_oid_cpy(&ref->oid, &oid);
		ref->flags = PACKREF_WAS_LOOSE;
261 262
	}

263
	git_sortedcache_wunlock(backend->refcache);
264

265
done:
266
	git_buf_free(&ref_file);
267
	return error;
268 269
}

270
static int _dirent_loose_load(void *payload, git_buf *full_path)
271
{
272
	refdb_fs_backend *backend = payload;
273 274
	const char *file_path;

275 276 277
	if (git__suffixcmp(full_path->ptr, ".lock") == 0)
		return 0;

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

		return error;
	}
289

290
	file_path = full_path->ptr + strlen(backend->path);
291

292
	return loose_lookup_to_packfile(backend, file_path);
293 294 295 296 297 298 299 300 301 302
}

/*
 * 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)
{
303
	int error;
304 305 306 307 308 309 310 311 312 313
	git_buf refs_path = GIT_BUF_INIT;

	if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)
		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
	 */
314
	error = git_path_direach(
315
		&refs_path, backend->direach_flags, _dirent_loose_load, backend);
316

317 318
	git_buf_free(&refs_path);

319
	return error;
320 321 322 323 324 325 326
}

static int refdb_fs_backend__exists(
	int *exists,
	git_refdb_backend *_backend,
	const char *ref_name)
{
327
	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
328
	git_buf ref_path = GIT_BUF_INIT;
329
	int error;
330

331
	assert(backend);
332

333 334 335
	if ((error = packed_reload(backend)) < 0 ||
		(error = git_buf_joinpath(&ref_path, backend->path, ref_name)) < 0)
		return error;
336

337
	*exists = git_path_isfile(ref_path.ptr) ||
338
		(git_sortedcache_lookup(backend->refcache, ref_name) != NULL);
339 340 341 342 343 344 345 346 347 348 349 350 351

	git_buf_free(&ref_path);
	return 0;
}

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) {
352
		giterr_set(GITERR_REFERENCE, "corrupted loose reference file");
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
		return NULL;
	}

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

	return refname_start;
}

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;

373 374 375
	if (out)
		*out = NULL;

376 377 378 379
	if ((error = loose_readbuffer(&ref_file, backend->path, ref_name)) < 0)
		/* cannot read loose ref file - gah */;
	else if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) {
		const char *target;
380 381 382

		git_buf_rtrim(&ref_file);

383
		if (!(target = loose_parse_symbolic(&ref_file)))
384
			error = -1;
385
		else if (out != NULL)
386
			*out = git_reference__alloc_symbolic(ref_name, target);
387
	} else {
388
		git_oid oid;
389

390 391
		if (!(error = loose_parse_oid(&oid, ref_name, &ref_file)) &&
			out != NULL)
392
			*out = git_reference__alloc(ref_name, &oid, NULL);
393 394 395 396 397 398
	}

	git_buf_free(&ref_file);
	return error;
}

399
static int ref_error_notfound(const char *name)
400
{
401
	giterr_set(GITERR_REFERENCE, "reference '%s' not found", name);
402
	return GIT_ENOTFOUND;
403 404 405 406 407 408 409 410
}

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

413 414
	if ((error = packed_reload(backend)) < 0)
		return error;
415

416 417
	if (git_sortedcache_rlock(backend->refcache) < 0)
		return -1;
418 419 420 421 422 423 424 425 426 427

	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;
	}

428 429
	git_sortedcache_runlock(backend->refcache);

430
	return error;
431 432 433 434 435 436 437
}

static int refdb_fs_backend__lookup(
	git_reference **out,
	git_refdb_backend *_backend,
	const char *ref_name)
{
438 439
	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
	int error;
440

441
	assert(backend);
442

443
	if (!(error = loose_lookup(out, backend, ref_name)))
444 445 446 447
		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 */
448
	if (error == GIT_ENOTFOUND) {
449
		giterr_clear();
450
		error = packed_lookup(out, backend, ref_name);
451 452
	}

453
	return error;
454 455
}

456 457
typedef struct {
	git_reference_iterator parent;
458

459
	char *glob;
460 461

	git_pool pool;
462
	git_vector loose;
463

464
	git_sortedcache *cache;
465 466
	size_t loose_pos;
	size_t packed_pos;
467 468 469
} refdb_fs_iter;

static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter)
470
{
471
	refdb_fs_iter *iter = (refdb_fs_iter *) _iter;
472 473

	git_vector_free(&iter->loose);
474
	git_pool_clear(&iter->pool);
475
	git_sortedcache_free(iter->cache);
476 477
	git__free(iter);
}
478

479
static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter)
480
{
481
	int error = 0;
482
	git_buf path = GIT_BUF_INIT;
483
	git_iterator *fsit = NULL;
484
	git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT;
485
	const git_index_entry *entry = NULL;
486

487 488 489
	if (!backend->path) /* do nothing if no path for loose refs */
		return 0;

490 491
	fsit_opts.flags = backend->iterator_flags;

492
	if ((error = git_buf_printf(&path, "%s/refs", backend->path)) < 0 ||
493
		(error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) {
494 495 496
		git_buf_free(&path);
		return error;
	}
497

498
	error = git_buf_sets(&path, GIT_REFS_DIR);
499

500
	while (!error && !git_iterator_advance(&entry, fsit)) {
501
		const char *ref_name;
502
		struct packref *ref;
503
		char *ref_dup;
504

505 506 507
		git_buf_truncate(&path, strlen(GIT_REFS_DIR));
		git_buf_puts(&path, entry->path);
		ref_name = git_buf_cstr(&path);
508

509
		if (git__suffixcmp(ref_name, ".lock") == 0 ||
510
			(iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0))
511
			continue;
512

513
		git_sortedcache_rlock(backend->refcache);
514 515
		ref = git_sortedcache_lookup(backend->refcache, ref_name);
		if (ref)
516
			ref->flags |= PACKREF_SHADOWED;
517
		git_sortedcache_runlock(backend->refcache);
518

519 520
		ref_dup = git_pool_strdup(&iter->pool, ref_name);
		if (!ref_dup)
521
			error = -1;
522 523
		else
			error = git_vector_insert(&iter->loose, ref_dup);
524
	}
525

526 527
	git_iterator_free(fsit);
	git_buf_free(&path);
528

529
	return error;
530 531
}

532 533
static int refdb_fs_backend__iterator_next(
	git_reference **out, git_reference_iterator *_iter)
534
{
535
	int error = GIT_ITEROVER;
536
	refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
537
	refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
538
	struct packref *ref;
539

Vicent Marti committed
540
	while (iter->loose_pos < iter->loose.length) {
541
		const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
Vicent Marti committed
542 543 544 545 546

		if (loose_lookup(out, backend, path) == 0)
			return 0;

		giterr_clear();
547
	}
548

549 550 551 552
	if (!iter->cache) {
		if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0)
			return error;
	}
553

554 555 556
	error = GIT_ITEROVER;
	while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) {
		ref = git_sortedcache_entry(iter->cache, iter->packed_pos++);
557 558
		if (!ref) /* stop now if another thread deleted refs and we past end */
			break;
559 560 561 562 563

		if (ref->flags & PACKREF_SHADOWED)
			continue;
		if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0)
			continue;
564

Vicent Marti committed
565
		*out = git_reference__alloc(ref->name, &ref->oid, &ref->peel);
566 567
		error = (*out != NULL) ? 0 : -1;
		break;
568 569
	}

570
	return error;
571 572
}

573 574 575
static int refdb_fs_backend__iterator_next_name(
	const char **out, git_reference_iterator *_iter)
{
576
	int error = GIT_ITEROVER;
577 578
	refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
	refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
579
	struct packref *ref;
580 581 582 583

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

584 585 586
		if (loose_lookup(NULL, backend, path) == 0) {
			*out = path;
			return 0;
587 588
		}

589
		giterr_clear();
590 591
	}

592 593 594 595
	if (!iter->cache) {
		if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0)
			return error;
	}
596

597 598 599
	error = GIT_ITEROVER;
	while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) {
		ref = git_sortedcache_entry(iter->cache, iter->packed_pos++);
600 601
		if (!ref) /* stop now if another thread deleted refs and we past end */
			break;
602 603 604

		if (ref->flags & PACKREF_SHADOWED)
			continue;
605
		if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0)
606 607
			continue;

608 609 610
		*out = ref->name;
		error = 0;
		break;
611 612
	}

613
	return error;
614 615 616 617 618
}

static int refdb_fs_backend__iterator(
	git_reference_iterator **out, git_refdb_backend *_backend, const char *glob)
{
619
	int error;
620
	refdb_fs_iter *iter;
621
	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
622

623
	assert(backend);
624

625 626
	if ((error = packed_reload(backend)) < 0)
		return error;
627 628 629 630

	iter = git__calloc(1, sizeof(refdb_fs_iter));
	GITERR_CHECK_ALLOC(iter);

631 632 633
	git_pool_init(&iter->pool, 1);

	if (git_vector_init(&iter->loose, 8, NULL) < 0)
634 635 636 637 638
		goto fail;

	if (glob != NULL &&
		(iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL)
		goto fail;
639 640 641 642 643

	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;

644 645
	if (iter_load_loose_paths(backend, iter) < 0)
		goto fail;
646 647 648

	*out = (git_reference_iterator *)iter;
	return 0;
649 650 651 652

fail:
	refdb_fs_backend__iterator_free((git_reference_iterator *)iter);
	return -1;
653 654
}

Vicent Marti committed
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677
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)
{
678
	size_t i;
679
	int error;
Vicent Marti committed
680

681 682
	if ((error = packed_reload(backend)) < 0)
		return error;
Vicent Marti committed
683 684 685 686

	if (!force) {
		int exists;

687 688 689 690
		if ((error = refdb_fs_backend__exists(
			&exists, (git_refdb_backend *)backend, new_ref)) < 0) {
			return error;
		}
Vicent Marti committed
691 692 693

		if (exists) {
			giterr_set(GITERR_REFERENCE,
694
				"failed to write reference '%s': a reference with "
695
				"that name already exists.", new_ref);
Vicent Marti committed
696 697 698 699
			return GIT_EEXISTS;
		}
	}

700
	git_sortedcache_rlock(backend->refcache);
701 702

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

705 706
		if (ref && !ref_is_available(old_ref, new_ref, ref->name)) {
			git_sortedcache_runlock(backend->refcache);
Vicent Marti committed
707
			giterr_set(GITERR_REFERENCE,
708
				"path to reference '%s' collides with existing one", new_ref);
Vicent Marti committed
709 710
			return -1;
		}
711
	}
712

713
	git_sortedcache_runlock(backend->refcache);
Vicent Marti committed
714 715
	return 0;
}
716

717
static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name)
718
{
719
	int error;
720 721
	git_buf ref_path = GIT_BUF_INIT;

722
	assert(file && backend && name);
723

724
	if (!git_path_isvalid(backend->repo, name, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
725
		giterr_set(GITERR_INVALID, "invalid reference name '%s'", name);
726 727 728
		return GIT_EINVALIDSPEC;
	}

729 730 731
	/* Remove a possibly existing empty directory hierarchy
	 * which name would collide with the reference name
	 */
732 733
	if ((error = git_futils_rmdir_r(name, backend->path, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
		return error;
734

735
	if (git_buf_joinpath(&ref_path, backend->path, name) < 0)
736 737
		return -1;

738
	error = git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE);
739

740 741 742
	if (error == GIT_EDIRECTORY)
		giterr_set(GITERR_REFERENCE, "cannot lock ref '%s', there are refs beneath that folder", name);

743
	git_buf_free(&ref_path);
744
	return error;
745
}
746

747 748
static int loose_commit(git_filebuf *file, const git_reference *ref)
{
749 750
	assert(file && ref);

751 752
	if (ref->type == GIT_REF_OID) {
		char oid[GIT_OID_HEXSZ + 1];
753
		git_oid_nfmt(oid, sizeof(oid), &ref->target.oid);
754

755
		git_filebuf_printf(file, "%s\n", oid);
756
	} else if (ref->type == GIT_REF_SYMBOLIC) {
757
		git_filebuf_printf(file, GIT_SYMREF "%s\n", ref->target.symbolic);
758 759 760 761
	} else {
		assert(0); /* don't let this happen */
	}

762
	return git_filebuf_commit(file);
763 764
}

765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815
static int refdb_fs_backend__lock(void **out, git_refdb_backend *_backend, const char *refname)
{
	int error;
	git_filebuf *lock;
	refdb_fs_backend *backend = (refdb_fs_backend *) _backend;

	lock = git__calloc(1, sizeof(git_filebuf));
	GITERR_CHECK_ALLOC(lock);

	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_signature *who,
	const char *message,
	const git_oid *old_id,
	const char *old_target);

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);

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)
		error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, sig, message, NULL, NULL);
	else
		git_filebuf_cleanup(lock);

	git__free(lock);
	return error;
}

816 817 818 819 820 821 822 823 824 825 826 827
/*
 * 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;

828
	if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL)
829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
		return 0;

	/*
	 * Find the tagged object in the repository
	 */
	if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJ_ANY) < 0)
		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.
	 */
	if (git_object_type(object) == GIT_OBJ_TAG) {
		git_tag *tag = (git_tag *)object;

		/*
		 * Find the object pointed at by this tag
		 */
		git_oid_cpy(&ref->peel, git_tag_target_id(tag));
849
		ref->flags |= PACKREF_HAS_PEEL;
850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867

		/*
		 * 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];
868
	git_oid_nfmt(oid, sizeof(oid), &ref->oid);
869 870 871 872 873 874 875 876 877 878 879

	/*
	 * 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`.
	 */
880
	if (ref->flags & PACKREF_HAS_PEEL) {
881
		char peel[GIT_OID_HEXSZ + 1];
882
		git_oid_nfmt(peel, sizeof(peel), &ref->peel);
883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904

		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.
 */
905
static int packed_remove_loose(refdb_fs_backend *backend)
906
{
907
	size_t i;
908
	git_filebuf lock = GIT_FILEBUF_INIT;
909
	git_buf ref_content = GIT_BUF_INIT;
910
	int error = 0;
911

912 913 914 915
	/* 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);
916
		git_oid current_id;
917

918
		if (!ref || !(ref->flags & PACKREF_WAS_LOOSE))
919 920
			continue;

921 922
		git_filebuf_cleanup(&lock);

923 924 925
		/* 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 */
926
		if (error == GIT_EEXISTS || error == GIT_ENOTFOUND)
927 928 929
			continue;

		if (error < 0) {
930
			git_buf_free(&ref_content);
931 932
			giterr_set(GITERR_REFERENCE, "failed to lock loose reference '%s'", ref->name);
			return error;
933 934 935 936
		}

		error = git_futils_readbuffer(&ref_content, lock.path_original);
		/* Someone else beat us to cleaning up the ref, let's simply continue */
937
		if (error == GIT_ENOTFOUND)
938 939 940
			continue;

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

944 945
		/* 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)
946
			continue;
947

948
		/* If the ref moved since we packed it, we must not delete it */
949
		if (!git_oid_equal(&current_id, &ref->oid))
950 951
			continue;

952 953 954
		/*
		 * if we fail to remove a single file, this is *not* good,
		 * but we should keep going and remove as many as possible.
955 956
		 * If we fail to remove, the ref is still in the old state, so
		 * we haven't lost information.
957
		 */
958
		p_unlink(lock.path_original);
959 960
	}

961
	git_buf_free(&ref_content);
962
	git_filebuf_cleanup(&lock);
963
	return 0;
964 965 966 967 968 969 970
}

/*
 * Write all the contents in the in-memory packfile to disk.
 */
static int packed_write(refdb_fs_backend *backend)
{
971
	git_sortedcache *refcache = backend->refcache;
972
	git_filebuf pack_file = GIT_FILEBUF_INIT;
973
	int error;
974
	size_t i;
975

976
	/* lock the cache to updates while we do this */
977 978
	if ((error = git_sortedcache_wlock(refcache)) < 0)
		return error;
979

980
	/* Open the file! */
981
	if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), 0, GIT_PACKEDREFS_FILE_MODE)) < 0)
982
		goto fail;
983 984 985 986

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

990 991
	for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) {
		struct packref *ref = git_sortedcache_entry(refcache, i);
992
		assert(ref);
993

994
		if ((error = packed_find_peel(backend, ref)) < 0)
995
			goto fail;
996

997
		if ((error = packed_write_ref(ref, &pack_file)) < 0)
998
			goto fail;
999 1000 1001 1002
	}

	/* if we've written all the references properly, we can commit
	 * the packfile to make the changes effective */
1003
	if ((error = git_filebuf_commit(&pack_file)) < 0)
1004
		goto fail;
1005 1006 1007

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

1011 1012
	git_sortedcache_updated(refcache);
	git_sortedcache_wunlock(refcache);
1013 1014 1015 1016

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

1017
fail:
1018
	git_filebuf_cleanup(&pack_file);
1019
	git_sortedcache_wunlock(refcache);
1020

1021
	return error;
1022 1023
}

1024
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);
1025 1026 1027
static int has_reflog(git_repository *repo, const char *name);

/* We only write if it's under heads/, remotes/ or notes/ or if it already has a log */
1028
static int should_write_reflog(int *write, git_repository *repo, const char *name)
1029
{
1030
	int error, logall;
1031

1032 1033
	error = git_repository__cvar(&logall, repo, GIT_CVAR_LOGALLREFUPDATES);
	if (error < 0)
1034 1035
		return error;

1036 1037 1038
	/* Defaults to the opposite of the repo being bare */
	if (logall == GIT_LOGALLREFUPDATES_UNSET)
		logall = !git_repository_is_bare(repo);
1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051

	if (!logall) {
		*write = 0;
	} else if (has_reflog(repo, name)) {
		*write = 1;
	} else if (!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)) {
		*write = 1;
	} else {
		*write = 0;
	}
1052 1053 1054

	return 0;
}
1055

1056
static int cmp_old_ref(int *cmp, git_refdb_backend *backend, const char *name,
1057 1058 1059 1060 1061 1062
	const git_oid *old_id, const char *old_target)
{
	int error = 0;
	git_reference *old_ref = NULL;

	*cmp = 0;
1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
	/* 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 */
	if (old_id && old_ref->type != GIT_REF_OID) {
		*cmp = -1;
		goto out;
	}
	if (old_target && old_ref->type != GIT_REF_SYMBOLIC) {
		*cmp = 1;
		goto out;
1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091
	}

	if (old_id && old_ref->type == GIT_REF_OID)
		*cmp = git_oid_cmp(old_id, &old_ref->target.oid);

	if (old_target && old_ref->type == GIT_REF_SYMBOLIC)
		*cmp = git__strcmp(old_target, old_ref->target.symbolic);

out:
	git_reference_free(old_ref);

	return error;
}

1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
/*
 * 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;
1109
	git_oid old_id = {{0}};
1110 1111 1112 1113 1114
	git_reference *tmp = NULL, *head = NULL, *peeled = NULL;
	const char *name;

	if (ref->type == GIT_REF_SYMBOLIC)
		return 0;
1115

1116 1117
	/* if we can't resolve, we use {0}*40 as old id */
	git_reference_name_to_id(&old_id, backend->repo, ref->name);
1118

1119
	if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0)
1120 1121
		return error;

1122 1123 1124
	if (git_reference_type(head) == GIT_REF_OID)
		goto cleanup;

1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147
	if ((error = git_reference_lookup(&tmp, backend->repo, GIT_HEAD_FILE)) < 0)
		goto cleanup;

	/* Go down the symref chain until we find the branch */
	while (git_reference_type(tmp) == GIT_REF_SYMBOLIC) {
		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))
1148 1149
		goto cleanup;

1150 1151
	error = reflog_append(backend, head, &old_id, git_reference_target(ref), who, message);

1152
cleanup:
1153
	git_reference_free(tmp);
1154 1155 1156 1157
	git_reference_free(head);
	return error;
}

1158 1159
static int refdb_fs_backend__write(
	git_refdb_backend *_backend,
Vicent Marti committed
1160
	const git_reference *ref,
1161
	int force,
1162
	const git_signature *who,
1163
	const char *message,
1164 1165
	const git_oid *old_id,
	const char *old_target)
1166
{
1167
	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
1168
	git_filebuf file = GIT_FILEBUF_INIT;
1169
	int error = 0;
1170

1171
	assert(backend);
1172

1173
	if ((error = reference_path_available(backend, ref->name, NULL, force)) < 0)
Vicent Marti committed
1174 1175
		return error;

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

1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197
	return refdb_fs_backend__write_tail(_backend, ref, &file, true, who, message, old_id, old_target);
}

static int refdb_fs_backend__write_tail(
	git_refdb_backend *_backend,
	const git_reference *ref,
	git_filebuf *file,
	int update_reflog,
	const git_signature *who,
	const char *message,
	const git_oid *old_id,
	const char *old_target)
{
	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
	int error = 0, cmp = 0, should_write;
	const char *new_target = NULL;
	const git_oid *new_id = NULL;

1198
	if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0)
1199
		goto on_error;
1200 1201 1202 1203 1204

	if (cmp) {
		giterr_set(GITERR_REFERENCE, "old reference value does not match");
		error = GIT_EMODIFIED;
		goto on_error;
1205 1206
	}

1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221
	if (ref->type == GIT_REF_SYMBOLIC)
		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 */
	}

1222 1223
	if (update_reflog) {
		if ((error = should_write_reflog(&should_write, backend->repo, ref->name)) < 0)
1224
			goto on_error;
1225 1226 1227 1228 1229 1230 1231

		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;
		}
1232 1233
	}

1234
	return loose_commit(file, ref);
1235 1236

on_error:
1237
        git_filebuf_cleanup(file);
1238
        return error;
1239 1240 1241 1242
}

static int refdb_fs_backend__delete(
	git_refdb_backend *_backend,
1243 1244
	const char *ref_name,
	const git_oid *old_id, const char *old_target)
1245
{
1246
	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
1247
	git_filebuf file = GIT_FILEBUF_INIT;
1248
	int error = 0;
1249

1250
	assert(backend && ref_name);
1251

1252 1253 1254
	if ((error = loose_lock(&file, backend, ref_name)) < 0)
		return error;

1255 1256 1257 1258 1259
	if ((error = refdb_reflog_fs__delete(_backend, ref_name)) < 0) {
		git_filebuf_cleanup(&file);
		return error;
	}

1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274
	return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target);
}

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)
{
	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
	git_buf loose_path = GIT_BUF_INIT;
	size_t pack_pos;
	int error = 0, cmp = 0;
	bool loose_deleted = 0;

1275 1276 1277 1278 1279 1280 1281 1282 1283 1284
	error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target);
	if (error < 0)
		goto cleanup;

	if (cmp) {
		giterr_set(GITERR_REFERENCE, "old reference value does not match");
		error = GIT_EMODIFIED;
		goto cleanup;
	}

1285
	/* If a loose reference exists, remove it from the filesystem */
Vicent Marti committed
1286
	if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0)
1287 1288 1289
		return -1;


1290 1291 1292 1293
	error = p_unlink(loose_path.ptr);
	if (error < 0 && errno == ENOENT)
		error = 0;
	else if (error < 0)
1294
		goto cleanup;
1295 1296
	else if (error == 0)
		loose_deleted = 1;
1297

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

1301
	/* If a packed reference exists, remove it from the packfile and repack */
1302 1303
	if ((error = git_sortedcache_wlock(backend->refcache)) < 0)
		goto cleanup;
Vicent Marti committed
1304

1305 1306
	if (!(error = git_sortedcache_lookup_index(
			&pack_pos, backend->refcache, ref_name)))
1307
		error = git_sortedcache_remove(backend->refcache, pack_pos);
1308

1309
	git_sortedcache_wunlock(backend->refcache);
1310

1311 1312 1313 1314 1315 1316
	if (error == GIT_ENOTFOUND) {
		error = loose_deleted ? 0 : ref_error_notfound(ref_name);
		goto cleanup;
	}

	error = packed_write(backend);
1317

1318
cleanup:
1319
	git_buf_free(&loose_path);
1320
	git_filebuf_cleanup(file);
1321 1322

	return error;
1323 1324
}

1325 1326
static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name);

Vicent Marti committed
1327 1328 1329 1330 1331
static int refdb_fs_backend__rename(
	git_reference **out,
	git_refdb_backend *_backend,
	const char *old_name,
	const char *new_name,
1332
	int force,
1333
	const git_signature *who,
1334
	const char *message)
Vicent Marti committed
1335
{
1336
	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
Vicent Marti committed
1337
	git_reference *old, *new;
1338
	git_filebuf file = GIT_FILEBUF_INIT;
Vicent Marti committed
1339 1340
	int error;

1341
	assert(backend);
Vicent Marti committed
1342

1343 1344 1345
	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
1346 1347
		return error;

1348
	if ((error = refdb_fs_backend__delete(_backend, old_name, NULL, NULL)) < 0) {
Vicent Marti committed
1349 1350 1351 1352
		git_reference_free(old);
		return error;
	}

1353 1354 1355 1356
	new = git_reference__set_name(old, new_name);
	if (!new) {
		git_reference_free(old);
		return -1;
Vicent Marti committed
1357 1358
	}

1359
	if ((error = loose_lock(&file, backend, new->name)) < 0) {
1360 1361 1362 1363 1364 1365 1366
		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)) &&
1367
	    ((error = reflog_append(backend, new, git_reference_target(new), NULL, who, message)) < 0)) {
1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380
		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
1381
		git_reference_free(new);
1382
		return error;
Vicent Marti committed
1383 1384
	}

1385
	*out = new;
Vicent Marti committed
1386 1387 1388
	return 0;
}

1389 1390
static int refdb_fs_backend__compress(git_refdb_backend *_backend)
{
1391
	int error;
1392
	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
1393

1394
	assert(backend);
1395

1396 1397 1398 1399
	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;
1400 1401 1402 1403 1404 1405

	return 0;
}

static void refdb_fs_backend__free(git_refdb_backend *_backend)
{
1406
	refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
1407

1408
	assert(backend);
1409

1410
	git_sortedcache_free(backend->refcache);
Vicent Marti committed
1411
	git__free(backend->path);
1412 1413 1414
	git__free(backend);
}

1415 1416
static int setup_namespace(git_buf *path, git_repository *repo)
{
nulltoken committed
1417
	char *parts, *start, *end;
1418

1419 1420 1421 1422
	/* Not all repositories have a path */
	if (repo->path_repository == NULL)
		return 0;

1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433
	/* Load the path to the repo first */
	git_buf_puts(path, repo->path_repository);

	/* if the repo is not namespaced, nothing else to do */
	if (repo->namespace == NULL)
		return 0;

	parts = end = git__strdup(repo->namespace);
	if (parts == NULL)
		return -1;

1434
	/*
1435 1436 1437 1438 1439 1440 1441 1442 1443 1444
	 * 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/
	 */
	while ((start = git__strsep(&end, "/")) != NULL) {
		git_buf_printf(path, "refs/namespaces/%s/", start);
	}

	git_buf_printf(path, "refs/namespaces/%s/refs", end);
Vicent Marti committed
1445
	git__free(parts);
1446 1447

	/* Make sure that the folder with the namespace exists */
1448 1449
	if (git_futils_mkdir_relative(git_buf_cstr(path), repo->path_repository,
			0777, GIT_MKDIR_PATH, NULL) < 0)
1450 1451
		return -1;

1452
	/* Return root of the namespaced path, i.e. without the trailing '/refs' */
1453 1454 1455 1456
	git_buf_rtruncate_at_char(path, '/');
	return 0;
}

1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486
static int reflog_alloc(git_reflog **reflog, const char *name)
{
	git_reflog *log;

	*reflog = NULL;

	log = git__calloc(1, sizeof(git_reflog));
	GITERR_CHECK_ALLOC(log);

	log->ref_name = git__strdup(name);
	GITERR_CHECK_ALLOC(log->ref_name);

	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)
{
	const char *ptr;
	git_reflog_entry *entry;

#define seek_forward(_increase) do { \
	if (_increase >= buf_size) { \
1487
		giterr_set(GITERR_INVALID, "ran out of data while parsing reflog"); \
1488 1489 1490 1491 1492 1493 1494 1495 1496 1497
		goto fail; \
	} \
	buf += _increase; \
	buf_size -= _increase; \
	} while (0)

	while (buf_size > GIT_REFLOG_SIZE_MIN) {
		entry = git__calloc(1, sizeof(git_reflog_entry));
		GITERR_CHECK_ALLOC(entry);

1498
		entry->committer = git__calloc(1, sizeof(git_signature));
1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542
		GITERR_CHECK_ALLOC(entry->committer);

		if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < 0)
			goto fail;
		seek_forward(GIT_OID_HEXSZ + 1);

		if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < 0)
			goto fail;
		seek_forward(GIT_OID_HEXSZ + 1);

		ptr = buf;

		/* Seek forward to the end of the signature. */
		while (*buf && *buf != '\t' && *buf != '\n')
			seek_forward(1);

		if (git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf) < 0)
			goto fail;

		if (*buf == '\t') {
			/* We got a message. Read everything till we reach LF. */
			seek_forward(1);
			ptr = buf;

			while (*buf && *buf != '\n')
				seek_forward(1);

			entry->msg = git__strndup(ptr, buf - ptr);
			GITERR_CHECK_ALLOC(entry->msg);
		} else
			entry->msg = NULL;

		while (*buf && *buf == '\n' && buf_size > 1)
			seek_forward(1);

		if (git_vector_insert(&log->entries, entry) < 0)
			goto fail;
	}

	return 0;

#undef seek_forward

fail:
1543
	git_reflog_entry__free(entry);
1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555

	return -1;
}

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,
1556
			O_WRONLY | O_CREAT,
1557 1558 1559 1560 1561 1562 1563 1564
			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)
{
1565
	return git_buf_join3(path, '/', repo->path_repository, GIT_REFLOG_DIR, name);
1566 1567
}

1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582
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);

	backend = (refdb_fs_backend *) _backend;
	repo = backend->repo;

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

1583 1584 1585 1586
	error = create_new_reflog_file(git_buf_cstr(&path));
	git_buf_free(&path);

	return error;
1587 1588
}

1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603
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:
	git_buf_free(&path);
	return ret;
}

1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614
static int refdb_reflog_fs__has_log(git_refdb_backend *_backend, const char *name)
{
	refdb_fs_backend *backend;

	assert(_backend && name);

	backend = (refdb_fs_backend *) _backend;

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

1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693
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);

	backend = (refdb_fs_backend *) _backend;
	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;
 
	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:
	git_buf_free(&log_file);
	git_buf_free(&log_path);

	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) {
		git_buf_putc(buf, '\t');
		git_buf_puts(buf, msg);
	}

	git_buf_putc(buf, '\n');

	return git_buf_oom(buf);
}

1694 1695 1696 1697 1698 1699 1700 1701
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;

1702
	if (!git_path_isvalid(backend->repo, refname, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
1703
		giterr_set(GITERR_INVALID, "invalid reference name '%s'", refname);
1704 1705 1706
		return GIT_EINVALIDSPEC;
	}

1707 1708 1709 1710 1711
	if (retrieve_reflog_path(&log_path, repo, refname) < 0)
		return -1;

	if (!git_path_isfile(git_buf_cstr(&log_path))) {
		giterr_set(GITERR_INVALID,
1712
			"log file for reference '%s' doesn't exist", refname);
1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724
		error = -1;
		goto cleanup;
	}

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

cleanup:
	git_buf_free(&log_path);

	return error;
}

1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737
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);

	backend = (refdb_fs_backend *) _backend;

1738
	if ((error = lock_reflog(&fbuf, backend, reflog->ref_name)) < 0)
1739 1740 1741 1742 1743 1744 1745 1746 1747 1748
		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;
	}

1749
	error = git_filebuf_commit(&fbuf);
1750 1751 1752 1753 1754 1755 1756
	goto success;

cleanup:
	git_filebuf_cleanup(&fbuf);

success:
	git_buf_free(&log);
1757

1758 1759 1760
	return error;
}

1761
/* Append to the reflog, must be called under reference lock */
1762
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)
1763
{
1764
	int error, is_symbolic;
1765
	git_oid old_id = {{0}}, new_id = {{0}};
1766 1767 1768
	git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
	git_repository *repo = backend->repo;

1769 1770 1771 1772 1773 1774
	is_symbolic = ref->type == GIT_REF_SYMBOLIC;

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

1777 1778 1779 1780
	/* From here on is_symoblic also means that it's HEAD */

	if (old) {
		git_oid_cpy(&old_id, old);
1781
	} else {
1782
		error = git_reference_name_to_id(&old_id, repo, ref->name);
1783
		if (error < 0 && error != GIT_ENOTFOUND)
1784 1785 1786
			return error;
	}

1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798
	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;
1799

1800 1801
			giterr_clear();
		}
1802
	}
1803

1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814
	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;
	}

1815 1816 1817
	/* 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.
	 */
1818
	if (git_path_isdir(git_buf_cstr(&path))) {
1819 1820 1821 1822
		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))) {
1823 1824 1825 1826 1827 1828 1829
			giterr_set(GITERR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder",
				ref->name);
			error = GIT_EDIRECTORY;
		}

		if (error != 0)
			goto cleanup;
1830 1831
	}

1832 1833 1834 1835 1836 1837
	error = git_futils_writebuffer(&buf, git_buf_cstr(&path), O_WRONLY|O_CREAT|O_APPEND, GIT_REFLOG_FILE_MODE);

cleanup:
	git_buf_free(&buf);
	git_buf_free(&path);

1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868
	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);

	backend = (refdb_fs_backend *) _backend;
	repo = backend->repo;

	if ((error = git_reference__normalize_name(
		&normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0)
			return error;

	if (git_buf_joinpath(&temp_path, repo->path_repository, GIT_REFLOG_DIR) < 0)
		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;

1869 1870 1871 1872 1873
	if (!git_path_exists(git_buf_cstr(&old_path))) {
		error = GIT_ENOTFOUND;
		goto cleanup;
	}

1874 1875 1876 1877 1878 1879 1880 1881 1882 1883
	/*
	 * 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;

1884
	if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) {
1885 1886 1887 1888 1889 1890 1891
		error = -1;
		goto cleanup;
	}

	p_close(fd);

	if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) {
1892
		giterr_set(GITERR_OS, "failed to rename reflog for %s", new_name);
1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908
		error = -1;
		goto cleanup;
	}

	if (git_path_isdir(git_buf_cstr(&new_path)) && 
		(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) {
1909
		giterr_set(GITERR_OS, "failed to rename reflog for %s", new_name);
1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945
		error = -1;
	}

cleanup:
	git_buf_free(&temp_path);
	git_buf_free(&old_path);
	git_buf_free(&new_path);
	git_buf_free(&normalized);

	return error;
}

static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name)
{
	int error;
	git_buf path = GIT_BUF_INIT;

	git_repository *repo;
	refdb_fs_backend *backend;

	assert(_backend && name);

	backend = (refdb_fs_backend *) _backend;
	repo = backend->repo;

	error = retrieve_reflog_path(&path, repo, name);

	if (!error && git_path_exists(path.ptr))
		error = p_unlink(path.ptr);

	git_buf_free(&path);

	return error;

}

1946 1947
int git_refdb_backend_fs(
	git_refdb_backend **backend_out,
1948
	git_repository *repository)
1949
{
1950
	int t = 0;
Vicent Marti committed
1951
	git_buf path = GIT_BUF_INIT;
1952 1953 1954 1955 1956 1957
	refdb_fs_backend *backend;

	backend = git__calloc(1, sizeof(refdb_fs_backend));
	GITERR_CHECK_ALLOC(backend);

	backend->repo = repository;
Vicent Marti committed
1958

1959 1960
	if (setup_namespace(&path, repository) < 0)
		goto fail;
Vicent Marti committed
1961 1962

	backend->path = git_buf_detach(&path);
1963

1964 1965 1966 1967 1968 1969 1970 1971
	if (git_buf_joinpath(&path, backend->path, GIT_PACKEDREFS_FILE) < 0 ||
		git_sortedcache_new(
			&backend->refcache, offsetof(struct packref, name),
			NULL, NULL, packref_cmp, git_buf_cstr(&path)) < 0)
		goto fail;

	git_buf_free(&path);

1972 1973 1974 1975 1976 1977 1978 1979 1980
	if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_IGNORECASE) && t) {
		backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE;
		backend->direach_flags  |= GIT_PATH_DIR_IGNORE_CASE;
	}
	if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_PRECOMPOSE) && t) {
		backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
		backend->direach_flags  |= GIT_PATH_DIR_PRECOMPOSE_UNICODE;
	}

1981 1982
	backend->parent.exists = &refdb_fs_backend__exists;
	backend->parent.lookup = &refdb_fs_backend__lookup;
1983
	backend->parent.iterator = &refdb_fs_backend__iterator;
1984
	backend->parent.write = &refdb_fs_backend__write;
1985
	backend->parent.del = &refdb_fs_backend__delete;
Vicent Marti committed
1986
	backend->parent.rename = &refdb_fs_backend__rename;
1987
	backend->parent.compress = &refdb_fs_backend__compress;
1988 1989
	backend->parent.lock = &refdb_fs_backend__lock;
	backend->parent.unlock = &refdb_fs_backend__unlock;
1990
	backend->parent.has_log = &refdb_reflog_fs__has_log;
1991
	backend->parent.ensure_log = &refdb_reflog_fs__ensure_log;
1992
	backend->parent.free = &refdb_fs_backend__free;
1993 1994 1995 1996
	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;
1997 1998 1999

	*backend_out = (git_refdb_backend *)backend;
	return 0;
2000 2001 2002 2003 2004 2005

fail:
	git_buf_free(&path);
	git__free(backend->path);
	git__free(backend);
	return -1;
2006
}