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

#include "refs.h"
#include "hash.h"
#include "repository.h"
#include "fileops.h"
12
#include "pack.h"
13
#include "reflog.h"
14

Vicent Marti committed
15 16
#include <git2/tag.h>
#include <git2/object.h>
17
#include <git2/oid.h>
18
#include <git2/branch.h>
Vicent Marti committed
19

20
GIT__USE_STRMAP;
21

22 23
#define DEFAULT_NESTING_LEVEL	5
#define MAX_NESTING_LEVEL		10
24

25 26 27 28
enum {
	GIT_PACKREF_HAS_PEEL = 1,
	GIT_PACKREF_WAS_LOOSE = 2
};
29

30 31 32 33 34 35
struct packref {
	git_oid oid;
	git_oid peel;
	char flags;
	char name[GIT_FLEX_ARRAY];
};
36

37
static int reference_read(
38
	git_buf *file_content,
39 40 41 42
	time_t *mtime,
	const char *repo_path,
	const char *ref_name,
	int *updated);
43

Vicent Marti committed
44
/* loose refs */
45 46
static int loose_parse_symbolic(git_reference *ref, git_buf *file_content);
static int loose_parse_oid(git_oid *ref, git_buf *file_content);
47 48 49 50
static int loose_lookup(git_reference *ref);
static int loose_lookup_to_packfile(struct packref **ref_out,
	git_repository *repo, const char *name);
static int loose_write(git_reference *ref);
Vicent Marti committed
51 52

/* packed refs */
53 54 55 56
static int packed_parse_peel(struct packref *tag_ref,
	const char **buffer_out, const char *buffer_end);
static int packed_parse_oid(struct packref **ref_out,
	const char **buffer_out, const char *buffer_end);
Vicent Marti committed
57 58
static int packed_load(git_repository *repo);
static int packed_loadloose(git_repository *repository);
59 60
static int packed_write_ref(struct packref *ref, git_filebuf *file);
static int packed_find_peel(git_repository *repo, struct packref *ref);
Vicent Marti committed
61 62
static int packed_remove_loose(git_repository *repo, git_vector *packing_list);
static int packed_sort(const void *a, const void *b);
63
static int packed_lookup(git_reference *ref);
Vicent Marti committed
64 65
static int packed_write(git_repository *repo);

66
/* internal helpers */
67 68
static int reference_path_available(git_repository *repo,
	const char *ref, const char *old_ref);
69 70
static int reference_delete(git_reference *ref);
static int reference_lookup(git_reference *ref);
71

72
void git_reference_free(git_reference *reference)
73
{
74 75
	if (reference == NULL)
		return;
76

77
	git__free(reference->name);
78
	reference->name = NULL;
79

80
	if (reference->flags & GIT_REF_SYMBOLIC) {
81
		git__free(reference->target.symbolic);
82 83
		reference->target.symbolic = NULL;
	}
84

85
	git__free(reference);
86 87
}

Vicent Martí committed
88
static int reference_alloc(
89
	git_reference **ref_out,
Vicent Marti committed
90
	git_repository *repo,
91
	const char *name)
Vicent Marti committed
92
{
93
	git_reference *reference = NULL;
94

95 96
	assert(ref_out && repo && name);

97
	reference = git__malloc(sizeof(git_reference));
98
	GITERR_CHECK_ALLOC(reference);
99

100
	memset(reference, 0x0, sizeof(git_reference));
101
	reference->owner = repo;
102

103
	reference->name = git__strdup(name);
104
	GITERR_CHECK_ALLOC(reference->name);
105

106
	*ref_out = reference;
107
	return 0;
108 109
}

110 111 112 113 114 115
static int reference_read(
	git_buf *file_content,
	time_t *mtime,
	const char *repo_path,
	const char *ref_name,
	int *updated)
116
{
117
	git_buf path = GIT_BUF_INIT;
118
	int result;
119

120 121
	assert(file_content && repo_path && ref_name);

122
	/* Determine the full path of the file */
123 124
	if (git_buf_joinpath(&path, repo_path, ref_name) < 0)
		return -1;
125

126 127
	result = git_futils_readbuffer_updated(
		file_content, path.ptr, mtime, NULL, updated);
128
	git_buf_free(&path);
129

130
	return result;
131 132
}

133
static int loose_parse_symbolic(git_reference *ref, git_buf *file_content)
134
{
135
	const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);
136
	const char *refname_start;
137

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

140 141 142 143
	if (git_buf_len(file_content) < header_len + 1) {
		giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
		return -1;
	}
144

145
	/*
146
	 * Assume we have already checked for the header
147
	 * before calling this function
148 149
	 */
	refname_start += header_len;
150

151
	ref->target.symbolic = git__strdup(refname_start);
152
	GITERR_CHECK_ALLOC(ref->target.symbolic);
153

154
	return 0;
155 156
}

157
static int loose_parse_oid(git_oid *oid, git_buf *file_content)
158
{
159 160 161 162 163 164 165 166 167 168
	size_t len;
	const char *str;

	len = git_buf_len(file_content);
	if (len < GIT_OID_HEXSZ)
		goto corrupted;

	/* str is guranteed to be zero-terminated */
	str = git_buf_cstr(file_content);

Rick Bradley committed
169
	/* we need to get 40 OID characters from the file */
170 171 172 173 174 175
	if (git_oid_fromstr(oid, git_buf_cstr(file_content)) < 0)
		goto corrupted;

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

178
corrupted:
179 180
	giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
	return -1;
181 182
}

183
static git_ref_t loose_guess_rtype(const git_buf *full_path)
184
{
185
	git_buf ref_file = GIT_BUF_INIT;
186
	git_ref_t type;
187 188 189

	type = GIT_REF_INVALID;

190
	if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) {
191
		if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0)
192 193 194 195 196
			type = GIT_REF_SYMBOLIC;
		else
			type = GIT_REF_OID;
	}

197
	git_buf_free(&ref_file);
198 199 200
	return type;
}

201 202
static int loose_lookup(git_reference *ref)
{
203
	int result, updated;
204
	git_buf ref_file = GIT_BUF_INIT;
205

206 207 208 209 210
	result = reference_read(&ref_file, &ref->mtime,
		ref->owner->path_repository, ref->name, &updated);

	if (result < 0)
		return result;
211 212

	if (!updated)
213
		return 0;
214

Vicent Martí committed
215
	if (ref->flags & GIT_REF_SYMBOLIC) {
216
		git__free(ref->target.symbolic);
Vicent Martí committed
217 218
		ref->target.symbolic = NULL;
	}
219 220 221

	ref->flags = 0;

222
	if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) {
223
		ref->flags |= GIT_REF_SYMBOLIC;
224
		git_buf_rtrim(&ref_file);
225
		result = loose_parse_symbolic(ref, &ref_file);
226 227
	} else {
		ref->flags |= GIT_REF_OID;
228
		result = loose_parse_oid(&ref->target.oid, &ref_file);
229 230
	}

231
	git_buf_free(&ref_file);
232
	return result;
233 234 235 236
}

static int loose_lookup_to_packfile(
		struct packref **ref_out,
237
		git_repository *repo,
238
		const char *name)
239
{
240
	git_buf ref_file = GIT_BUF_INIT;
241 242
	struct packref *ref = NULL;
	size_t name_len;
243

244
	*ref_out = NULL;
245

246 247
	if (reference_read(&ref_file, NULL, repo->path_repository, name, NULL) < 0)
		return -1;
248

249 250
	git_buf_rtrim(&ref_file);

251 252
	name_len = strlen(name);
	ref = git__malloc(sizeof(struct packref) + name_len + 1);
253
	GITERR_CHECK_ALLOC(ref);
254

255 256
	memcpy(ref->name, name, name_len);
	ref->name[name_len] = 0;
257

258 259
	if (loose_parse_oid(&ref->oid, &ref_file) < 0) {
		git_buf_free(&ref_file);
260
		git__free(ref);
261 262
		return -1;
	}
263

264 265
	ref->flags = GIT_PACKREF_WAS_LOOSE;

266
	*ref_out = ref;
267
	git_buf_free(&ref_file);
268
	return 0;
269 270
}

