refdb_fs.c 61.5 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 809 810
static int iter_load_loose_paths(
	refdb_fs_backend *backend,
	refdb_fs_iter *iter)
811
{
812
	int error = 0;
813
	git_str path = GIT_STR_INIT;
814
	git_iterator *fsit = NULL;
815
	git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT;
816
	const git_index_entry *entry = NULL;
817 818
	const char *ref_prefix = GIT_REFS_DIR;
	size_t ref_prefix_len = strlen(ref_prefix);
819

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

823
	fsit_opts.flags = backend->iterator_flags;
824
	fsit_opts.oid_type = backend->oid_type;
825

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

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

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

861
	error = git_str_sets(&path, ref_prefix);
862

863
	while (!error && !git_iterator_advance(&entry, fsit)) {
864
		const char *ref_name;
865
		char *ref_dup;
866

867 868 869
		git_str_truncate(&path, ref_prefix_len);
		git_str_puts(&path, entry->path);
		ref_name = git_str_cstr(&path);
870

871
		if (git__suffixcmp(ref_name, ".lock") == 0 ||
872
			(iter->glob && wildmatch(iter->glob, ref_name, 0) != 0))
873
			continue;
874

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

882
	git_iterator_free(fsit);
883
	git_str_dispose(&path);
884

885
	return error;
886 887
}

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

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

899 900 901 902 903
		if (loose_lookup(out, backend, path) == 0) {
			ref = git_sortedcache_lookup(iter->cache, path);
			if (ref)
				ref->flags |= PACKREF_SHADOWED;

Vicent Marti committed
904
			return 0;
905
		}
Vicent Marti committed
906

907
		git_error_clear();
908
	}
909

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

		if (ref->flags & PACKREF_SHADOWED)
			continue;
918
		if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0)
919
			continue;
920

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

926
	return error;
927 928
}

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

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

941
		if (loose_lookup(NULL, backend, path) == 0) {
942 943 944 945
			ref = git_sortedcache_lookup(iter->cache, path);
			if (ref)
				ref->flags |= PACKREF_SHADOWED;

946 947
			*out = path;
			return 0;
948 949
		}

950
		git_error_clear();
951 952
	}

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

		if (ref->flags & PACKREF_SHADOWED)
			continue;
961
		if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0)
962 963
			continue;

964 965 966
		*out = ref->name;
		error = 0;
		break;
967 968
	}

969
	return error;
970 971 972 973 974
}

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

979
	GIT_ASSERT_ARG(backend);
980 981

	iter = git__calloc(1, sizeof(refdb_fs_iter));
982
	GIT_ERROR_CHECK_ALLOC(iter);
983

984 985
	if ((error = git_pool_init(&iter->pool, 1)) < 0)
		goto out;
986

987 988
	if ((error = git_vector_init(&iter->loose, 8, NULL)) < 0)
		goto out;
989 990

	if (glob != NULL &&
991 992 993 994 995
	    (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL) {
		error = GIT_ERROR_NOMEMORY;
		goto out;
	}

996
	if ((error = iter_load_loose_paths(backend, iter)) < 0)
997 998
		goto out;

999
	if ((error = packed_reload(backend)) < 0)
1000
		goto out;
1001 1002 1003

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

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

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

1042 1043
	if ((error = packed_reload(backend)) < 0)
		return error;
Vicent Marti committed
1044 1045 1046 1047

	if (!force) {
		int exists;

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

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

1061 1062
	if ((error = git_sortedcache_rlock(backend->refcache)) < 0)
		return error;
1063 1064

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

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

1075
	git_sortedcache_runlock(backend->refcache);
Vicent Marti committed
1076 1077
	return 0;
}
1078

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

1085 1086 1087
	GIT_ASSERT_ARG(file);
	GIT_ASSERT_ARG(backend);
	GIT_ASSERT_ARG(name);
1088

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

1094 1095 1096 1097 1098
	if (is_per_worktree_ref(name))
		basedir = backend->gitpath;
	else
		basedir = backend->commonpath;

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

1105 1106
	if ((error = loose_path(&ref_path, basedir, name)) < 0)
		return error;
1107

1108
	filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS;
1109
	if (backend->fsync)
1110 1111 1112
		filebuf_flags |= GIT_FILEBUF_FSYNC;

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

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

1117
	git_str_dispose(&ref_path);
1118
	return error;
1119
}
1120

