refdb_fs.c 61.4 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
#include "path.h"
22 23 24 25

#include <git2/tag.h>
#include <git2/object.h>
#include <git2/refdb.h>
26
#include <git2/branch.h>
27
#include <git2/sys/refdb_backend.h>
28
#include <git2/sys/refs.h>
29
#include <git2/sys/reflog.h>
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
	PACKREF_CANNOT_PEEL = 4,
38
	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;
58 59
	/* path to git directory */
	char *gitpath;
60 61
	/* path to common objects' directory */
	char *commonpath;
62

63 64 65 66
	git_oid_t oid_type;

	int fsync : 1,
	    sorted : 1;
67
	int peeling_mode;
68 69
	git_iterator_flag_t iterator_flags;
	uint32_t direach_flags;
70
	git_sortedcache *refcache;
71 72
	git_map packed_refs_map;
	git_mutex prlock; /* protect packed_refs_map */
73
	git_futils_filestamp packed_refs_stamp;
74 75
} refdb_fs_backend;

76
static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name);
Colin Stolley committed
77
static char *packed_set_peeling_mode(char *data, size_t data_sz, refdb_fs_backend *backend);
78

79
GIT_INLINE(int) loose_path(
80
	git_str *out,
81 82 83
	const char *base,
	const char *refname)
{
84
	if (git_str_joinpath(out, base, refname) < 0)
85 86
		return -1;

87
	return git_fs_path_validate_str_length_with_suffix(out,
88 89 90 91
		CONST_STRLEN(".lock"));
}

GIT_INLINE(int) reflog_path(
92
	git_str *out,
93 94 95 96 97 98 99 100 101
	git_repository *repo,
	const char *refname)
{
	const char *base;
	int error;

	base = (strcmp(refname, GIT_HEAD_FILE) == 0) ? repo->gitdir :
		repo->commondir;

102
	if ((error = git_str_joinpath(out, base, GIT_REFLOG_DIR)) < 0)
103 104 105 106 107
		return error;

	return loose_path(out, out->ptr, refname);
}

108
static int packref_cmp(const void *a_, const void *b_)
109
{
110 111
	const struct packref *a = a_, *b = b_;
	return strcmp(a->name, b->name);
112 113
}

114
static int packed_reload(refdb_fs_backend *backend)
115
{
116
	int error;
117
	git_str packedrefs = GIT_STR_INIT;
118
	size_t oid_hexsize = git_oid_hexsize(backend->oid_type);
119
	char *scan, *eof, *eol;
120

121
	if (!backend->gitpath)
122 123
		return 0;

124
	error = git_sortedcache_lockandload(backend->refcache, &packedrefs);
125 126

	/*
127 128 129 130
	 * 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.
131
	 */
132 133
	if (error <= 0) {
		if (error == GIT_ENOTFOUND) {
134
			GIT_UNUSED(git_sortedcache_clear(backend->refcache, true));
135
			git_error_clear();
136 137 138
			error = 0;
		}
		return error;
139 140
	}

141
	/* At this point, refresh the packed refs from the loaded buffer. */
142

143
	GIT_UNUSED(git_sortedcache_clear(backend->refcache, false));
144

Colin Stolley committed
145
	scan = packedrefs.ptr;
146
	eof  = scan + packedrefs.size;
147

Colin Stolley committed
148
	scan = packed_set_peeling_mode(scan, packedrefs.size, backend);
Colin Stolley committed
149
	if (!scan)
150
		goto parse_failed;
151

152 153
	while (scan < eof && *scan == '#') {
		if (!(eol = strchr(scan, '\n')))
154
			goto parse_failed;
155
		scan = eol + 1;
156 157
	}

158 159 160 161 162
	while (scan < eof) {
		struct packref *ref;
		git_oid oid;

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

164
		if (git_oid__fromstr(&oid, scan, backend->oid_type) < 0)
165
			goto parse_failed;
166
		scan += oid_hexsize;
167

168 169 170 171 172 173 174
		if (*scan++ != ' ')
			goto parse_failed;
		if (!(eol = strchr(scan, '\n')))
			goto parse_failed;
		*eol = '\0';
		if (eol[-1] == '\r')
			eol[-1] = '\0';
175

176
		if (git_sortedcache_upsert((void **)&ref, backend->refcache, scan) < 0)
177
			goto parse_failed;
178 179 180 181 182 183 184
		scan = eol + 1;

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

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

		if (*scan == '^') {
185
			if (git_oid__fromstr(&oid, scan + 1, backend->oid_type) < 0)
186
				goto parse_failed;
187
			scan += oid_hexsize + 1;
188 189 190 191 192 193 194 195 196 197 198 199 200 201

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

204
	git_sortedcache_wunlock(backend->refcache);
205
	git_str_dispose(&packedrefs);
206

207 208 209
	return 0;

parse_failed:
210
	git_error_set(GIT_ERROR_REFERENCE, "corrupted packed references file");
211

212
	GIT_UNUSED(git_sortedcache_clear(backend->refcache, false));
213
	git_sortedcache_wunlock(backend->refcache);
214
	git_str_dispose(&packedrefs);
215

216 217 218
	return -1;
}

219
static int loose_parse_oid(
220 221 222 223
	git_oid *oid,
	const char *filename,
	git_str *file_content,
	git_oid_t oid_type)
224
{
225
	const char *str = git_str_cstr(file_content);
226
	size_t oid_hexsize = git_oid_hexsize(oid_type);
227

228
	if (git_str_len(file_content) < oid_hexsize)
229 230 231
		goto corrupted;

	/* we need to get 40 OID characters from the file */
232
	if (git_oid__fromstr(oid, str, oid_type) < 0)
233 234 235
		goto corrupted;

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

corrupted:
241
	git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file: %s", filename);
242 243 244
	return -1;
}

245
static int loose_readbuffer(git_str *buf, const char *base, const char *path)
246 247 248
{
	int error;

249 250
	if ((error = loose_path(buf, base, path)) < 0 ||
	    (error = git_futils_readbuffer(buf, buf->ptr)) < 0)
251
		git_str_dispose(buf);
252 253 254 255 256

	return error;
}

static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name)
257
{
258
	int error = 0;
259
	git_str ref_file = GIT_STR_INIT;
260
	struct packref *ref = NULL;
261
	git_oid oid;
262

263 264 265
	/* if we fail to load the loose reference, assume someone changed
	 * the filesystem under us and skip it...
	 */
266
	if (loose_readbuffer(&ref_file, backend->gitpath, name) < 0) {
267
		git_error_clear();
268 269
		goto done;
	}
270

271
	/* skip symbolic refs */
272
	if (!git__prefixcmp(git_str_cstr(&ref_file), GIT_SYMREF))
273
		goto done;
274

275
	/* parse OID from file */
276
	if ((error = loose_parse_oid(&oid, name, &ref_file, backend->oid_type)) < 0)
277
		goto done;
278

279 280
	if ((error = git_sortedcache_wlock(backend->refcache)) < 0)
		goto done;
281

282 283
	if (!(error = git_sortedcache_upsert(
			(void **)&ref, backend->refcache, name))) {
284

285 286
		git_oid_cpy(&ref->oid, &oid);
		ref->flags = PACKREF_WAS_LOOSE;
287 288
	}

289
	git_sortedcache_wunlock(backend->refcache);
290

291
done:
292
	git_str_dispose(&ref_file);
293
	return error;
294 295
}

296
static int _dirent_loose_load(void *payload, git_str *full_path)
297
{
298
	refdb_fs_backend *backend = payload;
299 300
	const char *file_path;

301 302 303
	if (git__suffixcmp(full_path->ptr, ".lock") == 0)
		return 0;

304 305
	if (git_fs_path_isdir(full_path->ptr)) {
		int error = git_fs_path_direach(
306
			full_path, backend->direach_flags, _dirent_loose_load, backend);
307 308
		/* Race with the filesystem, ignore it */
		if (error == GIT_ENOTFOUND) {
309
			git_error_clear();
310 311 312 313 314
			return 0;
		}

		return error;
	}
315

316
	file_path = full_path->ptr + strlen(backend->gitpath);
317

318
	return loose_lookup_to_packfile(backend, file_path);
319 320 321 322 323 324 325 326 327 328
}

/*
 * 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)
{
329
	int error;
330
	git_str refs_path = GIT_STR_INIT;
331

332
	if (git_str_joinpath(&refs_path, backend->gitpath, GIT_REFS_DIR) < 0)
333 334 335 336 337 338 339
		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
	 */
340
	error = git_fs_path_direach(
341
		&refs_path, backend->direach_flags, _dirent_loose_load, backend);
342

343
	git_str_dispose(&refs_path);
344

345
	return error;
346 347 348 349 350 351 352
}