271
static int loose_write(git_reference *ref)
Vicent Marti committed
272
{
273
	git_filebuf file = GIT_FILEBUF_INIT;
274
	git_buf ref_path = GIT_BUF_INIT;
275
	struct stat st;
Vicent Marti committed
276

277
	/* Remove a possibly existing empty directory hierarchy
278 279
	 * which name would collide with the reference name
	 */
280 281 282 283 284
	if (git_futils_rmdir_r(ref->name, ref->owner->path_repository,
		GIT_RMDIR_SKIP_NONEMPTY) < 0)
		return -1;

	if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0)
285
		return -1;
286

287 288 289 290
	if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {
		git_buf_free(&ref_path);
		return -1;
	}
Vicent Marti committed
291

292
	git_buf_free(&ref_path);
Vicent Marti committed
293

294
	if (ref->flags & GIT_REF_OID) {
295
		char oid[GIT_OID_HEXSZ + 1];
Vicent Marti committed
296

297 298
		git_oid_fmt(oid, &ref->target.oid);
		oid[GIT_OID_HEXSZ] = '\0';
Vicent Marti committed
299

300
		git_filebuf_printf(&file, "%s\n", oid);
Vicent Marti committed
301

302 303
	} else if (ref->flags & GIT_REF_SYMBOLIC) {
		git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic);
Vicent Marti committed
304
	} else {
305
		assert(0); /* don't let this happen */
Vicent Marti committed
306 307
	}

308
	if (p_stat(ref_path.ptr, &st) == 0)
309 310
		ref->mtime = st.st_mtime;

311
	return git_filebuf_commit(&file, GIT_REFS_FILE_MODE);
Vicent Marti committed
312 313 314
}

static int packed_parse_peel(
315
		struct packref *tag_ref,
316
		const char **buffer_out,
317
		const char *buffer_end)
318
{
319 320 321
	const char *buffer = *buffer_out + 1;

	assert(buffer[-1] == '^');
322 323

	/* Ensure it's not the first entry of the file */
324
	if (tag_ref == NULL)
325
		goto corrupt;
326 327

	/* Ensure reference is a tag */
328
	if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0)
329
		goto corrupt;
330

331
	if (buffer + GIT_OID_HEXSZ > buffer_end)
332
		goto corrupt;
333

334
	/* Is this a valid object id? */
335
	if (git_oid_fromstr(&tag_ref->peel, buffer) < 0)
336
		goto corrupt;
337

338 339 340 341
	buffer = buffer + GIT_OID_HEXSZ;
	if (*buffer == '\r')
		buffer++;

342 343 344 345 346 347
	if (buffer != buffer_end) {
		if (*buffer == '\n')
			buffer++;
		else
			goto corrupt;
	}
348

349
	*buffer_out = buffer;
350 351 352 353 354
	return 0;

corrupt:
	giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
	return -1;
355 356
}

Vicent Marti committed
357
static int packed_parse_oid(
358
		struct packref **ref_out,
359 360
		const char **buffer_out,
		const char *buffer_end)
361
{
362
	struct packref *ref = NULL;
363 364 365 366

	const char *buffer = *buffer_out;
	const char *refname_begin, *refname_end;

367
	size_t refname_len;
368
	git_oid id;
369

370
	refname_begin = (buffer + GIT_OID_HEXSZ + 1);
371 372
	if (refname_begin >= buffer_end || refname_begin[-1] != ' ')
		goto corrupt;
373

374
	/* Is this a valid object id? */
375 376
	if (git_oid_fromstr(&id, buffer) < 0)
		goto corrupt;
377

378
	refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
379
	if (refname_end == NULL)
380
		refname_end = buffer_end;
381

382 383
	if (refname_end[-1] == '\r')
		refname_end--;
384

385
	refname_len = refname_end - refname_begin;
386

387
	ref = git__malloc(sizeof(struct packref) + refname_len + 1);
388
	GITERR_CHECK_ALLOC(ref);
389

390 391
	memcpy(ref->name, refname_begin, refname_len);
	ref->name[refname_len] = 0;
392

393
	git_oid_cpy(&ref->oid, &id);
394 395

	ref->flags = 0;
396

397
	*ref_out = ref;
398 399
	*buffer_out = refname_end + 1;

400
	return 0;
401

402
corrupt:
403
	git__free(ref);
404 405
	giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
	return -1;
406 407
}

Vicent Marti committed
408
static int packed_load(git_repository *repo)
409
{
410
	int result, updated;
411
	git_buf packfile = GIT_BUF_INIT;
412
	const char *buffer_start, *buffer_end;
Vicent Marti committed
413 414
	git_refcache *ref_cache = &repo->references;

415 416
	/* First we make sure we have allocated the hash table */
	if (ref_cache->packfile == NULL) {
417
		ref_cache->packfile = git_strmap_alloc();
418
		GITERR_CHECK_ALLOC(ref_cache->packfile);
419 420
	}

421
	result = reference_read(&packfile, &ref_cache->packfile_time,
422
		repo->path_repository, GIT_PACKEDREFS_FILE, &updated);
Vicent Marti committed
423

424 425 426 427 428 429 430
	/*
	 * If we couldn't find the file, we need to clear the table and
	 * return. On any other error, we return that error. If everything
	 * went fine and the file wasn't updated, then there's nothing new
	 * for us here, so just return. Anything else means we need to
	 * refresh the packed refs.
	 */
431
	if (result == GIT_ENOTFOUND) {
432
		git_strmap_clear(ref_cache->packfile);
433
		return 0;
434
	}
435

436 437 438 439 440 441
	if (result < 0)
		return -1;

	if (!updated)
		return 0;

442 443 444 445
	/*
	 * At this point, we want to refresh the packed refs. We already
	 * have the contents in our buffer.
	 */
446
	git_strmap_clear(ref_cache->packfile);
447

448 449
	buffer_start = (const char *)packfile.ptr;
	buffer_end = (const char *)(buffer_start) + packfile.size;
450

451 452
	while (buffer_start < buffer_end && buffer_start[0] == '#') {
		buffer_start = strchr(buffer_start, '\n');
453 454 455
		if (buffer_start == NULL)
			goto parse_failed;

456
		buffer_start++;
457
	}
458

459
	while (buffer_start < buffer_end) {
460
		int err;
461
		struct packref *ref = NULL;
462

463 464
		if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0)
			goto parse_failed;
465

466
		if (buffer_start[0] == '^') {
467 468
			if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0)
				goto parse_failed;
469
		}
470

471
		git_strmap_insert(ref_cache->packfile, ref->name, ref, err);
472 473
		if (err < 0)
			goto parse_failed;
474
	}
475

476
	git_buf_free(&packfile);
477
	return 0;
478

479
parse_failed:
480
	git_strmap_free(ref_cache->packfile);
481
	ref_cache->packfile = NULL;
482
	git_buf_free(&packfile);
483
	return -1;
484
}
485

486 487

struct dirent_list_data {
488
	git_repository *repo;
489 490
	size_t repo_path_len;
	unsigned int list_flags;
491 492 493

	int (*callback)(const char *, void *);
	void *callback_payload;
494
	int callback_error;
495 496
};