1121 1122
static int loose_commit(git_filebuf *file, const git_reference *ref)
{
1123 1124
	GIT_ASSERT_ARG(file);
	GIT_ASSERT_ARG(ref);
1125

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

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

1137
	return git_filebuf_commit(file);
1138 1139
}

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

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

	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,
1164 1165 1166
	const char *old_target,
	const git_signature *who,
	const char *message);
1167 1168 1169 1170 1171

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

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

	git__free(lock);
	return error;
}

1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203
/*
 * 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;

1204
	if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL)
1205 1206 1207 1208 1209
		return 0;

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

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

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

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

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

1288 1289 1290 1291
	/* 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);
1292
		git_oid current_id;
1293

1294
		if (!ref || !(ref->flags & PACKREF_WAS_LOOSE))
1295 1296
			continue;

1297 1298
		git_filebuf_cleanup(&lock);

1299 1300 1301
		/* 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 */
1302
		if (error == GIT_EEXISTS || error == GIT_ENOTFOUND)
1303 1304 1305
			continue;

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

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

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

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

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

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

1337
	git_str_dispose(&ref_content);
1338
	git_filebuf_cleanup(&lock);
1339
	return 0;
1340 1341 1342 1343 1344 1345 1346
}

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

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

1357
	packed_map_free(backend);
1358 1359 1360

	git_mutex_unlock(&backend->prlock);

1361
	/* lock the cache to updates while we do this */
1362 1363
	if ((error = git_sortedcache_wlock(refcache)) < 0)
		return error;
1364

1365
	if (backend->fsync)
1366 1367
		open_flags = GIT_FILEBUF_FSYNC;

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

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

1378 1379
	for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) {
		struct packref *ref = git_sortedcache_entry(refcache, i);
1380 1381 1382 1383 1384

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

1386
		if ((error = packed_find_peel(backend, ref)) < 0)
1387
			goto fail;
1388

1389
		if ((error = packed_write_ref(ref, &pack_file)) < 0)
1390
			goto fail;
1391 1392 1393 1394
	}

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

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

1403 1404
	git_sortedcache_updated(refcache);
	git_sortedcache_wunlock(refcache);
1405 1406 1407 1408

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

1409
fail:
1410
	git_filebuf_cleanup(&pack_file);
1411
	git_sortedcache_wunlock(refcache);
1412

1413
	return error;
1414 1415
}

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

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

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

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

	git_sortedcache_wunlock(backend->refcache);

1438 1439
	if (found)
		error = packed_write(backend);
1440 1441 1442 1443 1444

cleanup:
	return error;
}

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

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

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

1458 1459 1460
	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;
1461
		goto out;
1462
	}
1463 1464

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

1474
	if (old_id && old_ref->type == GIT_REFERENCE_DIRECT)
1475 1476
		*cmp = git_oid_cmp(old_id, &old_ref->target.oid);

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

out:
	git_reference_free(old_ref);

	return error;
}

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

1507 1508 1509 1510 1511
	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;
1512

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

1517 1518 1519
	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;
1520

1521
out:
1522
	git_reference_free(head);
1523
	git_refdb_free(refdb);
1524 1525 1526
	return error;
}

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

1540
	GIT_ASSERT_ARG(backend);
1541

1542
	if ((error = reference_path_available(backend, ref->name, NULL, force)) < 0)
Vicent Marti committed
1543 1544
		return error;

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

1549
	return refdb_fs_backend__write_tail(_backend, ref, &file, true, old_id, old_target, who, message);
1550 1551 1552 1553 1554 1555 1556 1557
}

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