static int refdb_fs_backend__exists(
	int *exists,
	git_refdb_backend *_backend,
	const char *ref_name)
{
353
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
354
	git_str ref_path = GIT_STR_INIT;
355
	int error;
356

357
	GIT_ASSERT_ARG(backend);
358

359 360
	*exists = 0;

361
	if ((error = loose_path(&ref_path, backend->gitpath, ref_name)) < 0)
362 363
		goto out;

364
	if (git_fs_path_isfile(ref_path.ptr)) {
365 366 367
		*exists = 1;
		goto out;
	}
368

369 370
	if ((error = packed_reload(backend)) < 0)
		goto out;
371

372 373 374 375 376 377
	if (git_sortedcache_lookup(backend->refcache, ref_name) != NULL) {
		*exists = 1;
		goto out;
	}

out:
378
	git_str_dispose(&ref_path);
379
	return error;
380 381
}

382
static const char *loose_parse_symbolic(git_str *file_content)
383 384 385 386 387 388
{
	const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);
	const char *refname_start;

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

389
	if (git_str_len(file_content) < header_len + 1) {
390
		git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file");
391 392 393 394 395 396 397 398 399 400 401 402
		return NULL;
	}

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

	return refname_start;
}

403 404 405 406 407 408 409
/*
 * 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/"
 */
410 411
static bool is_per_worktree_ref(const char *ref_name)
{
412 413
	return git__prefixcmp(ref_name, "refs/") != 0 ||
	    git__prefixcmp(ref_name, "refs/bisect/") == 0;
414 415
}

416 417 418 419 420
static int loose_lookup(
	git_reference **out,
	refdb_fs_backend *backend,
	const char *ref_name)
{
421
	git_str ref_file = GIT_STR_INIT;
422
	int error = 0;
423
	const char *ref_dir;
424

425 426 427
	if (out)
		*out = NULL;

428 429 430 431 432 433
	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)
434
		/* cannot read loose ref file - gah */;
435
	else if (git__prefixcmp(git_str_cstr(&ref_file), GIT_SYMREF) == 0) {
436
		const char *target;
437

438
		git_str_rtrim(&ref_file);
439

440
		if (!(target = loose_parse_symbolic(&ref_file)))
441
			error = -1;
442
		else if (out != NULL)
443
			*out = git_reference__alloc_symbolic(ref_name, target);
444
	} else {
445
		git_oid oid;
446

447
		if (!(error = loose_parse_oid(&oid, ref_name, &ref_file, backend->oid_type)) &&
448
			out != NULL)
449
			*out = git_reference__alloc(ref_name, &oid, NULL);
450 451
	}

452
	git_str_dispose(&ref_file);
453 454 455
	return error;
}

456
static int ref_error_notfound(const char *name)
457
{
458
	git_error_set(GIT_ERROR_REFERENCE, "reference '%s' not found", name);
459
	return GIT_ENOTFOUND;
460 461
}

Colin Stolley committed
462 463
static char *packed_set_peeling_mode(
        char *data,
464 465 466 467
        size_t data_sz,
        refdb_fs_backend *backend)
{
	static const char *traits_header = "# pack-refs with:";
Colin Stolley committed
468
	char *eol;
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
	backend->peeling_mode = PEELING_NONE;

	if (git__prefixncmp(data, data_sz, traits_header) == 0) {
		size_t hdr_sz = strlen(traits_header);
		const char *sorted = " sorted ";
		const char *peeled = " peeled ";
		const char *fully_peeled = " fully-peeled ";
		data += hdr_sz;
		data_sz -= hdr_sz;

		eol = memchr(data, '\n', data_sz);

		if (!eol)
			return NULL;

Colin Stolley committed
484
		if (git__memmem(data, eol - data, fully_peeled, strlen(fully_peeled)))
485
			backend->peeling_mode = PEELING_FULL;
Colin Stolley committed
486
		else if (git__memmem(data, eol - data, peeled, strlen(peeled)))
487 488
			backend->peeling_mode = PEELING_STANDARD;

489
		backend->sorted = NULL != git__memmem(data, eol - data, sorted, strlen(sorted));
490 491 492 493 494 495

		return eol + 1;
	}
	return data;
}

496 497 498 499 500 501 502 503 504 505
static void packed_map_free(refdb_fs_backend *backend)
{
	if (backend->packed_refs_map.data) {
#ifdef GIT_WIN32
		git__free(backend->packed_refs_map.data);
#else
		git_futils_mmap_free(&backend->packed_refs_map);
#endif
		backend->packed_refs_map.data = NULL;
		backend->packed_refs_map.len = 0;
506
		git_futils_filestamp_set(&backend->packed_refs_stamp, NULL);
507 508 509
	}
}

510 511 512 513 514 515
static int packed_map_check(refdb_fs_backend *backend)
{
	int error = 0;
	git_file fd = -1;
	struct stat st;

Colin Stolley committed
516
	if ((error = git_mutex_lock(&backend->prlock)) < 0)
517 518
		return error;

519 520 521
	if (backend->packed_refs_map.data &&
	    !git_futils_filestamp_check(
	            &backend->packed_refs_stamp, backend->refcache->path)) {
522 523 524
		git_mutex_unlock(&backend->prlock);
		return error;
	}
525
	packed_map_free(backend);
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549

	fd = git_futils_open_ro(backend->refcache->path);
	if (fd < 0) {
		git_mutex_unlock(&backend->prlock);
		if (fd == GIT_ENOTFOUND) {
			git_error_clear();
			return 0;
		}
		return fd;
	}

	if (p_fstat(fd, &st) < 0) {
		p_close(fd);
		git_mutex_unlock(&backend->prlock);
		git_error_set(GIT_ERROR_OS, "unable to stat packed-refs '%s'", backend->refcache->path);
		return -1;
	}

	if (st.st_size == 0) {
		p_close(fd);
		git_mutex_unlock(&backend->prlock);
		return 0;
	}

550 551
	git_futils_filestamp_set_from_stat(&backend->packed_refs_stamp, &st);

552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
#ifdef GIT_WIN32
	/* on windows, we copy the entire file into memory rather than using
	 * mmap() because using mmap() on windows also locks the file and this
	 * map is long-lived. */
	backend->packed_refs_map.len = (size_t)st.st_size;
	backend->packed_refs_map.data =
	        git__malloc(backend->packed_refs_map.len);
	GIT_ERROR_CHECK_ALLOC(backend->packed_refs_map.data);
	{
		ssize_t bytesread =
		        p_read(fd, backend->packed_refs_map.data,
		               backend->packed_refs_map.len);
		error = (bytesread == (ssize_t)backend->packed_refs_map.len) ?  0 : -1;
	}
#else
567
	error = git_futils_mmap_ro(&backend->packed_refs_map, fd, 0, (size_t)st.st_size);
568
#endif
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
	p_close(fd);
	if (error < 0) {
		git_mutex_unlock(&backend->prlock);
		return error;
	}

	packed_set_peeling_mode(
	        backend->packed_refs_map.data, backend->packed_refs_map.len,
	        backend);

	git_mutex_unlock(&backend->prlock);
	return error;
}

/*
 * Find beginning of packed-ref record pointed to by p.
 *   buf - a lower-bound pointer to some memory buffer
 *   p - an upper-bound pointer to the same memory buffer
 */
static const char *start_of_record(const char *buf, const char *p)
{
	const char *nl = p;
Colin Stolley committed
591
	while (true) {
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
		nl = git__memrchr(buf, '\n', nl - buf);
		if (!nl)
			return buf;

		if (nl[1] == '^' && nl > buf)
			--nl;
		else
			break;
	};
	return nl + 1;
}

/*
 * Find end of packed-ref record pointed to by p.
 *   end - an upper-bound pointer to some memory buffer
 *   p - a lower-bound pointer to the same memory buffer
 */
static const char *end_of_record(const char *p, const char *end)
{
	while (1) {
		size_t sz = end - p;
		p = memchr(p, '\n', sz);
Colin Stolley committed
614
		if (!p)
615 616 617 618 619 620 621 622 623 624
			return end;
		++p;
		if (p < end && p[0] == '^')
			++p;
		else
			break;
	}
	return p;
}

625 626 627 628 629
static int cmp_record_to_refname(
	const char *rec,
	size_t data_end,
	const char *ref_name,
	git_oid_t oid_type)