497
static int _dirent_loose_listall(void *_data, git_buf *full_path)
498 499
{
	struct dirent_list_data *data = (struct dirent_list_data *)_data;
500
	const char *file_path = full_path->ptr + data->repo_path_len;
501

502
	if (git_path_isdir(full_path->ptr) == true)
503
		return git_path_direach(full_path, _dirent_loose_listall, _data);
504

505
	/* do not add twice a reference that exists already in the packfile */
506
	if ((data->list_flags & GIT_REF_PACKED) != 0 &&
507
		git_strmap_exists(data->repo->references.packfile, file_path))
508
		return 0;
509

510 511
	if (data->list_flags != GIT_REF_LISTALL) {
		if ((data->list_flags & loose_guess_rtype(full_path)) == 0)
512
			return 0; /* we are filtering out this reference */
513
	}
514

515 516 517 518
	/* Locked references aren't returned */
	if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION))
		return 0;

519 520 521 522
	if (data->callback(file_path, data->callback_payload))
		data->callback_error = GIT_EUSER;

	return data->callback_error;
523 524
}

525
static int _dirent_loose_load(void *data, git_buf *full_path)
526
{
Vicent Marti committed
527
	git_repository *repository = (git_repository *)data;
528
	void *old_ref = NULL;
529
	struct packref *ref;
530
	const char *file_path;
531
	int err;
532

533
	if (git_path_isdir(full_path->ptr) == true)
534
		return git_path_direach(full_path, _dirent_loose_load, repository);
535

536
	file_path = full_path->ptr + strlen(repository->path_repository);
537

538 539
	if (loose_lookup_to_packfile(&ref, repository, file_path) < 0)
		return -1;
Vicent Marti committed
540

541
	git_strmap_insert2(
542 543
		repository->references.packfile, ref->name, ref, old_ref, err);
	if (err < 0) {
544 545
		git__free(ref);
		return -1;
Vicent Marti committed
546
	}
547

548 549
	git__free(old_ref);
	return 0;
550 551
}

Vicent Marti committed
552 553 554 555 556 557 558
/*
 * 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(git_repository *repository)
559
{
560
	git_buf refs_path = GIT_BUF_INIT;
561
	int result;
562

Vicent Marti committed
563 564
	/* the packfile must have been previously loaded! */
	assert(repository->references.packfile);
565

566 567
	if (git_buf_joinpath(&refs_path, repository->path_repository, GIT_REFS_DIR) < 0)
		return -1;
568

Vicent Marti committed
569 570 571
	/*
	 * Load all the loose files from disk into the Packfile table.
	 * This will overwrite any old packed entries with their
572
	 * updated loose versions
Vicent Marti committed
573
	 */
574
	result = git_path_direach(&refs_path, _dirent_loose_load, repository);
575
	git_buf_free(&refs_path);
576 577

	return result;
578
}
579

Vicent Marti committed
580 581 582
/*
 * Write a single reference into a packfile
 */
583
static int packed_write_ref(struct packref *ref, git_filebuf *file)
584
{
Vicent Marti committed
585
	char oid[GIT_OID_HEXSZ + 1];
586

Vicent Marti committed
587 588
	git_oid_fmt(oid, &ref->oid);
	oid[GIT_OID_HEXSZ] = 0;
589

590
	/*
Vicent Marti committed
591 592 593 594 595 596 597 598 599
	 * 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`.
	 */
600
	if (ref->flags & GIT_PACKREF_HAS_PEEL) {
Vicent Marti committed
601
		char peel[GIT_OID_HEXSZ + 1];
602
		git_oid_fmt(peel, &ref->peel);
Vicent Marti committed
603 604
		peel[GIT_OID_HEXSZ] = 0;

605 606
		if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0)
			return -1;
Vicent Marti committed
607
	} else {
608 609
		if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0)
			return -1;
Vicent Marti committed
610 611
	}

612
	return 0;
613 614
}

Vicent Marti committed
615 616 617
/*
 * Find out what object this reference resolves to.
 *
618
 * For references that point to a 'big' tag (e.g. an
Vicent Marti committed
619 620 621 622
 * 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.
 */
623
static int packed_find_peel(git_repository *repo, struct packref *ref)
624
{
625
	git_object *object;
Vicent Marti committed
626

627
	if (ref->flags & GIT_PACKREF_HAS_PEEL)
628
		return 0;
629

Vicent Marti committed
630 631 632 633
	/*
	 * Only applies to tags, i.e. references
	 * in the /refs/tags folder
	 */
634
	if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0)
635
		return 0;
636

Vicent Marti committed
637
	/*
638
	 * Find the tagged object in the repository
Vicent Marti committed
639
	 */
640 641
	if (git_object_lookup(&object, repo, &ref->oid, GIT_OBJ_ANY) < 0)
		return -1;
642

Vicent Marti committed
643
	/*
644 645 646
	 * 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.
Vicent Marti committed
647
	 */
648 649
	if (git_object_type(object) == GIT_OBJ_TAG) {
		git_tag *tag = (git_tag *)object;
650

651 652 653
		/*
		 * Find the object pointed at by this tag
		 */
Russell Belfer committed
654
		git_oid_cpy(&ref->peel, git_tag_target_id(tag));
655
		ref->flags |= GIT_PACKREF_HAS_PEEL;
656 657 658 659 660 661 662

		/*
		 * 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
		 */
	}
Vicent Marti committed
663

664
	git_object_free(object);
665
	return 0;
666
}
667

Vicent Marti committed
668 669 670 671 672 673 674 675 676 677 678 679
/*
 * 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.
 */
static int packed_remove_loose(git_repository *repo, git_vector *packing_list)
680
{
Vicent Marti committed
681
	unsigned int i;
682
	git_buf full_path = GIT_BUF_INIT;
683
	int failed = 0;
Vicent Marti committed
684 685

	for (i = 0; i < packing_list->length; ++i) {
686
		struct packref *ref = git_vector_get(packing_list, i);
687

688
		if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0)
689 690
			continue;

691 692
		if (git_buf_joinpath(&full_path, repo->path_repository, ref->name) < 0)
			return -1; /* critical; do not try to recover on oom */
693

694 695 696
		if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) {
			if (failed)
				continue;
Vicent Marti committed
697

698 699 700 701 702 703
			giterr_set(GITERR_REFERENCE,
				"Failed to remove loose reference '%s' after packing: %s",
				full_path.ptr, strerror(errno));

			failed = 1;
		}
Vicent Marti committed
704 705 706 707 708 709 710 711 712

		/*
		 * if we fail to remove a single file, this is *not* good,
		 * but we should keep going and remove as many as possible.
		 * After we've removed as many files as possible, we return
		 * the error code anyway.
		 */
	}

713
	git_buf_free(&full_path);
714
	return failed ? -1 : 0;
715
}
716

Vicent Marti committed
717
static int packed_sort(const void *a, const void *b)
718
{
719 720
	const struct packref *ref_a = (const struct packref *)a;
	const struct packref *ref_b = (const struct packref *)b;
Vicent Marti committed
721 722

	return strcmp(ref_a->name, ref_b->name);
723 724
}

Vicent Marti committed
725 726 727 728
/*
 * Write all the contents in the in-memory packfile to disk.
 */
static int packed_write(git_repository *repo)
729
{
730
	git_filebuf pack_file = GIT_FILEBUF_INIT;
Vicent Marti committed
731
	unsigned int i;
732
	git_buf pack_file_path = GIT_BUF_INIT;
Vicent Marti committed
733
	git_vector packing_list;
734
	unsigned int total_refs;
735

Vicent Marti committed
736
	assert(repo && repo->references.packfile);
737

738
	total_refs =
739
		(unsigned int)git_strmap_num_entries(repo->references.packfile);
740 741 742

	if (git_vector_init(&packing_list, total_refs, packed_sort) < 0)
		return -1;
743

Vicent Marti committed
744 745
	/* Load all the packfile into a vector */
	{
746
		struct packref *reference;
Vicent Marti committed
747

748
		/* cannot fail: vector already has the right size */
749
		git_strmap_foreach_value(repo->references.packfile, reference, {
750
			git_vector_insert(&packing_list, reference);
751
		});
752 753
	}

Vicent Marti committed
754 755
	/* sort the vector so the entries appear sorted on the packfile */
	git_vector_sort(&packing_list);
756

Vicent Marti committed
757
	/* Now we can open the file! */
758 759
	if (git_buf_joinpath(&pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE) < 0)
		goto cleanup_memory;
760

761 762
	if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0)
		goto cleanup_packfile;