1567
	if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0)
1568
		goto on_error;
1569 1570

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

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

1591
	if (update_reflog) {
1592 1593 1594 1595
		git_refdb *refdb;

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

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

1606
	return loose_commit(file, ref);
1607 1608

on_error:
1609
        git_filebuf_cleanup(file);
1610
        return error;
1611 1612
}

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

1623 1624
	GIT_ASSERT_ARG(backend);
	GIT_ASSERT_ARG(ref_name);
1625

1626
	if ((error = git_str_sets(&relative_path, ref_name)) < 0)
1627 1628
		goto cleanup;

1629 1630 1631 1632
	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/")) {
1633

1634
		git_str_truncate(&relative_path, commonlen);
1635

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

1645
		if (!error)
1646
			error = git_path_validate_str_length(NULL, &base_path);
1647

1648 1649 1650 1651
		if (error < 0)
			goto cleanup;

		error = git_futils_rmdir_r(ref_name + commonlen,
1652
			git_str_cstr(&base_path),
1653
			GIT_RMDIR_EMPTY_PARENTS | GIT_RMDIR_SKIP_ROOT);
1654

1655 1656
		if (error == GIT_ENOTFOUND)
			error = 0;
1657 1658 1659
	}

cleanup:
1660 1661
	git_str_dispose(&relative_path);
	git_str_dispose(&base_path);
1662
	return error;
1663 1664
}

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

1674 1675
	GIT_ASSERT_ARG(backend);
	GIT_ASSERT_ARG(ref_name);
1676

1677 1678 1679
	if ((error = loose_lock(&file, backend, ref_name)) < 0)
		return error;

1680 1681 1682 1683 1684
	if ((error = refdb_reflog_fs__delete(_backend, ref_name)) < 0) {
		git_filebuf_cleanup(&file);
		return error;
	}

1685 1686 1687
	return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target);
}

1688 1689
static int loose_delete(refdb_fs_backend *backend, const char *ref_name)
{
1690
	git_str path = GIT_STR_INIT;
1691 1692
	int error = 0;

1693 1694
	if ((error = loose_path(&path, backend->commonpath, ref_name)) < 0)
		return error;
1695

1696
	error = p_unlink(path.ptr);
1697 1698 1699 1700 1701
	if (error < 0 && errno == ENOENT)
		error = GIT_ENOTFOUND;
	else if (error != 0)
		error = -1;

1702
	git_str_dispose(&path);
1703 1704 1705 1706

	return error;
}

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

1717 1718 1719 1720 1721
	error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target);
	if (error < 0)
		goto cleanup;

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

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

1747 1748
	if (error == 0)
		packed_deleted = 1;
1749

1750
	if ((error = loose_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND)
1751
		goto cleanup;
Vicent Marti committed
1752

1753
	if (error == GIT_ENOTFOUND) {
1754
		error = packed_deleted ? 0 : ref_error_notfound(ref_name);
1755 1756 1757 1758
		goto cleanup;
	}

cleanup:
1759
	git_filebuf_cleanup(file);
1760
	if (error == 0)
1761
		error = refdb_fs_backend__prune_refs(backend, ref_name, "");
1762
	return error;
1763 1764
}

1765 1766
static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name);

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

1781
	GIT_ASSERT_ARG(backend);
Vicent Marti committed
1782

1783 1784 1785
	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
1786 1787
		return error;

1788
	if ((error = refdb_fs_backend__delete(_backend, old_name, NULL, NULL)) < 0) {
Vicent Marti committed
1789 1790 1791 1792
		git_reference_free(old);
		return error;
	}

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

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

1825
	*out = new;
Vicent Marti committed
1826 1827 1828
	return 0;
}

1829 1830
static int refdb_fs_backend__compress(git_refdb_backend *_backend)
{
1831
	int error;
1832
	refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
1833

1834
	GIT_ASSERT_ARG(backend);
1835

1836 1837 1838 1839
	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;
1840 1841 1842 1843 1844 1845

	return 0;
}

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