630 631 632 633
{
	const size_t ref_len = strlen(ref_name);
	int cmp_val;
	const char *end;
634
	size_t oid_hexsize = git_oid_hexsize(oid_type);
635

636 637 638 639
	rec += oid_hexsize + 1; /* <oid> + space */

	/* an incomplete (corrupt) record is treated as less than ref_name */
	if (data_end < oid_hexsize + 3)
640
		return -1;
641 642

	data_end -= oid_hexsize + 1;
643 644

	end = memchr(rec, '\n', data_end);
Colin Stolley committed
645
	if (end)
646 647 648 649
		data_end = end - rec;

	cmp_val = memcmp(rec, ref_name, min(ref_len, data_end));

Colin Stolley committed
650
	if (cmp_val == 0 && data_end != ref_len)
651 652 653 654 655 656 657 658
		return (data_end > ref_len) ? 1 : -1;
	return cmp_val;
}

static int packed_unsorted_lookup(
        git_reference **out,
        refdb_fs_backend *backend,
        const char *ref_name)
659 660
{
	int error = 0;
661
	struct packref *entry;
662

663 664
	if ((error = packed_reload(backend)) < 0)
		return error;
665

666 667
	if (git_sortedcache_rlock(backend->refcache) < 0)
		return -1;
668 669 670 671 672 673 674 675 676 677

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

678 679
	git_sortedcache_runlock(backend->refcache);

680
	return error;
681 682
}

683 684 685 686 687 688 689
static int packed_lookup(
        git_reference **out,
        refdb_fs_backend *backend,
        const char *ref_name)
{
	int error = 0;
	const char *left, *right, *data_end;
690
	size_t oid_hexsize = git_oid_hexsize(backend->oid_type);
691 692 693 694

	if ((error = packed_map_check(backend)) < 0)
		return error;

Colin Stolley committed
695
	if (!backend->sorted)
696 697 698
		return packed_unsorted_lookup(out, backend, ref_name);

	left = backend->packed_refs_map.data;
699
	right = data_end = (const char *) backend->packed_refs_map.data +
700 701 702 703 704 705 706 707 708 709 710 711 712 713
	                   backend->packed_refs_map.len;

	while (left < right && *left == '#') {
		if (!(left = memchr(left, '\n', data_end - left)))
			goto parse_failed;
		left++;
	}

	while (left < right) {
		const char *mid, *rec;
		int compare;

		mid = left + (right - left) / 2;
		rec = start_of_record(left, mid);
714
		compare = cmp_record_to_refname(rec, data_end - rec, ref_name, backend->oid_type);
715 716 717 718 719 720 721 722 723

		if (compare < 0) {
			left = end_of_record(mid, right);
		} else if (compare > 0) {
			right = rec;
		} else {
			const char *eol;
			git_oid oid, peel, *peel_ptr = NULL;

724 725
			if (data_end - rec < (long)oid_hexsize ||
			    git_oid__fromstr(&oid, rec, backend->oid_type) < 0) {
726 727
				goto parse_failed;
			}
728
			rec += oid_hexsize + 1;
729 730 731 732 733 734 735 736 737 738 739
			if (!(eol = memchr(rec, '\n', data_end - rec))) {
				goto parse_failed;
			}

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

			if (eol + 1 < data_end) {
				rec = eol + 1;

				if (*rec == '^') {
					rec++;
740 741
					if (data_end - rec < (long)oid_hexsize ||
					    git_oid__fromstr(&peel, rec, backend->oid_type) < 0) {
742 743 744 745 746 747 748 749 750 751 752 753 754 755
						goto parse_failed;
					}
					peel_ptr = &peel;
				}
			}

			*out = git_reference__alloc(ref_name, &oid, peel_ptr);
			if (!*out) {
				return -1;
			}

			return 0;
		}
	}
John Alden committed
756
	return ref_error_notfound(ref_name);
757 758 759 760 761 762

parse_failed:
	git_error_set(GIT_ERROR_REFERENCE, "corrupted packed references file");
	return -1;
}

763 764 765 766 767
static int refdb_fs_backend__lookup(
	git_reference **out,
	git_refdb_backend *_backend,
	const char *ref_name)
{
768
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
769
	int error;
770

771
	GIT_ASSERT_ARG(backend);
772

773
	if (!(error = loose_lookup(out, backend, ref_name)))
774 775 776 777
		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 */
778
	if (error == GIT_ENOTFOUND) {
779
		git_error_clear();
780
		error = packed_lookup(out, backend, ref_name);
781
	}
782
	return error;
783 784
}

785 786
typedef struct {
	git_reference_iterator parent;
787

788
	char *glob;
789 790

	git_pool pool;
791
	git_vector loose;
792

793
	git_sortedcache *cache;
794 795
	size_t loose_pos;
	size_t packed_pos;
796 797 798
} refdb_fs_iter;

static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter)
799
{
800
	refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent);
801 802

	git_vector_free(&iter->loose);
803
	git_pool_clear(&iter->pool);
804
	git_sortedcache_free(iter->cache);
805 806
	git__free(iter);
}
807

808
static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter)
809
{
810
	int error = 0;
811
	git_str path = GIT_STR_INIT;
812
	git_iterator *fsit = NULL;
813
	git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT;
814
	const git_index_entry *entry = NULL;
815 816
	const char *ref_prefix = GIT_REFS_DIR;
	size_t ref_prefix_len = strlen(ref_prefix);
817

818
	if (!backend->commonpath) /* do nothing if no commonpath for loose refs */
819 820
		return 0;

821 822
	fsit_opts.flags = backend->iterator_flags;

823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846
	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;
		}
	}

847 848 849
	if ((error = git_str_puts(&path, backend->commonpath)) < 0 ||
		(error = git_str_put(&path, ref_prefix, ref_prefix_len)) < 0) {
		git_str_dispose(&path);
850 851
		return error;
	}
852

853
	if ((error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) {
854
		git_str_dispose(&path);
855 856 857
		return (iter->glob && error == GIT_ENOTFOUND)? 0 : error;
	}

858
	error = git_str_sets(&path, ref_prefix);
859

860
	while (!error && !git_iterator_advance(&entry, fsit)) {
861
		const char *ref_name;
862
		char *ref_dup;
863

864 865 866
		git_str_truncate(&path, ref_prefix_len);
		git_str_puts(&path, entry->path);
		ref_name = git_str_cstr(&path);
867

868
		if (git__suffixcmp(ref_name, ".lock") == 0 ||
869
			(iter->glob && wildmatch(iter->glob, ref_name, 0) != 0))
870
			continue;
871

872 873
		ref_dup = git_pool_strdup(&iter->pool, ref_name);
		if (!ref_dup)
874
			error = -1;
875 876
		else
			error = git_vector_insert(&iter->loose, ref_dup);
877
	}
878

879
	git_iterator_free(fsit);
880
	git_str_dispose(&path);
881

882
	return error;
883 884
}

885 886
static int refdb_fs_backend__iterator_next(
	git_reference **out, git_reference_iterator *_iter)
887
{
888
	int error = GIT_ITEROVER;
889 890
	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);
891
	struct packref *ref;
892

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

896 897 898 899 900
		if (loose_lookup(out, backend, path) == 0) {
			ref = git_sortedcache_lookup(iter->cache, path);
			if (ref)
				ref->flags |= PACKREF_SHADOWED;

Vicent Marti committed
901
			return 0;
902
		}
Vicent Marti committed
903

904
		git_error_clear();
905
	}
906

907 908 909
	error = GIT_ITEROVER;
	while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) {
		ref = git_sortedcache_entry(iter->cache, iter->packed_pos++);
910 911
		if (!ref) /* stop now if another thread deleted refs and we past end */
			break;
912 913 914

		if (ref->flags & PACKREF_SHADOWED)
			continue;
915
		if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0)
916
			continue;
917

Vicent Marti committed
918
		*out = git_reference__alloc(ref->name, &ref->oid, &ref->peel);
919 920
		error = (*out != NULL) ? 0 : -1;
		break;
921 922
	}

923
	return error;
924 925
}

926 927 928
static int refdb_fs_backend__iterator_next_name(
	const char **out, git_reference_iterator *_iter)
{
929
	int error = GIT_ITEROVER;
930 931
	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);
932
	struct packref *ref;
933 934 935

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

938
		if (loose_lookup(NULL, backend, path) == 0) {
939 940 941 942
			ref = git_sortedcache_lookup(iter->cache, path);
			if (ref)
				ref->flags |= PACKREF_SHADOWED;

943 944
			*out = path;
			return 0;
945 946
		}

947
		git_error_clear();
948 949
	}

950 951 952
	error = GIT_ITEROVER;
	while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) {
		ref = git_sortedcache_entry(iter->cache, iter->packed_pos++);
953 954
		if (!ref) /* stop now if another thread deleted refs and we past end */
			break;
955 956 957

		if (ref->flags & PACKREF_SHADOWED)
			continue;
958
		if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0)