763

764 765 766
	/* Packfiles have a header... apparently
	 * This is in fact not required, but we might as well print it
	 * just for kicks */
767 768
	if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0)
		goto cleanup_packfile;
769

Vicent Marti committed
770
	for (i = 0; i < packing_list.length; ++i) {
771
		struct packref *ref = (struct packref *)git_vector_get(&packing_list, i);
772

773 774
		if (packed_find_peel(repo, ref) < 0)
			goto cleanup_packfile;
775

776 777
		if (packed_write_ref(ref, &pack_file) < 0)
			goto cleanup_packfile;
Vicent Marti committed
778
	}
779

Vicent Marti committed
780 781
	/* if we've written all the references properly, we can commit
	 * the packfile to make the changes effective */
782 783
	if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0)
		goto cleanup_memory;
784

785 786 787 788
	/* when and only when the packfile has been properly written,
	 * we can go ahead and remove the loose refs */
	 if (packed_remove_loose(repo, &packing_list) < 0)
		 goto cleanup_memory;
789

790 791 792 793 794
	 {
		struct stat st;
		if (p_stat(pack_file_path.ptr, &st) == 0)
			repo->references.packfile_time = st.st_mtime;
	 }
795

Vicent Marti committed
796
	git_vector_free(&packing_list);
797
	git_buf_free(&pack_file_path);
798

799 800 801 802 803
	/* we're good now */
	return 0;

cleanup_packfile:
	git_filebuf_cleanup(&pack_file);
804

805 806 807 808 809
cleanup_memory:
	git_vector_free(&packing_list);
	git_buf_free(&pack_file_path);

	return -1;
Vicent Marti committed
810
}
811

812 813 814 815 816 817
struct reference_available_t {
	const char *new_ref;
	const char *old_ref;
	int available;
};

818 819
static int _reference_available_cb(const char *ref, void *data)
{
820
	struct reference_available_t *d;
821

822
	assert(ref && data);
823
	d = (struct reference_available_t *)data;
824

825
	if (!d->old_ref || strcmp(d->old_ref, ref)) {
826 827 828
		size_t reflen = strlen(ref);
		size_t newlen = strlen(d->new_ref);
		size_t cmplen = reflen < newlen ? reflen : newlen;
829
		const char *lead = reflen < newlen ? d->new_ref : ref;
830

831 832 833 834
		if (!strncmp(d->new_ref, ref, cmplen) && lead[cmplen] == '/') {
			d->available = 0;
			return -1;
		}
835 836
	}

837
	return 0;
838 839
}

840
static int reference_path_available(
841 842
	git_repository *repo,
	const char *ref,
843
	const char* old_ref)
844
{
845
	int error;
846
	struct reference_available_t data;
847

848 849 850
	data.new_ref = ref;
	data.old_ref = old_ref;
	data.available = 1;
851

852 853 854 855
	error = git_reference_foreach(
		repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&data);
	if (error < 0)
		return error;
856 857 858

	if (!data.available) {
		giterr_set(GITERR_REFERENCE,
859
			"The path to reference '%s' collides with an existing one", ref);
860
		return -1;
861
	}
862

863
	return 0;
864 865
}

866
static int reference_exists(int *exists, git_repository *repo, const char *ref_name)
867
{
868
	git_buf ref_path = GIT_BUF_INIT;
869

870 871
	if (packed_load(repo) < 0)
		return -1;
872

873 874
	if (git_buf_joinpath(&ref_path, repo->path_repository, ref_name) < 0)
		return -1;
875

876
	if (git_path_isfile(ref_path.ptr) == true ||
877
		git_strmap_exists(repo->references.packfile, ref_path.ptr))
878
	{
879 880 881 882
		*exists = 1;
	} else {
		*exists = 0;
	}
883

884
	git_buf_free(&ref_path);
885 886
	return 0;
}
887

888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919
/*
 * Check if a reference could be written to disk, based on:
 *
 *	- Whether a reference with the same name already exists,
 *	and we are allowing or disallowing overwrites
 *
 *	- Whether the name of the reference would collide with
 *	an existing path
 */
static int reference_can_write(
	git_repository *repo,
	const char *refname,
	const char *previous_name,
	int force)
{
	/* see if the reference shares a path with an existing reference;
	 * if a path is shared, we cannot create the reference, even when forcing */
	if (reference_path_available(repo, refname, previous_name) < 0)
		return -1;

	/* check if the reference actually exists, but only if we are not forcing
	 * the rename. If we are forcing, it's OK to overwrite */
	if (!force) {
		int exists;

		if (reference_exists(&exists, repo, refname) < 0)
			return -1;

		/* We cannot proceed if the reference already exists and we're not forcing
		 * the rename; the existing one would be overwritten */
		if (exists) {
			giterr_set(GITERR_REFERENCE,
920
				"A reference with that name (%s) already exists", refname);
921
			return GIT_EEXISTS;
922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937
		}
	}

	/* FIXME: if the reference exists and we are forcing, do we really need to
	 * remove the reference first?
	 *
	 * Two cases:
	 *
	 *	- the reference already exists and is loose: not a problem, the file
	 *	gets overwritten on disk
	 *
	 *	- the reference already exists and is packed: we write a new one as
	 *	loose, which by all means renders the packed one useless
	 */

	return 0;
938
}
939

940

941 942 943
static int packed_lookup(git_reference *ref)
{
	struct packref *pack_ref = NULL;
944
	git_strmap *packfile_refs;
945
	khiter_t pos;
946

947 948
	if (packed_load(ref->owner) < 0)
		return -1;
949

950 951 952
	/* maybe the packfile hasn't changed at all, so we don't
	 * have to re-lookup the reference */
	if ((ref->flags & GIT_REF_PACKED) &&
953
		ref->mtime == ref->owner->references.packfile_time)
954
		return 0;
955

Vicent Martí committed
956
	if (ref->flags & GIT_REF_SYMBOLIC) {
957
		git__free(ref->target.symbolic);
Vicent Martí committed
958 959
		ref->target.symbolic = NULL;
	}
960 961

	/* Look up on the packfile */
962
	packfile_refs = ref->owner->references.packfile;
963 964
	pos = git_strmap_lookup_index(packfile_refs, ref->name);
	if (!git_strmap_valid_index(packfile_refs, pos)) {
965
		giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref->name);
966
		return GIT_ENOTFOUND;
967
	}
968

969
	pack_ref = git_strmap_value_at(packfile_refs, pos);
970

971 972 973 974
	ref->flags = GIT_REF_OID | GIT_REF_PACKED;
	ref->mtime = ref->owner->references.packfile_time;
	git_oid_cpy(&ref->target.oid, &pack_ref->oid);

975
	return 0;
976
}
977

978
static int reference_lookup(git_reference *ref)
979
{
980
	int result;
981

982 983 984
	result = loose_lookup(ref);
	if (result == 0)
		return 0;
985

986 987
	/* 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 */
988
	if (result == GIT_ENOTFOUND) {
989 990 991 992 993
		giterr_clear();
		result = packed_lookup(ref);
		if (result == 0)
			return 0;
	}
994

995
	/* unexpected error; free the reference */
996
	git_reference_free(ref);
997
	return result;
998 999
}