1848 1849
	if (!backend)
		return;
1850

1851
	git_sortedcache_free(backend->refcache);
1852 1853

	git_mutex_lock(&backend->prlock);
1854
	packed_map_free(backend);
1855 1856 1857
	git_mutex_unlock(&backend->prlock);
	git_mutex_free(&backend->prlock);

1858
	git__free(backend->gitpath);
1859
	git__free(backend->commonpath);
1860 1861 1862
	git__free(backend);
}

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

1868 1869
	if (!in)
		goto done;
1870

1871
	git_str_puts(&path, in);
1872 1873

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

	parts = end = git__strdup(repo->namespace);
	if (parts == NULL)
1881
		goto done;
1882

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

1892
	git_str_printf(&path, "refs/namespaces/%s/refs", end);
Vicent Marti committed
1893
	git__free(parts);
1894 1895

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

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

done:
1906
	git_str_dispose(&path);
1907
	return out;
1908 1909
}

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

	*reflog = NULL;

	log = git__calloc(1, sizeof(git_reflog));
1920
	GIT_ERROR_CHECK_ALLOC(log);
1921 1922

	log->ref_name = git__strdup(name);
1923
	GIT_ERROR_CHECK_ALLOC(log->ref_name);
1924

1925 1926
	log->oid_type = oid_type;

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

1942 1943
	if ((git_parse_ctx_init(&parser, buf, buf_size)) < 0)
		return -1;
1944

1945
	for (; parser.remain_len; git_parse_advance_line(&parser)) {
1946
		git_reflog_entry *entry;
1947 1948
		const char *sig;
		char c;
1949

1950 1951 1952 1953
		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);
1954

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

1960 1961 1962
		sig = parser.line;
		while (git_parse_peek(&c, &parser, 0) == 0 && c != '\t' && c != '\n')
			git_parse_advance_chars(&parser, 1);
1963

1964
		if (git_signature__parse(entry->committer, &sig, parser.line, NULL, 0) < 0)
1965
			goto next;
1966

1967 1968 1969
		if (c == '\t') {
			size_t len;
			git_parse_advance_chars(&parser, 1);
1970

1971 1972 1973
			len = parser.line_len;
			if (parser.line[len - 1] == '\n')
				len--;
1974

1975
			entry->msg = git__strndup(parser.line, len);
1976
			GIT_ERROR_CHECK_ALLOC(entry->msg);
1977
		}
1978

1979 1980 1981 1982
		if ((git_vector_insert(&log->entries, entry)) < 0) {
			git_reflog_entry__free(entry);
			return -1;
		}
1983

1984
		continue;
1985

1986 1987 1988
next:
		git_reflog_entry__free(entry);
	}
1989

1990
	return 0;
1991 1992 1993 1994 1995 1996 1997 1998 1999 2000
}

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,
2001
			O_WRONLY | O_CREAT,
2002 2003 2004 2005 2006 2007
			GIT_REFLOG_FILE_MODE)) < 0)
		return -1;

	return p_close(fd);
}

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

2015
	GIT_ASSERT_ARG(_backend && name);
2016

2017
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
2018 2019
	repo = backend->repo;

2020
	if ((error = reflog_path(&path, repo, name)) < 0)
2021 2022
		return error;

2023 2024
	error = create_new_reflog_file(git_str_cstr(&path));
	git_str_dispose(&path);
2025 2026

	return error;
2027 2028
}

2029 2030 2031
static int has_reflog(git_repository *repo, const char *name)
{
	int ret = 0;
2032
	git_str path = GIT_STR_INIT;
2033

2034
	if (reflog_path(&path, repo, name) < 0)
2035 2036
		goto cleanup;

2037
	ret = git_fs_path_isfile(git_str_cstr(&path));
2038 2039

cleanup:
2040
	git_str_dispose(&path);
2041 2042 2043
	return ret;
}