959 960
			continue;

961 962 963
		*out = ref->name;
		error = 0;
		break;
964 965
	}

966
	return error;
967 968 969 970 971
}

static int refdb_fs_backend__iterator(
	git_reference_iterator **out, git_refdb_backend *_backend, const char *glob)
{
972
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
973 974
	refdb_fs_iter *iter = NULL;
	int error;
975

976
	GIT_ASSERT_ARG(backend);
977 978

	iter = git__calloc(1, sizeof(refdb_fs_iter));
979
	GIT_ERROR_CHECK_ALLOC(iter);
980

981 982
	if ((error = git_pool_init(&iter->pool, 1)) < 0)
		goto out;
983

984 985
	if ((error = git_vector_init(&iter->loose, 8, NULL)) < 0)
		goto out;
986 987

	if (glob != NULL &&
988 989 990 991 992
	    (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL) {
		error = GIT_ERROR_NOMEMORY;
		goto out;
	}

993
	if ((error = iter_load_loose_paths(backend, iter)) < 0)
994 995
		goto out;

996
	if ((error = packed_reload(backend)) < 0)
997
		goto out;
998 999 1000

	if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0)
		goto out;
1001 1002 1003 1004 1005 1006

	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;
1007 1008 1009 1010
out:
	if (error)
		refdb_fs_backend__iterator_free((git_reference_iterator *)iter);
	return error;
1011 1012
}

Vicent Marti committed
1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
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,
1033
	const char *old_ref,
Vicent Marti committed
1034 1035
	int force)
{
1036
	size_t i;
1037
	int error;
Vicent Marti committed
1038

1039 1040
	if ((error = packed_reload(backend)) < 0)
		return error;
Vicent Marti committed
1041 1042 1043 1044

	if (!force) {
		int exists;

1045 1046 1047 1048
		if ((error = refdb_fs_backend__exists(
			&exists, (git_refdb_backend *)backend, new_ref)) < 0) {
			return error;
		}
Vicent Marti committed
1049 1050

		if (exists) {
1051
			git_error_set(GIT_ERROR_REFERENCE,
1052
				"failed to write reference '%s': a reference with "
1053
				"that name already exists.", new_ref);
Vicent Marti committed
1054 1055 1056 1057
			return GIT_EEXISTS;
		}
	}

1058 1059
	if ((error = git_sortedcache_rlock(backend->refcache)) < 0)
		return error;
1060 1061

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

1064 1065
		if (ref && !ref_is_available(old_ref, new_ref, ref->name)) {
			git_sortedcache_runlock(backend->refcache);
1066
			git_error_set(GIT_ERROR_REFERENCE,
1067
				"path to reference '%s' collides with existing one", new_ref);
Vicent Marti committed
1068 1069
			return -1;
		}
1070
	}
1071

1072
	git_sortedcache_runlock(backend->refcache);
Vicent Marti committed
1073 1074
	return 0;
}
1075

1076
static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name)
1077
{
1078
	int error, filebuf_flags;
1079
	git_str ref_path = GIT_STR_INIT;
1080
	const char *basedir;
1081

1082 1083 1084
	GIT_ASSERT_ARG(file);
	GIT_ASSERT_ARG(backend);
	GIT_ASSERT_ARG(name);
1085

1086
	if (!git_path_is_valid(backend->repo, name, 0, GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
1087
		git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", name);
1088 1089 1090
		return GIT_EINVALIDSPEC;
	}

1091 1092 1093 1094 1095
	if (is_per_worktree_ref(name))
		basedir = backend->gitpath;
	else
		basedir = backend->commonpath;

1096 1097 1098
	/* Remove a possibly existing empty directory hierarchy
	 * which name would collide with the reference name
	 */
1099
	if ((error = git_futils_rmdir_r(name, basedir, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
1100
		return error;
1101

1102 1103
	if ((error = loose_path(&ref_path, basedir, name)) < 0)
		return error;
1104

1105
	filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS;
1106
	if (backend->fsync)
1107 1108 1109
		filebuf_flags |= GIT_FILEBUF_FSYNC;

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

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

1114
	git_str_dispose(&ref_path);
1115
	return error;
1116
}
1117

1118 1119
static int loose_commit(git_filebuf *file, const git_reference *ref)
{
1120 1121
	GIT_ASSERT_ARG(file);
	GIT_ASSERT_ARG(ref);
1122

1123
	if (ref->type == GIT_REFERENCE_DIRECT) {
1124
		char oid[GIT_OID_MAX_HEXSIZE + 1];
1125
		git_oid_nfmt(oid, sizeof(oid), &ref->target.oid);
1126

1127
		git_filebuf_printf(file, "%s\n", oid);
1128
	} else if (ref->type == GIT_REFERENCE_SYMBOLIC) {
1129
		git_filebuf_printf(file, GIT_SYMREF "%s\n", ref->target.symbolic);
1130
	} else {
1131
		GIT_ASSERT(0);
1132 1133
	}

1134
	return git_filebuf_commit(file);
1135 1136
}

1137 1138 1139 1140
static int refdb_fs_backend__lock(void **out, git_refdb_backend *_backend, const char *refname)
{
	int error;
	git_filebuf *lock;
1141
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1142 1143

	lock = git__calloc(1, sizeof(git_filebuf));
1144
	GIT_ERROR_CHECK_ALLOC(lock);
1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160

	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,
1161 1162 1163
	const char *old_target,
	const git_signature *who,
	const char *message);
1164 1165 1166 1167 1168

static int refdb_fs_backend__delete_tail(
	git_refdb_backend *_backend,
	git_filebuf *file,
	const char *ref_name,
1169 1170
	const git_oid *old_id,
	const char *old_target);
1171 1172 1173 1174 1175 1176 1177 1178 1179 1180

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)
1181
		error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, NULL, NULL, sig, message);
1182 1183 1184 1185 1186 1187 1188
	else
		git_filebuf_cleanup(lock);

	git__free(lock);
	return error;
}

1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200
/*
 * 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;

1201
	if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL)
1202 1203 1204 1205 1206
		return 0;

	/*
	 * Find the tagged object in the repository
	 */
1207
	if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJECT_ANY) < 0)
1208 1209 1210 1211 1212 1213 1214
		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.
	 */
1215
	if (git_object_type(object) == GIT_OBJECT_TAG) {
1216 1217 1218 1219 1220 1221
		git_tag *tag = (git_tag *)object;

		/*
		 * Find the object pointed at by this tag
		 */
		git_oid_cpy(&ref->peel, git_tag_target_id(tag));
1222
		ref->flags |= PACKREF_HAS_PEEL;
1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239

		/*
		 * 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)
{
1240
	char oid[GIT_OID_MAX_HEXSIZE + 1];
1241
	git_oid_nfmt(oid, sizeof(oid), &ref->oid);
1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252

	/*
	 * 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`.
	 */
1253
	if (ref->flags & PACKREF_HAS_PEEL) {
1254
		char peel[GIT_OID_MAX_HEXSIZE + 1];
1255
		git_oid_nfmt(peel, sizeof(peel), &ref->peel);
1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277

		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.
 */
1278
static int packed_remove_loose(refdb_fs_backend *backend)
1279
{
1280
	size_t i;
1281
	git_filebuf lock = GIT_FILEBUF_INIT;
1282
	git_str ref_content = GIT_STR_INIT;
1283
	int error = 0;
1284

1285 1286 1287 1288
	/* 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);
1289
		git_oid current_id;
1290

1291
		if (!ref || !(ref->flags & PACKREF_WAS_LOOSE))
1292 1293
			continue;

1294 1295
		git_filebuf_cleanup(&lock);

1296 1297 1298
		/* 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 */
1299
		if (error == GIT_EEXISTS || error == GIT_ENOTFOUND)
1300 1301 1302
			continue;

		if (error < 0) {
1303
			git_str_dispose(&ref_content);
1304
			git_error_set(GIT_ERROR_REFERENCE, "failed to lock loose reference '%s'", ref->name);
1305
			return error;
1306 1307 1308 1309
		}

		error = git_futils_readbuffer(&ref_content, lock.path_original);
		/* Someone else beat us to cleaning up the ref, let's simply continue */
1310
		if (error == GIT_ENOTFOUND)
1311 1312 1313
			continue;

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

1317
		/* Figure out the current id; if we find a bad ref file, skip it so we can do the rest */
1318
		if (loose_parse_oid(&current_id, lock.path_original, &ref_content, backend->oid_type) < 0)
1319
			continue;
1320

1321
		/* If the ref moved since we packed it, we must not delete it */
1322
		if (!git_oid_equal(&current_id, &ref->oid))
1323 1324
			continue;

1325 1326 1327
		/*
		 * if we fail to remove a single file, this is *not* good,
		 * but we should keep going and remove as many as possible.
1328 1329
		 * If we fail to remove, the ref is still in the old state, so
		 * we haven't lost information.
1330
		 */
1331
		p_unlink(lock.path_original);
1332 1333
	}

1334
	git_str_dispose(&ref_content);
1335
	git_filebuf_cleanup(&lock);
1336
	return 0;
1337 1338 1339 1340 1341 1342 1343
}

/*
 * Write all the contents in the in-memory packfile to disk.
 */
static int packed_write(refdb_fs_backend *backend)
{
1344
	git_sortedcache *refcache = backend->refcache;
1345
	git_filebuf pack_file = GIT_FILEBUF_INIT;
1346
	int error, open_flags = 0;
1347
	size_t i;
1348

1349 1350 1351 1352 1353
	/* take lock and close up packed-refs mmap if open */
	if ((error = git_mutex_lock(&backend->prlock)) < 0) {
		return error;
	}

1354
	packed_map_free(backend);
1355 1356 1357

	git_mutex_unlock(&backend->prlock);

1358
	/* lock the cache to updates while we do this */
1359 1360
	if ((error = git_sortedcache_wlock(refcache)) < 0)
		return error;
1361

1362
	if (backend->fsync)
1363 1364
		open_flags = GIT_FILEBUF_FSYNC;

1365
	/* Open the file! */
1366
	if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), open_flags, GIT_PACKEDREFS_FILE_MODE)) < 0)