1000 1001 1002 1003
/*
 * Delete a reference.
 * This is an internal method; the reference is removed
 * from disk or the packfile, but the pointer is not freed
Vicent Marti committed
1004
 */
1005
static int reference_delete(git_reference *ref)
Vicent Marti committed
1006
{
1007
	int result;
1008

Vicent Marti committed
1009 1010
	assert(ref);

1011 1012 1013 1014
	/* If the reference is packed, this is an expensive operation.
	 * We need to reload the packfile, remove the reference from the
	 * packing list, and repack */
	if (ref->flags & GIT_REF_PACKED) {
1015
		git_strmap *packfile_refs;
1016
		struct packref *packref;
1017 1018
		khiter_t pos;

1019
		/* load the existing packfile */
1020
		if (packed_load(ref->owner) < 0)
1021
			return -1;
Vicent Marti committed
1022

1023
		packfile_refs = ref->owner->references.packfile;
1024 1025
		pos = git_strmap_lookup_index(packfile_refs, ref->name);
		if (!git_strmap_valid_index(packfile_refs, pos)) {
1026
			giterr_set(GITERR_REFERENCE,
1027 1028 1029
				"Reference %s stopped existing in the packfile", ref->name);
			return -1;
		}
Vicent Marti committed
1030

1031 1032
		packref = git_strmap_value_at(packfile_refs, pos);
		git_strmap_delete_at(packfile_refs, pos);
1033

1034
		git__free(packref);
1035
		if (packed_write(ref->owner) < 0)
1036
			return -1;
Vicent Marti committed
1037

1038 1039 1040 1041
	/* If the reference is loose, we can just remove the reference
	 * from the filesystem */
	} else {
		git_reference *ref_in_pack;
1042
		git_buf full_path = GIT_BUF_INIT;
1043

1044
		if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0)
1045
			return -1;
1046

1047
		result = p_unlink(full_path.ptr);
1048
		git_buf_free(&full_path); /* done with path at this point */
1049 1050

		if (result < 0) {
1051
			giterr_set(GITERR_OS, "Failed to unlink '%s'", full_path.ptr);
1052 1053
			return -1;
		}
1054

1055 1056
		/* When deleting a loose reference, we have to ensure that an older
		 * packed version of it doesn't exist */
1057
		if (git_reference_lookup(&ref_in_pack, ref->owner, ref->name) == 0) {
1058
			assert((ref_in_pack->flags & GIT_REF_PACKED) != 0);
1059
			return git_reference_delete(ref_in_pack);
1060
		}
1061 1062

		giterr_clear();
1063 1064
	}

1065
	return 0;
1066 1067
}

1068
int git_reference_delete(git_reference *ref)
1069
{
1070
	int result = reference_delete(ref);
1071
	git_reference_free(ref);
1072
	return result;
1073 1074
}

1075
int git_reference_lookup(git_reference **ref_out,
1076
	git_repository *repo, const char *name)
1077
{
1078 1079 1080
	return git_reference_lookup_resolved(ref_out, repo, name, 0);
}

1081
int git_reference_name_to_id(
1082 1083 1084 1085 1086 1087 1088 1089
	git_oid *out, git_repository *repo, const char *name)
{
	int error;
	git_reference *ref;

	if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0)
		return error;

1090
	git_oid_cpy(out, git_reference_target(ref));
1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102
	git_reference_free(ref);
	return 0;
}

int git_reference_lookup_resolved(
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	int max_nesting)
{
	git_reference *scan;
	int result, nesting;
1103 1104

	assert(ref_out && repo && name);
1105

1106
	*ref_out = NULL;
1107

1108 1109 1110 1111
	if (max_nesting > MAX_NESTING_LEVEL)
		max_nesting = MAX_NESTING_LEVEL;
	else if (max_nesting < 0)
		max_nesting = DEFAULT_NESTING_LEVEL;
1112

1113 1114
	scan = git__calloc(1, sizeof(git_reference));
	GITERR_CHECK_ALLOC(scan);
1115

1116 1117
	scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char));
	GITERR_CHECK_ALLOC(scan->name);
1118

1119
	if ((result = git_reference__normalize_name_lax(
1120 1121 1122 1123 1124
		scan->name,
		GIT_REFNAME_MAX,
		name)) < 0) {
			git_reference_free(scan);
			return result;
1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154
	}

	scan->target.symbolic = git__strdup(scan->name);
	GITERR_CHECK_ALLOC(scan->target.symbolic);

	scan->owner = repo;
	scan->flags = GIT_REF_SYMBOLIC;

	for (nesting = max_nesting;
		 nesting >= 0 && (scan->flags & GIT_REF_SYMBOLIC) != 0;
		 nesting--)
	{
		if (nesting != max_nesting)
			strncpy(scan->name, scan->target.symbolic, GIT_REFNAME_MAX);

		scan->mtime = 0;

		if ((result = reference_lookup(scan)) < 0)
			return result; /* lookup git_reference_free on scan already */
	}

	if ((scan->flags & GIT_REF_OID) == 0 && max_nesting != 0) {
		giterr_set(GITERR_REFERENCE,
			"Cannot resolve reference (>%u levels deep)", max_nesting);
		git_reference_free(scan);
		return -1;
	}

	*ref_out = scan;
	return 0;
1155 1156
}

1157 1158 1159
/**
 * Getters
 */
1160
git_ref_t git_reference_type(const git_reference *ref)
Vicent Marti committed
1161 1162
{
	assert(ref);
1163 1164 1165 1166 1167 1168 1169 1170

	if (ref->flags & GIT_REF_OID)
		return GIT_REF_OID;

	if (ref->flags & GIT_REF_SYMBOLIC)
		return GIT_REF_SYMBOLIC;

	return GIT_REF_INVALID;
Vicent Marti committed
1171 1172
}

1173
int git_reference_is_packed(git_reference *ref)
Vicent Marti committed
1174 1175
{
	assert(ref);
1176
	return !!(ref->flags & GIT_REF_PACKED);
Vicent Marti committed
1177 1178
}

1179
const char *git_reference_name(const git_reference *ref)
Vicent Marti committed
1180 1181
{
	assert(ref);
1182
	return ref->name;
Vicent Marti committed
1183 1184
}

1185
git_repository *git_reference_owner(const git_reference *ref)
1186
{
1187 1188
	assert(ref);
	return ref->owner;
1189 1190
}

1191
const git_oid *git_reference_target(const git_reference *ref)
Vicent Marti committed
1192 1193 1194
{
	assert(ref);

1195
	if ((ref->flags & GIT_REF_OID) == 0)
Vicent Marti committed
1196 1197
		return NULL;

1198
	return &ref->target.oid;
Vicent Marti committed
1199 1200
}

1201
const char *git_reference_symbolic_target(const git_reference *ref)
1202
{
1203
	assert(ref);
1204

1205
	if ((ref->flags & GIT_REF_SYMBOLIC) == 0)
1206 1207
		return NULL;

1208
	return ref->target.symbolic;
1209 1210
}

1211
int git_reference_symbolic_create(
1212 1213 1214 1215
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	const char *target,
1216
	int force)
1217 1218
{
	char normalized[GIT_REFNAME_MAX];
1219
	git_reference *ref = NULL;
1220
	int error;
1221

1222
	if ((error = git_reference__normalize_name_lax(
1223 1224
		normalized,
		sizeof(normalized),
1225 1226
		name)) < 0)
			return error;
1227

1228 1229
	if ((error = reference_can_write(repo, normalized, NULL, force)) < 0)
		return error;
1230

1231
	if (reference_alloc(&ref, repo, normalized) < 0)
1232
		return -1;
1233

1234 1235 1236 1237
	ref->flags |= GIT_REF_SYMBOLIC;

	/* set the target; this will normalize the name automatically
	 * and write the reference on disk */
1238
	if (git_reference_symbolic_set_target(ref, target) < 0) {
1239 1240 1241
		git_reference_free(ref);
		return -1;
	}
1242 1243 1244 1245
	if (ref_out == NULL) {
		git_reference_free(ref);
	} else {
		*ref_out = ref;
1246 1247
	}

1248
	return 0;
1249 1250
}