2044 2045 2046 2047
static int refdb_reflog_fs__has_log(git_refdb_backend *_backend, const char *name)
{
	refdb_fs_backend *backend;

2048 2049
	GIT_ASSERT_ARG(_backend);
	GIT_ASSERT_ARG(name);
2050

2051
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
2052 2053 2054 2055

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

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

2068 2069 2070
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(_backend);
	GIT_ASSERT_ARG(name);
2071

2072
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
2073 2074
	repo = backend->repo;

2075
	if (reflog_alloc(&log, name, backend->oid_type) < 0)
2076 2077
		return -1;

2078
	if (reflog_path(&log_path, repo, name) < 0)
2079 2080
		goto cleanup;

2081
	error = git_futils_readbuffer(&log_file, git_str_cstr(&log_path));
2082 2083 2084 2085
	if (error < 0 && error != GIT_ENOTFOUND)
		goto cleanup;

	if ((error == GIT_ENOTFOUND) &&
2086
		((error = create_new_reflog_file(git_str_cstr(&log_path))) < 0))
2087
		goto cleanup;
2088

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

	*out = log;
	goto success;

cleanup:
	git_reflog_free(log);

success:
2100 2101
	git_str_dispose(&log_file);
	git_str_dispose(&log_path);
2102 2103 2104 2105 2106

	return error;
}

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

2116 2117
	git_oid_tostr(raw_old, GIT_OID_MAX_HEXSIZE + 1, oid_old);
	git_oid_tostr(raw_new, GIT_OID_MAX_HEXSIZE + 1, oid_new);
2118

2119
	git_str_clear(buf);
2120

2121 2122 2123
	git_str_puts(buf, raw_old);
	git_str_putc(buf, ' ');
	git_str_puts(buf, raw_new);
2124 2125 2126 2127

	git_signature__writebuf(buf, " ", committer);

	/* drop trailing LF */
2128
	git_str_rtrim(buf);
2129 2130

	if (msg) {
2131 2132
		size_t i;

2133 2134
		git_str_putc(buf, '\t');
		git_str_puts(buf, msg);
2135 2136 2137 2138

		for (i = 0; i < buf->size - 2; i++)
			if (buf->ptr[i] == '\n')
				buf->ptr[i] = ' ';
2139
		git_str_rtrim(buf);
2140 2141
	}

2142
	git_str_putc(buf, '\n');
2143

2144
	return git_str_oom(buf);
2145 2146
}

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

	repo = backend->repo;

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

2160
	if (reflog_path(&log_path, repo, refname) < 0)
2161 2162
		return -1;

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

2170
	error = git_filebuf_open(file, git_str_cstr(&log_path), 0, GIT_REFLOG_FILE_MODE);
2171 2172

cleanup:
2173
	git_str_dispose(&log_path);
2174 2175 2176 2177

	return error;
}

2178 2179 2180 2181 2182 2183
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;
2184
	git_str log = GIT_STR_INIT;
2185 2186
	git_filebuf fbuf = GIT_FILEBUF_INIT;

2187 2188
	GIT_ASSERT_ARG(_backend);
	GIT_ASSERT_ARG(reflog);
2189

2190
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
2191

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

2203
	error = git_filebuf_commit(&fbuf);
2204 2205 2206 2207 2208 2209
	goto success;

cleanup:
	git_filebuf_cleanup(&fbuf);

success:
2210
	git_str_dispose(&log);
2211

2212 2213 2214
	return error;
}

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

2229
	is_symbolic = ref->type == GIT_REFERENCE_SYMBOLIC;
2230 2231 2232 2233 2234

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

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

2239 2240 2241
	git_oid_clear(&old_id, backend->oid_type);
	git_oid_clear(&new_id, backend->oid_type);

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

2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261
	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;
2262

2263
			git_error_clear();
2264
		}
2265
	}
2266

2267 2268 2269
	if ((error = serialize_reflog_entry(&buf, &old_id, &new_id, who, message)) < 0)
		goto cleanup;