1367
		goto fail;
1368 1369 1370 1371

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

1375 1376
	for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) {
		struct packref *ref = git_sortedcache_entry(refcache, i);
1377 1378 1379 1380 1381

		GIT_ASSERT_WITH_CLEANUP(ref, {
			error = -1;
			goto fail;
		});
1382

1383
		if ((error = packed_find_peel(backend, ref)) < 0)
1384
			goto fail;
1385

1386
		if ((error = packed_write_ref(ref, &pack_file)) < 0)
1387
			goto fail;
1388 1389 1390 1391
	}

	/* if we've written all the references properly, we can commit
	 * the packfile to make the changes effective */
1392
	if ((error = git_filebuf_commit(&pack_file)) < 0)
1393
		goto fail;
1394 1395 1396

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

1400 1401
	git_sortedcache_updated(refcache);
	git_sortedcache_wunlock(refcache);
1402 1403 1404 1405

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

1406
fail:
1407
	git_filebuf_cleanup(&pack_file);
1408
	git_sortedcache_wunlock(refcache);
1409

1410
	return error;
1411 1412
}

1413 1414 1415
static int packed_delete(refdb_fs_backend *backend, const char *ref_name)
{
	size_t pack_pos;
1416
	int error, found = 0;
1417 1418 1419 1420 1421 1422 1423

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

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

1424 1425 1426
	/* 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) {
1427
		error = git_sortedcache_remove(backend->refcache, pack_pos);
1428 1429 1430 1431
		found = 1;
	}
	if (error == GIT_ENOTFOUND)
		error = 0;
1432 1433 1434

	git_sortedcache_wunlock(backend->refcache);

1435 1436
	if (found)
		error = packed_write(backend);
1437 1438 1439 1440 1441

cleanup:
	return error;
}

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

1444
static int cmp_old_ref(int *cmp, git_refdb_backend *backend, const char *name,
1445 1446 1447 1448 1449 1450
	const git_oid *old_id, const char *old_target)
{
	int error = 0;
	git_reference *old_ref = NULL;

	*cmp = 0;
1451 1452 1453 1454
	/* It "matches" if there is no old value to compare against */
	if (!old_id && !old_target)
		return 0;

1455 1456 1457
	if ((error = refdb_fs_backend__lookup(&old_ref, backend, name)) < 0) {
		if (error == GIT_ENOTFOUND && old_id && git_oid_is_zero(old_id))
			return 0;
1458
		goto out;
1459
	}
1460 1461

	/* If the types don't match, there's no way the values do */
1462
	if (old_id && old_ref->type != GIT_REFERENCE_DIRECT) {
1463 1464 1465
		*cmp = -1;
		goto out;
	}
1466
	if (old_target && old_ref->type != GIT_REFERENCE_SYMBOLIC) {
1467 1468
		*cmp = 1;
		goto out;
1469 1470
	}

1471
	if (old_id && old_ref->type == GIT_REFERENCE_DIRECT)
1472 1473
		*cmp = git_oid_cmp(old_id, &old_ref->target.oid);

1474
	if (old_target && old_ref->type == GIT_REFERENCE_SYMBOLIC)
1475 1476 1477 1478 1479 1480 1481 1482
		*cmp = git__strcmp(old_target, old_ref->target.symbolic);

out:
	git_reference_free(old_ref);

	return error;
}

1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498
/*
 * 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)
{
1499 1500 1501
	git_reference *head = NULL;
	git_refdb *refdb = NULL;
	int error, write_reflog;
1502
	git_oid old_id;
1503

1504 1505 1506 1507 1508
	if ((error = git_repository_refdb(&refdb, backend->repo)) < 0 ||
	    (error = git_refdb_should_write_head_reflog(&write_reflog, refdb, ref)) < 0)
		goto out;
	if (!write_reflog)
		goto out;
1509

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

1514 1515 1516
	if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0 ||
	    (error = reflog_append(backend, head, &old_id, git_reference_target(ref), who, message)) < 0)
		goto out;
1517

1518
out:
1519
	git_reference_free(head);
1520
	git_refdb_free(refdb);
1521 1522 1523
	return error;
}

1524 1525
static int refdb_fs_backend__write(
	git_refdb_backend *_backend,
Vicent Marti committed
1526
	const git_reference *ref,
1527
	int force,
1528
	const git_signature *who,
1529
	const char *message,
1530 1531
	const git_oid *old_id,
	const char *old_target)
1532
{
1533
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1534
	git_filebuf file = GIT_FILEBUF_INIT;
1535
	int error = 0;
1536

1537
	GIT_ASSERT_ARG(backend);
1538

1539
	if ((error = reference_path_available(backend, ref->name, NULL, force)) < 0)
Vicent Marti committed
1540 1541
		return error;

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

1546
	return refdb_fs_backend__write_tail(_backend, ref, &file, true, old_id, old_target, who, message);
1547 1548 1549 1550 1551 1552 1553 1554
}

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,
1555 1556 1557
	const char *old_target,
	const git_signature *who,
	const char *message)
1558
{
1559
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1560 1561 1562 1563
	int error = 0, cmp = 0, should_write;
	const char *new_target = NULL;
	const git_oid *new_id = NULL;

1564
	if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0)
1565
		goto on_error;
1566 1567

	if (cmp) {
1568
		git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match");
1569 1570
		error = GIT_EMODIFIED;
		goto on_error;
1571 1572
	}

1573
	if (ref->type == GIT_REFERENCE_SYMBOLIC)
1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587
		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 */
	}

1588
	if (update_reflog) {
1589 1590 1591 1592
		git_refdb *refdb;

		if ((error = git_repository_refdb__weakptr(&refdb, backend->repo)) < 0 ||
		    (error = git_refdb_should_write_reflog(&should_write, refdb, ref)) < 0)
1593
			goto on_error;
1594 1595 1596 1597 1598 1599 1600

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

1603
	return loose_commit(file, ref);
1604 1605

on_error:
1606
        git_filebuf_cleanup(file);
1607
        return error;
1608 1609
}

1610
static int refdb_fs_backend__prune_refs(
1611 1612
	refdb_fs_backend *backend,
	const char *ref_name,
1613
	const char *prefix)