1251
int git_reference_create(
1252 1253 1254 1255
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	const git_oid *id,
1256
	int force)
1257
{
1258
	int error;
1259 1260
	git_reference *ref = NULL;
	char normalized[GIT_REFNAME_MAX];
1261

1262
	if ((error = git_reference__normalize_name_lax(
1263 1264
		normalized,
		sizeof(normalized),
1265 1266
		name)) < 0)
			return error;
1267

1268 1269
	if ((error = reference_can_write(repo, normalized, NULL, force)) < 0)
		return error;
1270

1271
	if (reference_alloc(&ref, repo, name) < 0)
1272
		return -1;
1273 1274

	ref->flags |= GIT_REF_OID;
1275 1276

	/* set the oid; this will write the reference on disk */
1277
	if (git_reference_set_target(ref, id) < 0) {
1278 1279 1280
		git_reference_free(ref);
		return -1;
	}
1281

1282 1283 1284 1285
	if (ref_out == NULL) {
		git_reference_free(ref);
	} else {
		*ref_out = ref;
1286 1287
	}

1288
	return 0;
1289
}
Vicent Marti committed
1290 1291 1292
/*
 * Change the OID target of a reference.
 *
1293 1294
 * For both loose and packed references, just change
 * the oid in memory and (over)write the file in disk.
Vicent Marti committed
1295
 *
1296 1297
 * We do not repack packed references because of performance
 * reasons.
Vicent Marti committed
1298
 */
1299
int git_reference_set_target(git_reference *ref, const git_oid *id)
Vicent Marti committed
1300
{
1301
	git_odb *odb = NULL;
Vicent Marti committed
1302

1303
	if ((ref->flags & GIT_REF_OID) == 0) {
1304
		giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");
1305 1306
		return -1;
	}
Vicent Marti committed
1307

1308 1309
	assert(ref->owner);

1310
	if (git_repository_odb__weakptr(&odb, ref->owner) < 0)
1311
		return -1;
1312

1313 1314
	/* Don't let the user create references to OIDs that
	 * don't exist in the ODB */
1315
	if (!git_odb_exists(odb, id)) {
1316
		giterr_set(GITERR_REFERENCE,
1317 1318 1319
			"Target OID for the reference doesn't exist on the repository");
		return -1;
	}
Vicent Marti committed
1320

1321 1322
	/* Update the OID value on `ref` */
	git_oid_cpy(&ref->target.oid, id);
Vicent Marti committed
1323

1324
	/* Write back to disk */
1325
	return loose_write(ref);
1326 1327
}

Vicent Marti committed
1328 1329 1330 1331 1332 1333 1334
/*
 * Change the target of a symbolic reference.
 *
 * This is easy because symrefs cannot be inside
 * a pack. We just change the target in memory
 * and overwrite the file on disk.
 */
1335
int git_reference_symbolic_set_target(git_reference *ref, const char *target)
Vicent Marti committed
1336
{
1337
	int error;
1338
	char normalized[GIT_REFNAME_MAX];
Vicent Marti committed
1339

1340
	if ((ref->flags & GIT_REF_SYMBOLIC) == 0) {
1341
		giterr_set(GITERR_REFERENCE,
1342 1343 1344
			"Cannot set symbolic target on a direct reference");
		return -1;
	}
Vicent Marti committed
1345

1346
	if ((error = git_reference__normalize_name_lax(
1347 1348
		normalized,
		sizeof(normalized),
1349 1350
		target)) < 0)
			return error;
Vicent Marti committed
1351

1352 1353
	git__free(ref->target.symbolic);
	ref->target.symbolic = git__strdup(normalized);
1354
	GITERR_CHECK_ALLOC(ref->target.symbolic);
Vicent Marti committed
1355

1356
	return loose_write(ref);
Vicent Marti committed
1357 1358
}

1359
int git_reference_rename(git_reference *ref, const char *new_name, int force)
1360
{
1361
	int result;
1362
	unsigned int normalization_flags;
1363
	git_buf aux_path = GIT_BUF_INIT;
1364
	char normalized[GIT_REFNAME_MAX];
1365
	bool should_head_be_updated = false;
1366

1367 1368 1369 1370
	normalization_flags = ref->flags & GIT_REF_SYMBOLIC ?
		GIT_REF_FORMAT_ALLOW_ONELEVEL
		: GIT_REF_FORMAT_NORMAL;

1371
	if ((result = git_reference_normalize_name(
1372 1373 1374
		normalized,
		sizeof(normalized),
		new_name,
1375 1376
		normalization_flags)) < 0)
			return result;
1377

1378 1379
	if ((result = reference_can_write(ref->owner, normalized, ref->name, force)) < 0)
		return result;
1380

1381
	/* Initialize path now so we won't get an allocation failure once
1382 1383
	 * we actually start removing things. */
	if (git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name) < 0)
1384
		return -1;
1385

1386
	/*
1387 1388
	 * Check if we have to update HEAD.
	 */
1389
	if ((should_head_be_updated = git_branch_is_head(ref)) < 0)
1390 1391 1392
		goto cleanup;

	/*
1393
	 * Now delete the old ref and remove an possibly existing directory
1394 1395 1396
	 * named `new_name`. Note that using the internal `reference_delete`
	 * method deletes the ref from disk but doesn't free the pointer, so
	 * we can still access the ref's attributes for creating the new one
1397
	 */
1398
	if (reference_delete(ref) < 0)
1399
		goto cleanup;
1400

1401 1402 1403
	/*
	 * Finally we can create the new reference.
	 */
1404
	if (ref->flags & GIT_REF_SYMBOLIC) {
1405
		result = git_reference_symbolic_create(
1406
			NULL, ref->owner, new_name, ref->target.symbolic, force);
1407
	} else {
1408
		result = git_reference_create(
1409
			NULL, ref->owner, new_name, &ref->target.oid, force);
1410
	}
1411

1412
	if (result < 0)
1413
		goto rollback;
1414

1415
	/*
1416
	 * Update HEAD it was poiting to the reference being renamed.
1417
	 */
1418 1419
	if (should_head_be_updated && 
		git_repository_set_head(ref->owner, new_name) < 0) {
1420
			giterr_set(GITERR_REFERENCE,
1421
				"Failed to update HEAD after renaming reference");
1422 1423 1424 1425
			goto cleanup;
	}

	/*
1426
	 * Rename the reflog file, if it exists.
1427
	 */
1428
	if ((git_reference_has_log(ref)) && (git_reflog_rename(ref, new_name) < 0))
1429 1430
		goto cleanup;

1431
	/*
1432 1433 1434 1435 1436 1437 1438
	 * Change the name of the reference given by the user.
	 */
	git__free(ref->name);
	ref->name = git__strdup(new_name);

	/* The reference is no longer packed */
	ref->flags &= ~GIT_REF_PACKED;
1439

1440 1441 1442
	git_buf_free(&aux_path);
	return 0;

1443
cleanup:
1444
	git_buf_free(&aux_path);
1445
	return -1;
1446

1447 1448
rollback:
	/*
1449
	 * Try to create the old reference again, ignore failures
1450
	 */
1451
	if (ref->flags & GIT_REF_SYMBOLIC)
1452
		git_reference_symbolic_create(
1453
			NULL, ref->owner, ref->name, ref->target.symbolic, 0);
1454
	else
1455
		git_reference_create(
1456
			NULL, ref->owner, ref->name, &ref->target.oid, 0);
1457