2270
	if ((error = reflog_path(&path, repo, ref->name)) < 0)
2271 2272
		goto cleanup;

2273
	if (((error = git_futils_mkpath2file(git_str_cstr(&path), 0777)) < 0) &&
2274 2275 2276 2277
	    (error != GIT_EEXISTS)) {
		goto cleanup;
	}

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

		if (error != 0)
			goto cleanup;
2293 2294
	}

2295 2296
	open_flags = O_WRONLY | O_CREAT | O_APPEND;

2297
	if (backend->fsync)
2298 2299
		open_flags |= O_FSYNC;

2300
	error = git_futils_writebuffer(&buf, git_str_cstr(&path), open_flags, GIT_REFLOG_FILE_MODE);
2301 2302

cleanup:
2303 2304
	git_str_dispose(&buf);
	git_str_dispose(&path);
2305

2306 2307 2308 2309 2310 2311
	return error;
}

static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name)
{
	int error = 0, fd;
2312 2313 2314 2315
	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;
2316 2317 2318
	git_repository *repo;
	refdb_fs_backend *backend;

2319 2320 2321
	GIT_ASSERT_ARG(_backend);
	GIT_ASSERT_ARG(old_name);
	GIT_ASSERT_ARG(new_name);
2322

2323
	backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
2324 2325 2326
	repo = backend->repo;

	if ((error = git_reference__normalize_name(
2327
		&normalized, new_name, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL)) < 0)
2328 2329
			return error;

2330
	if (git_str_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0)
2331 2332
		return -1;

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

2336
	if ((error = loose_path(&new_path, git_str_cstr(&temp_path), git_str_cstr(&normalized))) < 0)
2337
		return error;
2338

2339
	if (!git_fs_path_exists(git_str_cstr(&old_path))) {
2340 2341 2342 2343
		error = GIT_ENOTFOUND;
		goto cleanup;
	}

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

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

	p_close(fd);

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

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

2373
	if (git_futils_mkpath2file(git_str_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) {
2374 2375 2376 2377
		error = -1;
		goto cleanup;
	}

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

cleanup:
2384 2385 2386 2387
	git_str_dispose(&temp_path);
	git_str_dispose(&old_path);
	git_str_dispose(&new_path);
	git_str_dispose(&normalized);
2388 2389 2390 2391 2392 2393

	return error;
}

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

2398 2399
	GIT_ASSERT_ARG(_backend);
	GIT_ASSERT_ARG(name);
2400

2401
	if ((error = reflog_path(&path, backend->repo, name)) < 0)
2402
		goto out;
2403

2404
	if (!git_fs_path_exists(path.ptr))
2405
		goto out;
2406

2407 2408
	if ((error = p_unlink(path.ptr)) < 0)
		goto out;
2409

2410
	error = refdb_fs_backend__prune_refs(backend, name, GIT_REFLOG_DIR);
2411 2412

out:
2413
	git_str_dispose(&path);
2414 2415 2416 2417

	return error;
}

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

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

2433

2434 2435 2436
	if (git_refdb_init_backend(&backend->parent, GIT_REFDB_BACKEND_VERSION) < 0)
		goto fail;

2437
	backend->repo = repository;
2438
	backend->oid_type = repository->oid_type;
Vicent Marti committed
2439

2440 2441
	if (repository->gitdir) {
		backend->gitpath = setup_namespace(repository, repository->gitdir);
Vicent Marti committed
2442

2443 2444 2445 2446 2447 2448 2449 2450 2451 2452
		if (backend->gitpath == NULL)
			goto fail;
	}

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

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

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

2460
	git_str_dispose(&gitpath);
2461

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

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

	*backend_out = (git_refdb_backend *)backend;
	return 0;
2494 2495

fail:
2496
	git_mutex_free(&backend->prlock);
2497
	git_str_dispose(&gitpath);
2498
	git__free(backend->gitpath);
2499
	git__free(backend->commonpath);
2500 2501
	git__free(backend);
	return -1;
2502
}