1614
{
1615 1616
	git_str relative_path = GIT_STR_INIT;
	git_str base_path = GIT_STR_INIT;
1617
	size_t commonlen;
1618
	int error;
1619

1620 1621
	GIT_ASSERT_ARG(backend);
	GIT_ASSERT_ARG(ref_name);
1622

1623
	if ((error = git_str_sets(&relative_path, ref_name)) < 0)
1624 1625
		goto cleanup;

1626 1627 1628 1629
	git_fs_path_squash_slashes(&relative_path);
	if ((commonlen = git_fs_path_common_dirlen("refs/heads/", git_str_cstr(&relative_path))) == strlen("refs/heads/") ||
		(commonlen = git_fs_path_common_dirlen("refs/tags/", git_str_cstr(&relative_path))) == strlen("refs/tags/") ||
		(commonlen = git_fs_path_common_dirlen("refs/remotes/", git_str_cstr(&relative_path))) == strlen("refs/remotes/")) {
1630

1631
		git_str_truncate(&relative_path, commonlen);
1632

1633
		if (prefix)
1634
			error = git_str_join3(&base_path, '/',
1635
				backend->commonpath, prefix,
1636
				git_str_cstr(&relative_path));
1637
		else
1638
			error = git_str_joinpath(&base_path,
1639
				backend->commonpath,
1640
				git_str_cstr(&relative_path));
1641

1642
		if (!error)
1643
			error = git_path_validate_str_length(NULL, &base_path);
1644

1645 1646 1647 1648
		if (error < 0)
			goto cleanup;

		error = git_futils_rmdir_r(ref_name + commonlen,
1649
			git_str_cstr(&base_path),
1650
			GIT_RMDIR_EMPTY_PARENTS | GIT_RMDIR_SKIP_ROOT);
1651

1652 1653
		if (error == GIT_ENOTFOUND)
			error = 0;
1654 1655 1656
	}

cleanup:
1657 1658
	git_str_dispose(&relative_path);
	git_str_dispose(&base_path);
1659
	return error;
1660 1661
}

1662 1663
static int refdb_fs_backend__delete(
	git_refdb_backend *_backend,
1664 1665
	const char *ref_name,
	const git_oid *old_id, const char *old_target)
1666
{
1667
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1668
	git_filebuf file = GIT_FILEBUF_INIT;
1669
	int error = 0;
1670

1671 1672
	GIT_ASSERT_ARG(backend);
	GIT_ASSERT_ARG(ref_name);
1673

1674 1675 1676
	if ((error = loose_lock(&file, backend, ref_name)) < 0)
		return error;

1677 1678 1679 1680 1681
	if ((error = refdb_reflog_fs__delete(_backend, ref_name)) < 0) {
		git_filebuf_cleanup(&file);
		return error;
	}

1682 1683 1684
	return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target);
}

1685 1686
static int loose_delete(refdb_fs_backend *backend, const char *ref_name)
{
1687
	git_str path = GIT_STR_INIT;
1688 1689
	int error = 0;

1690 1691
	if ((error = loose_path(&path, backend->commonpath, ref_name)) < 0)
		return error;
1692

1693
	error = p_unlink(path.ptr);
1694 1695 1696 1697 1698
	if (error < 0 && errno == ENOENT)
		error = GIT_ENOTFOUND;
	else if (error != 0)
		error = -1;

1699
	git_str_dispose(&path);
1700 1701 1702 1703

	return error;
}

1704 1705 1706 1707 1708 1709
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)
{
1710
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1711
	int error = 0, cmp = 0;
1712
	bool packed_deleted = 0;
1713

1714 1715 1716 1717 1718
	error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target);
	if (error < 0)
		goto cleanup;

	if (cmp) {
1719
		git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match");
1720 1721 1722 1723
		error = GIT_EMODIFIED;
		goto cleanup;
	}

1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741
	/*
	 * 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)
1742
		goto cleanup;
1743

1744 1745
	if (error == 0)
		packed_deleted = 1;
1746

1747
	if ((error = loose_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND)
1748
		goto cleanup;
Vicent Marti committed
1749

1750
	if (error == GIT_ENOTFOUND) {
1751
		error = packed_deleted ? 0 : ref_error_notfound(ref_name);
1752 1753 1754 1755
		goto cleanup;
	}

cleanup:
1756
	git_filebuf_cleanup(file);
1757
	if (error == 0)
1758
		error = refdb_fs_backend__prune_refs(backend, ref_name, "");
1759
	return error;
1760 1761
}

1762 1763
static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name);

Vicent Marti committed
1764 1765 1766 1767 1768
static int refdb_fs_backend__rename(
	git_reference **out,
	git_refdb_backend *_backend,
	const char *old_name,
	const char *new_name,
1769
	int force,
1770
	const git_signature *who,
1771
	const char *message)
Vicent Marti committed
1772
{
1773
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1774
	git_reference *old, *new = NULL;
1775
	git_filebuf file = GIT_FILEBUF_INIT;
Vicent Marti committed
1776 1777
	int error;

1778
	GIT_ASSERT_ARG(backend);
Vicent Marti committed
1779

1780 1781 1782
	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
1783 1784
		return error;

1785
	if ((error = refdb_fs_backend__delete(_backend, old_name, NULL, NULL)) < 0) {
Vicent Marti committed
1786 1787 1788 1789
		git_reference_free(old);
		return error;
	}

1790
	new = git_reference__realloc(&old, new_name);
1791 1792 1793
	if (!new) {
		git_reference_free(old);
		return -1;
Vicent Marti committed
1794 1795
	}

1796
	if ((error = loose_lock(&file, backend, new->name)) < 0) {
1797 1798 1799 1800 1801 1802 1803
		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)) &&
1804
	    ((error = reflog_append(backend, new, git_reference_target(new), NULL, who, message)) < 0)) {
1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817
		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
1818
		git_reference_free(new);
1819
		return error;
Vicent Marti committed
1820 1821
	}

1822
	*out = new;
Vicent Marti committed
1823 1824 1825
	return 0;
}

1826 1827
static int refdb_fs_backend__compress(git_refdb_backend *_backend)
{
1828
	int error;
1829
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1830

1831
	GIT_ASSERT_ARG(backend);
1832

1833 1834 1835 1836
	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;
1837 1838 1839 1840 1841 1842

	return 0;
}

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

1845 1846
	if (!backend)
		return;
1847

1848
	git_sortedcache_free(backend->refcache);
1849 1850

	git_mutex_lock(&backend->prlock);
1851
	packed_map_free(backend);
1852 1853 1854
	git_mutex_unlock(&backend->prlock);
	git_mutex_free(&backend->prlock);

1855
	git__free(backend->gitpath);
1856
	git__free(backend->commonpath);
1857 1858 1859
	git__free(backend);
}

1860
static char *setup_namespace(git_repository *repo, const char *in)
1861
{
1862
	git_str path = GIT_STR_INIT;
1863
	char *parts, *start, *end, *out = NULL;
1864

1865 1866
	if (!in)
		goto done;
1867

1868
	git_str_puts(&path, in);
1869 1870

	/* if the repo is not namespaced, nothing else to do */
1871
	if (repo->namespace == NULL) {
1872
		out = git_str_detach(&path);
1873 1874
		goto done;
	}
1875 1876 1877

	parts = end = git__strdup(repo->namespace);
	if (parts == NULL)
1878
		goto done;
1879

1880
	/*
1881 1882 1883 1884 1885
	 * 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/
	 */
1886
	while ((start = git__strsep(&end, "/")) != NULL)
1887
		git_str_printf(&path, "refs/namespaces/%s/", start);
1888

1889
	git_str_printf(&path, "refs/namespaces/%s/refs", end);
Vicent Marti committed
1890
	git__free(parts);
1891 1892

	/* Make sure that the folder with the namespace exists */
1893
	if (git_futils_mkdir_relative(git_str_cstr(&path), in, 0777,
1894 1895
			GIT_MKDIR_PATH, NULL) < 0)
		goto done;
1896

1897
	/* Return root of the namespaced gitpath, i.e. without the trailing 'refs' */
1898 1899 1900
	git_str_rtruncate_at_char(&path, '/');
	git_str_putc(&path, '/');
	out = git_str_detach(&path);
1901 1902

done:
1903
	git_str_dispose(&path);
1904
	return out;
1905 1906
}

1907 1908 1909 1910
static int reflog_alloc(
	git_reflog **reflog,
	const char *name,
	git_oid_t oid_type)