1458 1459 1460
	/* The reference is no longer packed */
	ref->flags &= ~GIT_REF_PACKED;

1461
	git_buf_free(&aux_path);
1462
	return -1;
1463
}
1464

1465
int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
Vicent Marti committed
1466
{
1467
	if (ref->flags & GIT_REF_OID)
1468
		return git_reference_lookup(ref_out, ref->owner, ref->name);
1469 1470
	else
		return git_reference_lookup_resolved(ref_out, ref->owner, ref->target.symbolic, -1);
1471 1472
}

1473
int git_reference_packall(git_repository *repo)
Vicent Marti committed
1474
{
1475 1476 1477
	if (packed_load(repo) < 0 || /* load the existing packfile */
		packed_loadloose(repo) < 0 || /* add all the loose refs */
		packed_write(repo) < 0) /* write back to disk */
1478
		return -1;
Vicent Marti committed
1479

1480
	return 0;
Vicent Marti committed
1481 1482
}

1483 1484 1485
int git_reference_foreach(
	git_repository *repo,
	unsigned int list_flags,
1486
	git_reference_foreach_cb callback,
1487
	void *payload)
1488
{
1489
	int result;
1490
	struct dirent_list_data data;
1491
	git_buf refs_path = GIT_BUF_INIT;
1492

1493
	/* list all the packed references first */
1494 1495
	if (list_flags & GIT_REF_PACKED) {
		const char *ref_name;
1496
		void *ref;
1497
		GIT_UNUSED(ref);
1498

1499
		if (packed_load(repo) < 0)
1500
			return -1;
1501

1502
		git_strmap_foreach(repo->references.packfile, ref_name, ref, {
1503 1504
			if (callback(ref_name, payload))
				return GIT_EUSER;
1505
		});
1506 1507
	}

1508 1509
	/* now list the loose references, trying not to
	 * duplicate the ref names already in the packed-refs file */
1510 1511 1512 1513 1514 1515

	data.repo_path_len = strlen(repo->path_repository);
	data.list_flags = list_flags;
	data.repo = repo;
	data.callback = callback;
	data.callback_payload = payload;
1516
	data.callback_error = 0;
1517

1518
	if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0)
1519
		return -1;
1520

1521
	result = git_path_direach(&refs_path, _dirent_loose_listall, &data);
1522

1523 1524
	git_buf_free(&refs_path);

1525
	return data.callback_error ? GIT_EUSER : result;
1526 1527
}

1528
static int cb__reflist_add(const char *ref, void *data)
1529 1530 1531 1532
{
	return git_vector_insert((git_vector *)data, git__strdup(ref));
}

1533
int git_reference_list(
1534 1535
	git_strarray *array,
	git_repository *repo,
1536
	unsigned int list_flags)
1537 1538 1539 1540 1541 1542 1543 1544
{
	git_vector ref_list;

	assert(array && repo);

	array->strings = NULL;
	array->count = 0;

1545
	if (git_vector_init(&ref_list, 8, NULL) < 0)
1546
		return -1;
1547

1548
	if (git_reference_foreach(
1549
			repo, list_flags, &cb__reflist_add, (void *)&ref_list) < 0) {
1550
		git_vector_free(&ref_list);
1551
		return -1;
1552 1553
	}

1554 1555
	array->strings = (char **)ref_list.contents;
	array->count = ref_list.length;
1556
	return 0;
1557
}
Vicent Marti committed
1558

1559
int git_reference_reload(git_reference *ref)
1560
{
1561
	return reference_lookup(ref);
1562
}
1563

1564 1565 1566 1567
void git_repository__refcache_free(git_refcache *refs)
{
	assert(refs);

Vicent Marti committed
1568
	if (refs->packfile) {
1569 1570
		struct packref *reference;

1571
		git_strmap_foreach_value(refs->packfile, reference, {
1572 1573
			git__free(reference);
		});
Vicent Marti committed
1574

1575
		git_strmap_free(refs->packfile);
Vicent Marti committed
1576
	}
1577
}
1578

1579
static int is_valid_ref_char(char ch)
1580
{
1581
	if ((unsigned) ch <= ' ')
1582
		return 0;
1583 1584 1585 1586 1587 1588 1589 1590

	switch (ch) {
	case '~':
	case '^':
	case ':':
	case '\\':
	case '?':
	case '[':
1591
	case '*':
1592
		return 0;
1593
	default:
1594
		return 1;
1595 1596 1597
	}
}

1598
static int ensure_segment_validity(const char *name)
1599
{
1600 1601
	const char *current = name;
	char prev = '\0';
1602
	int lock_len = strlen(GIT_FILELOCK_EXTENSION);
1603

1604 1605
	if (*current == '.')
		return -1; /* Refname starts with "." */
1606

1607 1608 1609
	for (current = name; ; current++) {
		if (*current == '\0' || *current == '/')
			break;
1610

1611 1612
		if (!is_valid_ref_char(*current))
			return -1; /* Illegal character in refname */
1613

1614 1615
		if (prev == '.' && *current == '.')
			return -1; /* Refname contains ".." */
1616

1617 1618
		if (prev == '@' && *current == '{')
			return -1; /* Refname contains "@{" */
1619

1620 1621
		prev = *current;
	}
1622

1623 1624
	/* A refname component can not end with ".lock" */
	if (current - name >= lock_len &&
1625
		!memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len))
1626 1627
			return -1;

1628
	return (int)(current - name);
1629
}
1630

1631
static bool is_all_caps_and_underscore(const char *name, size_t len)
1632
{
1633
	size_t i;
1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650
	char c;

	assert(name && len > 0);

	for (i = 0; i < len; i++)
	{
		c = name[i];
		if ((c < 'A' || c > 'Z') && c != '_')
			return false;
	}

	if (*name == '_' || name[len - 1] == '_')
		return false;

	return true;
}

1651 1652 1653 1654 1655 1656 1657 1658
int git_reference__normalize_name(
	git_buf *buf,
	const char *name,
	unsigned int flags)
{
	// Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100

	char *current;
1659
	int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
1660 1661 1662
	unsigned int process_flags;
	bool normalize = (buf != NULL);
	assert(name);
1663

1664
	process_flags = flags;
1665 1666
	current = (char *)name;

1667 1668
	if (normalize)
		git_buf_clear(buf);
1669 1670 1671 1672

	while (true) {
		segment_len = ensure_segment_validity(current);
		if (segment_len < 0) {
1673
			if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) &&
1674 1675 1676
					current[0] == '*' &&
					(current[1] == '\0' || current[1] == '/')) {
				/* Accept one wildcard as a full refname component. */
1677
				process_flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN;
1678 1679 1680 1681
				segment_len = 1;
			} else
				goto cleanup;
		}
1682

1683
		if (segment_len > 0) {
1684
			if (normalize) {
1685
				size_t cur_len = git_buf_len(buf);
1686

1687
				git_buf_joinpath(buf, git_buf_cstr(buf), current);
1688
				git_buf_truncate(buf,
1689
					cur_len + segment_len + (segments_count ? 1 : 0));
1690

1691 1692
				if (git_buf_oom(buf)) {
					error = -1;
1693
					goto cleanup;
1694
				}
1695
			}
1696

1697
			segments_count++;
1698
		}
1699

1700 1701
		/* No empty segment is allowed when not normalizing */
		if (segment_len == 0 && !normalize)
1702
			goto cleanup;
1703
		
1704 1705
		if (current[segment_len] == '\0')
			break;
1706

1707
		current += segment_len + 1;
1708
	}
1709

1710
	/* A refname can not be empty */
1711
	if (segment_len == 0 && segments_count == 0)
1712 1713 1714 1715 1716 1717 1718 1719 1720 1721
		goto cleanup;

	/* A refname can not end with "." */
	if (current[segment_len - 1] == '.')
		goto cleanup;

	/* A refname can not end with "/" */
	if (current[segment_len - 1] == '/')
		goto cleanup;