1911 1912 1913 1914 1915 1916
{
	git_reflog *log;

	*reflog = NULL;

	log = git__calloc(1, sizeof(git_reflog));
1917
	GIT_ERROR_CHECK_ALLOC(log);
1918 1919

	log->ref_name = git__strdup(name);
1920
	GIT_ERROR_CHECK_ALLOC(log->ref_name);
1921

1922 1923
	log->oid_type = oid_type;

1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936
	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)
{
1937
	git_parse_ctx parser = GIT_PARSE_CTX_INIT;
1938

1939 1940
	if ((git_parse_ctx_init(&parser, buf, buf_size)) < 0)
		return -1;
1941

1942
	for (; parser.remain_len; git_parse_advance_line(&parser)) {
1943
		git_reflog_entry *entry;
1944 1945
		const char *sig;
		char c;
1946

1947 1948 1949 1950
		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);
1951

1952
		if (git_parse_advance_oid(&entry->oid_old, &parser, log->oid_type) < 0 ||
1953
		    git_parse_advance_expected(&parser, " ", 1) < 0 ||
1954
		    git_parse_advance_oid(&entry->oid_cur, &parser, log->oid_type) < 0)
1955
			goto next;
1956

1957 1958 1959
		sig = parser.line;
		while (git_parse_peek(&c, &parser, 0) == 0 && c != '\t' && c != '\n')
			git_parse_advance_chars(&parser, 1);
1960

1961
		if (git_signature__parse(entry->committer, &sig, parser.line, NULL, 0) < 0)
1962
			goto next;
1963

1964 1965 1966
		if (c == '\t') {
			size_t len;
			git_parse_advance_chars(&parser, 1);
1967

1968 1969 1970
			len = parser.line_len;
			if (parser.line[len - 1] == '\n')
				len--;
1971

1972
			entry->msg = git__strndup(parser.line, len);
1973
			GIT_ERROR_CHECK_ALLOC(entry->msg);
1974
		}
1975

1976 1977 1978 1979
		if ((git_vector_insert(&log->entries, entry)) < 0) {
			git_reflog_entry__free(entry);
			return -1;
		}
1980

1981
		continue;
1982

1983 1984 1985
next:
		git_reflog_entry__free(entry);
	}
1986

1987
	return 0;
1988 1989 1990 1991 1992 1993 1994 1995 1996 1997
}

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,
1998
			O_WRONLY | O_CREAT,
1999 2000 2001 2002 2003 2004
			GIT_REFLOG_FILE_MODE)) < 0)
		return -1;

	return p_close(fd);
}

2005 2006 2007 2008
static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name)
{
	refdb_fs_backend *backend;
	git_repository *repo;
2009
	git_str path = GIT_STR_INIT;
2010 2011
	int error;

2012
	GIT_ASSERT_ARG(_backend && name);
2013

2014
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
2015 2016
	repo = backend->repo;

2017
	if ((error = reflog_path(&path, repo, name)) < 0)
2018 2019
		return error;

2020 2021
	error = create_new_reflog_file(git_str_cstr(&path));
	git_str_dispose(&path);
2022 2023

	return error;
2024 2025
}

2026 2027 2028
static int has_reflog(git_repository *repo, const char *name)
{
	int ret = 0;
2029
	git_str path = GIT_STR_INIT;
2030

2031
	if (reflog_path(&path, repo, name) < 0)
2032 2033
		goto cleanup;

2034
	ret = git_fs_path_isfile(git_str_cstr(&path));
2035 2036

cleanup:
2037
	git_str_dispose(&path);
2038 2039 2040
	return ret;
}

2041 2042 2043 2044
static int refdb_reflog_fs__has_log(git_refdb_backend *_backend, const char *name)
{
	refdb_fs_backend *backend;

2045 2046
	GIT_ASSERT_ARG(_backend);
	GIT_ASSERT_ARG(name);
2047

2048
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
2049 2050 2051 2052

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

2053 2054 2055 2056
static int refdb_reflog_fs__read(
	git_reflog **out,
	git_refdb_backend *_backend,
	const char *name)
2057 2058
{
	int error = -1;
2059 2060
	git_str log_path = GIT_STR_INIT;
	git_str log_file = GIT_STR_INIT;
2061 2062 2063 2064
	git_reflog *log = NULL;
	git_repository *repo;
	refdb_fs_backend *backend;

2065 2066 2067
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(_backend);
	GIT_ASSERT_ARG(name);
2068

2069
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
2070 2071
	repo = backend->repo;

2072
	if (reflog_alloc(&log, name, backend->oid_type) < 0)
2073 2074
		return -1;

2075
	if (reflog_path(&log_path, repo, name) < 0)
2076 2077
		goto cleanup;

2078
	error = git_futils_readbuffer(&log_file, git_str_cstr(&log_path));
2079 2080 2081 2082
	if (error < 0 && error != GIT_ENOTFOUND)
		goto cleanup;

	if ((error == GIT_ENOTFOUND) &&
2083
		((error = create_new_reflog_file(git_str_cstr(&log_path))) < 0))
2084
		goto cleanup;
2085

2086
	if ((error = reflog_parse(log,
2087
		git_str_cstr(&log_file), git_str_len(&log_file))) < 0)
2088 2089 2090 2091 2092 2093 2094 2095 2096
		goto cleanup;

	*out = log;
	goto success;

cleanup:
	git_reflog_free(log);

success:
2097 2098
	git_str_dispose(&log_file);
	git_str_dispose(&log_path);
2099 2100 2101 2102 2103

	return error;
}

static int serialize_reflog_entry(
2104
	git_str *buf,
2105 2106 2107 2108 2109
	const git_oid *oid_old,
	const git_oid *oid_new,
	const git_signature *committer,
	const char *msg)
{
2110 2111
	char raw_old[GIT_OID_MAX_HEXSIZE + 1];
	char raw_new[GIT_OID_MAX_HEXSIZE + 1];
2112

2113 2114
	git_oid_tostr(raw_old, GIT_OID_MAX_HEXSIZE + 1, oid_old);
	git_oid_tostr(raw_new, GIT_OID_MAX_HEXSIZE + 1, oid_new);
2115

2116
	git_str_clear(buf);
2117

2118 2119 2120
	git_str_puts(buf, raw_old);
	git_str_putc(buf, ' ');
	git_str_puts(buf, raw_new);
2121 2122 2123 2124

	git_signature__writebuf(buf, " ", committer);

	/* drop trailing LF */
2125
	git_str_rtrim(buf);
2126 2127

	if (msg) {
2128 2129
		size_t i;

2130 2131
		git_str_putc(buf, '\t');
		git_str_puts(buf, msg);
2132 2133 2134 2135

		for (i = 0; i < buf->size - 2; i++)
			if (buf->ptr[i] == '\n')
				buf->ptr[i] = ' ';
2136
		git_str_rtrim(buf);
2137 2138
	}

2139
	git_str_putc(buf, '\n');
2140

2141
	return git_str_oom(buf);
2142 2143
}

2144 2145 2146
static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char *refname)
{
	git_repository *repo;
2147
	git_str log_path = GIT_STR_INIT;
2148 2149 2150 2151
	int error;

	repo = backend->repo;

2152
	if (!git_path_is_valid(backend->repo, refname, 0, GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
2153
		git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", refname);
2154 2155 2156
		return GIT_EINVALIDSPEC;
	}

2157
	if (reflog_path(&log_path, repo, refname) < 0)
2158 2159
		return -1;

2160
	if (!git_fs_path_isfile(git_str_cstr(&log_path))) {
2161
		git_error_set(GIT_ERROR_INVALID,
2162
			"log file for reference '%s' doesn't exist", refname);
2163 2164 2165 2166
		error = -1;
		goto cleanup;
	}

2167
	error = git_filebuf_open(file, git_str_cstr(&log_path), 0, GIT_REFLOG_FILE_MODE);
2168 2169

cleanup:
2170
	git_str_dispose(&log_path);
2171 2172 2173 2174

	return error;
}

2175 2176 2177 2178 2179 2180
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;
2181
	git_str log = GIT_STR_INIT;
2182 2183
	git_filebuf fbuf = GIT_FILEBUF_INIT;

2184 2185
	GIT_ASSERT_ARG(_backend);
	GIT_ASSERT_ARG(reflog);
2186

2187
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
2188

2189
	if ((error = lock_reflog(&fbuf, backend, reflog->ref_name)) < 0)
2190 2191 2192 2193 2194 2195 2196 2197 2198 2199
		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;
	}

2200
	error = git_filebuf_commit(&fbuf);
2201 2202 2203 2204 2205 2206
	goto success;

cleanup:
	git_filebuf_cleanup(&fbuf);

success:
2207
	git_str_dispose(&log);
2208

2209 2210 2211
	return error;
}

2212
/* Append to the reflog, must be called under reference lock */
2213 2214 2215 2216 2217 2218 2219
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)
2220
{
2221
	int error, is_symbolic, open_flags;
2222
	git_oid old_id, new_id;
2223
	git_str buf = GIT_STR_INIT, path = GIT_STR_INIT;
2224 2225
	git_repository *repo = backend->repo;

2226
	is_symbolic = ref->type == GIT_REFERENCE_SYMBOLIC;
2227 2228 2229 2230 2231

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

Etienne Samson committed
2234
	/* From here on is_symbolic also means that it's HEAD */
2235

2236 2237 2238
	git_oid_clear(&old_id, backend->oid_type);
	git_oid_clear(&new_id, backend->oid_type);

2239 2240
	if (old) {
		git_oid_cpy(&old_id, old);
2241
	} else {
2242
		error = git_reference_name_to_id(&old_id, repo, ref->name);
2243
		if (error < 0 && error != GIT_ENOTFOUND)
2244 2245 2246
			return error;
	}

2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258
	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;
2259

2260
			git_error_clear();
2261
		}