1722 1723 1724 1725
	if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL))
		goto cleanup;

	if ((segments_count == 1 ) &&
1726
		!(is_all_caps_and_underscore(name, (size_t)segment_len) ||
1727 1728 1729 1730 1731 1732
			((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))
			goto cleanup;

	if ((segments_count > 1)
		&& (is_all_caps_and_underscore(name, strchr(name, '/') - name)))
			goto cleanup;
1733

1734
	error = 0;
1735

1736
cleanup:
1737
	if (error == GIT_EINVALIDSPEC)
1738 1739 1740
		giterr_set(
			GITERR_REFERENCE,
			"The given reference name '%s' is not valid", name);
1741

1742 1743 1744
	if (error && normalize)
		git_buf_free(buf);

1745 1746
	return error;
}
1747

1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761
int git_reference_normalize_name(
	char *buffer_out,
	size_t buffer_size,
	const char *name,
	unsigned int flags)
{
	git_buf buf = GIT_BUF_INIT;
	int error;

	if ((error = git_reference__normalize_name(&buf, name, flags)) < 0)
		goto cleanup;

	if (git_buf_len(&buf) > buffer_size - 1) {
		giterr_set(
1762
		GITERR_REFERENCE,
1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774
		"The provided buffer is too short to hold the normalization of '%s'", name);
		error = GIT_EBUFS;
		goto cleanup;
	}

	git_buf_copy_cstr(buffer_out, buffer_size, &buf);

	error = 0;

cleanup:
	git_buf_free(&buf);
	return error;
1775
}
1776

1777
int git_reference__normalize_name_lax(
1778 1779 1780
	char *buffer_out,
	size_t out_size,
	const char *name)
1781
{
1782 1783 1784 1785 1786
	return git_reference_normalize_name(
		buffer_out,
		out_size,
		name,
		GIT_REF_FORMAT_ALLOW_ONELEVEL);
1787
}
1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803
#define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC)

int git_reference_cmp(git_reference *ref1, git_reference *ref2)
{
	assert(ref1 && ref2);

	/* let's put symbolic refs before OIDs */
	if ((ref1->flags & GIT_REF_TYPEMASK) != (ref2->flags & GIT_REF_TYPEMASK))
		return (ref1->flags & GIT_REF_SYMBOLIC) ? -1 : 1;

	if (ref1->flags & GIT_REF_SYMBOLIC)
		return strcmp(ref1->target.symbolic, ref2->target.symbolic);

	return git_oid_cmp(&ref1->target.oid, &ref2->target.oid);
}

nulltoken committed
1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815
/* Update the reference named `ref_name` so it points to `oid` */
int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name)
{
	git_reference *ref;
	int res;

	res = git_reference_lookup(&ref, repo, ref_name);

	/* If we haven't found the reference at all, we assume we need to create
	 * a new reference and that's it */
	if (res == GIT_ENOTFOUND) {
		giterr_clear();
1816
		return git_reference_create(NULL, repo, ref_name, oid, 1);
nulltoken committed
1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828
	}

	if (res < 0)
		return -1;

	/* If we have found a reference, but it's symbolic, we need to update
	 * the direct reference it points to */
	if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
		git_reference *aux;
		const char *sym_target;

		/* The target pointed at by this reference */
1829
		sym_target = git_reference_symbolic_target(ref);
nulltoken committed
1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840

		/* resolve the reference to the target it points to */
		res = git_reference_resolve(&aux, ref);

		/*
		 * if the symbolic reference pointed to an inexisting ref,
		 * this is means we're creating a new branch, for example.
		 * We need to create a new direct reference with that name
		 */
		if (res == GIT_ENOTFOUND) {
			giterr_clear();
1841
			res = git_reference_create(NULL, repo, sym_target, oid, 1);
nulltoken committed
1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858
			git_reference_free(ref);
			return res;
		}

		/* free the original symbolic reference now; not before because
		 * we're using the `sym_target` pointer */
		git_reference_free(ref);

		if (res < 0)
			return -1;

		/* store the newly found direct reference in its place */
		ref = aux;
	}

	/* ref is made to point to `oid`: ref is either the original reference,
	 * or the target of the symbolic reference we've looked up */
1859
	res = git_reference_set_target(ref, oid);
nulltoken committed
1860 1861 1862
	git_reference_free(ref);
	return res;
}
1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899

struct glob_cb_data {
	const char *glob;
	int (*callback)(const char *, void *);
	void *payload;
};

static int fromglob_cb(const char *reference_name, void *payload)
{
	struct glob_cb_data *data = (struct glob_cb_data *)payload;

	if (!p_fnmatch(data->glob, reference_name, 0))
		return data->callback(reference_name, data->payload);

	return 0;
}

int git_reference_foreach_glob(
	git_repository *repo,
	const char *glob,
	unsigned int list_flags,
	int (*callback)(
		const char *reference_name,
		void *payload),
	void *payload)
{
	struct glob_cb_data data;

	assert(repo && glob && callback);

	data.glob = glob;
	data.callback = callback;
	data.payload = payload;

	return git_reference_foreach(
			repo, list_flags, fromglob_cb, &data);
}
1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916

int git_reference_has_log(
	git_reference *ref)
{
	git_buf path = GIT_BUF_INIT;
	int result;

	assert(ref);

	if (git_buf_join_n(&path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0)
		return -1;

	result = git_path_isfile(git_buf_cstr(&path));
	git_buf_free(&path);

	return result;
}
1917

1918 1919 1920 1921 1922
int git_reference__is_branch(const char *ref_name)
{
	return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0;
}

1923 1924 1925
int git_reference_is_branch(git_reference *ref)
{
	assert(ref);
1926
	return git_reference__is_branch(ref->name);
1927
}
1928 1929 1930 1931 1932 1933

int git_reference_is_remote(git_reference *ref)
{
	assert(ref);
	return git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR) == 0;
}
1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946

static int peel_error(int error, git_reference *ref, const char* msg)
{
	giterr_set(
		GITERR_INVALID,
		"The reference '%s' cannot be peeled - %s", git_reference_name(ref), msg);
	return error;
}

static int reference_target(git_object **object, git_reference *ref)
{
	const git_oid *oid;

1947
	oid = git_reference_target(ref);
1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969

	return git_object_lookup(object, git_reference_owner(ref), oid, GIT_OBJ_ANY);
}

int git_reference_peel(
		git_object **peeled,
		git_reference *ref,
		git_otype target_type)
{
	git_reference *resolved = NULL;
	git_object *target = NULL;
	int error;

	assert(ref);

	if ((error = git_reference_resolve(&resolved, ref)) < 0)
		return peel_error(error, ref, "Cannot resolve reference");

	if ((error = reference_target(&target, resolved)) < 0) {
		peel_error(error, ref, "Cannot retrieve reference target");
		goto cleanup;
	}
1970

1971 1972
	if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG)
		error = git_object__dup(peeled, target);
1973
	else
1974 1975 1976 1977 1978 1979 1980
		error = git_object_peel(peeled, target, target_type);

cleanup:
	git_object_free(target);
	git_reference_free(resolved);
	return error;
}
1981

1982 1983 1984 1985
int git_reference__is_valid_name(
	const char *refname,
	unsigned int flags)
{
nulltoken committed
1986 1987 1988
	int error;

	error = git_reference__normalize_name(NULL, refname, flags) == 0;
1989
	giterr_clear();
nulltoken committed
1990 1991

	return error;
1992 1993
}

1994 1995 1996
int git_reference_is_valid_name(
	const char *refname)
{
1997
	return git_reference__is_valid_name(
1998
		refname,
1999
		GIT_REF_FORMAT_ALLOW_ONELEVEL);
2000
}