2262
	}
2263

2264 2265 2266
	if ((error = serialize_reflog_entry(&buf, &old_id, &new_id, who, message)) < 0)
		goto cleanup;

2267
	if ((error = reflog_path(&path, repo, ref->name)) < 0)
2268 2269
		goto cleanup;

2270
	if (((error = git_futils_mkpath2file(git_str_cstr(&path), 0777)) < 0) &&
2271 2272 2273 2274
	    (error != GIT_EEXISTS)) {
		goto cleanup;
	}

2275 2276 2277
	/* 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.
	 */
2278
	if (git_fs_path_isdir(git_str_cstr(&path))) {
2279
		if ((error = git_futils_rmdir_r(git_str_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY)) < 0) {
2280 2281
			if (error == GIT_ENOTFOUND)
				error = 0;
2282
		} else if (git_fs_path_isdir(git_str_cstr(&path))) {
2283
			git_error_set(GIT_ERROR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder",
2284 2285 2286 2287 2288 2289
				ref->name);
			error = GIT_EDIRECTORY;
		}

		if (error != 0)
			goto cleanup;
2290 2291
	}

2292 2293
	open_flags = O_WRONLY | O_CREAT | O_APPEND;

2294
	if (backend->fsync)
2295 2296
		open_flags |= O_FSYNC;

2297
	error = git_futils_writebuffer(&buf, git_str_cstr(&path), open_flags, GIT_REFLOG_FILE_MODE);
2298 2299

cleanup:
2300 2301
	git_str_dispose(&buf);
	git_str_dispose(&path);
2302

2303 2304 2305 2306 2307 2308
	return error;
}

static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name)
{
	int error = 0, fd;
2309 2310 2311 2312
	git_str old_path = GIT_STR_INIT;
	git_str new_path = GIT_STR_INIT;
	git_str temp_path = GIT_STR_INIT;
	git_str normalized = GIT_STR_INIT;
2313 2314 2315
	git_repository *repo;
	refdb_fs_backend *backend;

2316 2317 2318
	GIT_ASSERT_ARG(_backend);
	GIT_ASSERT_ARG(old_name);
	GIT_ASSERT_ARG(new_name);
2319

2320
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
2321 2322 2323
	repo = backend->repo;

	if ((error = git_reference__normalize_name(
2324
		&normalized, new_name, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL)) < 0)
2325 2326
			return error;

2327
	if (git_str_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0)
2328 2329
		return -1;

2330
	if ((error = loose_path(&old_path, git_str_cstr(&temp_path), old_name)) < 0)
2331
		return error;
2332

2333
	if ((error = loose_path(&new_path, git_str_cstr(&temp_path), git_str_cstr(&normalized))) < 0)
2334
		return error;
2335

2336
	if (!git_fs_path_exists(git_str_cstr(&old_path))) {
2337 2338 2339 2340
		error = GIT_ENOTFOUND;
		goto cleanup;
	}

2341 2342 2343 2344 2345 2346 2347
	/*
	 * 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
	 */
2348
	if ((error = loose_path(&temp_path, git_str_cstr(&temp_path), "temp_reflog")) < 0)
2349
		return error;
2350

2351
	if ((fd = git_futils_mktmp(&temp_path, git_str_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) {
2352 2353 2354 2355 2356 2357
		error = -1;
		goto cleanup;
	}

	p_close(fd);

2358
	if (p_rename(git_str_cstr(&old_path), git_str_cstr(&temp_path)) < 0) {
2359
		git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name);
2360 2361 2362 2363
		error = -1;
		goto cleanup;
	}

2364
	if (git_fs_path_isdir(git_str_cstr(&new_path)) &&
2365
		(git_futils_rmdir_r(git_str_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) {
2366 2367 2368 2369
		error = -1;
		goto cleanup;
	}

2370
	if (git_futils_mkpath2file(git_str_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) {
2371 2372 2373 2374
		error = -1;
		goto cleanup;
	}

2375
	if (p_rename(git_str_cstr(&temp_path), git_str_cstr(&new_path)) < 0) {
2376
		git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name);
2377 2378 2379 2380
		error = -1;
	}

cleanup:
2381 2382 2383 2384
	git_str_dispose(&temp_path);
	git_str_dispose(&old_path);
	git_str_dispose(&new_path);
	git_str_dispose(&normalized);
2385 2386 2387 2388 2389 2390

	return error;
}

static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name)
{
2391
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
2392
	git_str path = GIT_STR_INIT;
2393
	int error;
2394

2395 2396
	GIT_ASSERT_ARG(_backend);
	GIT_ASSERT_ARG(name);
2397

2398
	if ((error = reflog_path(&path, backend->repo, name)) < 0)
2399
		goto out;
2400

2401
	if (!git_fs_path_exists(path.ptr))
2402
		goto out;
2403

2404 2405
	if ((error = p_unlink(path.ptr)) < 0)
		goto out;
2406

2407
	error = refdb_fs_backend__prune_refs(backend, name, GIT_REFLOG_DIR);
2408 2409

out:
2410
	git_str_dispose(&path);
2411 2412 2413 2414

	return error;
}

2415 2416
int git_refdb_backend_fs(
	git_refdb_backend **backend_out,
2417
	git_repository *repository)
2418
{
2419
	int t = 0;
2420
	git_str gitpath = GIT_STR_INIT;
2421 2422 2423
	refdb_fs_backend *backend;

	backend = git__calloc(1, sizeof(refdb_fs_backend));
2424
	GIT_ERROR_CHECK_ALLOC(backend);
2425 2426 2427 2428 2429
	if (git_mutex_init(&backend->prlock) < 0) {
		git__free(backend);
		return -1;
	}

2430

2431 2432 2433
	if (git_refdb_init_backend(&backend->parent, GIT_REFDB_BACKEND_VERSION) < 0)
		goto fail;

2434
	backend->repo = repository;
2435
	backend->oid_type = repository->oid_type;
Vicent Marti committed
2436

2437 2438
	if (repository->gitdir) {
		backend->gitpath = setup_namespace(repository, repository->gitdir);
Vicent Marti committed
2439

2440 2441 2442 2443 2444 2445 2446 2447 2448 2449
		if (backend->gitpath == NULL)
			goto fail;
	}

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

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

2451
	if (git_str_joinpath(&gitpath, backend->commonpath, GIT_PACKEDREFS_FILE) < 0 ||
2452 2453
		git_sortedcache_new(
			&backend->refcache, offsetof(struct packref, name),
2454
			NULL, NULL, packref_cmp, git_str_cstr(&gitpath)) < 0)
2455 2456
		goto fail;

2457
	git_str_dispose(&gitpath);
2458

2459
	if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_IGNORECASE) && t) {
2460
		backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE;
2461
		backend->direach_flags  |= GIT_FS_PATH_DIR_IGNORE_CASE;
2462
	}
2463
	if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_PRECOMPOSE) && t) {
2464
		backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
2465
		backend->direach_flags  |= GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE;
2466
	}
2467
	if ((!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_FSYNCOBJECTFILES) && t) ||
2468
		git_repository__fsync_gitdir)
2469
		backend->fsync = 1;
2470
	backend->iterator_flags |= GIT_ITERATOR_DESCEND_SYMLINKS;
2471

2472 2473
	backend->parent.exists = &refdb_fs_backend__exists;
	backend->parent.lookup = &refdb_fs_backend__lookup;
2474
	backend->parent.iterator = &refdb_fs_backend__iterator;
2475
	backend->parent.write = &refdb_fs_backend__write;
2476
	backend->parent.del = &refdb_fs_backend__delete;
Vicent Marti committed
2477
	backend->parent.rename = &refdb_fs_backend__rename;
2478
	backend->parent.compress = &refdb_fs_backend__compress;
2479 2480
	backend->parent.lock = &refdb_fs_backend__lock;
	backend->parent.unlock = &refdb_fs_backend__unlock;
2481
	backend->parent.has_log = &refdb_reflog_fs__has_log;
2482
	backend->parent.ensure_log = &refdb_reflog_fs__ensure_log;
2483
	backend->parent.free = &refdb_fs_backend__free;
2484 2485 2486 2487
	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;
2488 2489 2490

	*backend_out = (git_refdb_backend *)backend;
	return 0;
2491 2492

fail:
2493
	git_mutex_free(&backend->prlock);
2494
	git_str_dispose(&gitpath);
2495
	git__free(backend->gitpath);
2496
	git__free(backend->commonpath);
2497 2498
	git__free(backend);
	return -1;
2499
}