refs.c 45.3 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 342
	buffer = buffer + GIT_OID_HEXSZ;
	if (*buffer == '\r')
		buffer++;

	if (*buffer != '\n')
343
		goto corrupt;
344 345

	*buffer_out = buffer + 1;
346 347 348 349 350
	return 0;

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

Vicent Marti committed
353
static int packed_parse_oid(
354
		struct packref **ref_out,
355 356
		const char **buffer_out,
		const char *buffer_end)
357
{
358
	struct packref *ref = NULL;
359 360 361 362

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

363
	size_t refname_len;
364
	git_oid id;
365

366
	refname_begin = (buffer + GIT_OID_HEXSZ + 1);
367 368
	if (refname_begin >= buffer_end || refname_begin[-1] != ' ')
		goto corrupt;
369

370
	/* Is this a valid object id? */
371 372
	if (git_oid_fromstr(&id, buffer) < 0)
		goto corrupt;
373

374
	refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
375
	if (refname_end == NULL)
376
		refname_end = buffer_end;
377

378 379
	if (refname_end[-1] == '\r')
		refname_end--;
380

381
	refname_len = refname_end - refname_begin;
382

383
	ref = git__malloc(sizeof(struct packref) + refname_len + 1);
384
	GITERR_CHECK_ALLOC(ref);
385

386 387
	memcpy(ref->name, refname_begin, refname_len);
	ref->name[refname_len] = 0;
388

389
	git_oid_cpy(&ref->oid, &id);
390 391

	ref->flags = 0;
392

393
	*ref_out = ref;
394 395
	*buffer_out = refname_end + 1;

396
	return 0;
397

398
corrupt:
399
	git__free(ref);
400 401
	giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");
	return -1;
402 403
}

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

411 412
	/* First we make sure we have allocated the hash table */
	if (ref_cache->packfile == NULL) {
413
		ref_cache->packfile = git_strmap_alloc();
414
		GITERR_CHECK_ALLOC(ref_cache->packfile);
415 416
	}

417
	result = reference_read(&packfile, &ref_cache->packfile_time,
418
		repo->path_repository, GIT_PACKEDREFS_FILE, &updated);
Vicent Marti committed
419

420 421 422 423 424 425 426
	/*
	 * 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.
	 */
427
	if (result == GIT_ENOTFOUND) {
428
		git_strmap_clear(ref_cache->packfile);
429
		return 0;
430
	}
431

432 433 434 435 436 437
	if (result < 0)
		return -1;

	if (!updated)
		return 0;

438 439 440 441
	/*
	 * At this point, we want to refresh the packed refs. We already
	 * have the contents in our buffer.
	 */
442
	git_strmap_clear(ref_cache->packfile);
443

444 445
	buffer_start = (const char *)packfile.ptr;
	buffer_end = (const char *)(buffer_start) + packfile.size;
446

447 448
	while (buffer_start < buffer_end && buffer_start[0] == '#') {
		buffer_start = strchr(buffer_start, '\n');
449 450 451
		if (buffer_start == NULL)
			goto parse_failed;

452
		buffer_start++;
453
	}
454

455
	while (buffer_start < buffer_end) {
456
		int err;
457
		struct packref *ref = NULL;
458

459 460
		if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0)
			goto parse_failed;
461

462
		if (buffer_start[0] == '^') {
463 464
			if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0)
				goto parse_failed;
465
		}
466

467
		git_strmap_insert(ref_cache->packfile, ref->name, ref, err);
468 469
		if (err < 0)
			goto parse_failed;
470
	}
471

472
	git_buf_free(&packfile);
473
	return 0;
474

475
parse_failed:
476
	git_strmap_free(ref_cache->packfile);
477
	ref_cache->packfile = NULL;
478
	git_buf_free(&packfile);
479
	return -1;
480
}
481

482 483

struct dirent_list_data {
484
	git_repository *repo;
485 486
	size_t repo_path_len;
	unsigned int list_flags;
487 488 489

	int (*callback)(const char *, void *);
	void *callback_payload;
490
	int callback_error;
491 492
};

493
static int _dirent_loose_listall(void *_data, git_buf *full_path)
494 495
{
	struct dirent_list_data *data = (struct dirent_list_data *)_data;
496
	const char *file_path = full_path->ptr + data->repo_path_len;
497

498
	if (git_path_isdir(full_path->ptr) == true)
499
		return git_path_direach(full_path, _dirent_loose_listall, _data);
500

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

506 507
	if (data->list_flags != GIT_REF_LISTALL) {
		if ((data->list_flags & loose_guess_rtype(full_path)) == 0)
508
			return 0; /* we are filtering out this reference */
509
	}
510

511 512 513 514
	/* Locked references aren't returned */
	if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION))
		return 0;

515 516 517 518
	if (data->callback(file_path, data->callback_payload))
		data->callback_error = GIT_EUSER;

	return data->callback_error;
519 520
}

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

529
	if (git_path_isdir(full_path->ptr) == true)
530
		return git_path_direach(full_path, _dirent_loose_load, repository);
531

532
	file_path = full_path->ptr + strlen(repository->path_repository);
533

534 535
	if (loose_lookup_to_packfile(&ref, repository, file_path) < 0)
		return -1;
Vicent Marti committed
536

537
	git_strmap_insert2(
538 539
		repository->references.packfile, ref->name, ref, old_ref, err);
	if (err < 0) {
540 541
		git__free(ref);
		return -1;
Vicent Marti committed
542
	}
543

544 545
	git__free(old_ref);
	return 0;
546 547
}

Vicent Marti committed
548 549 550 551 552 553 554
/*
 * 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)
555
{
556
	git_buf refs_path = GIT_BUF_INIT;
557
	int result;
558

Vicent Marti committed
559 560
	/* the packfile must have been previously loaded! */
	assert(repository->references.packfile);
561

562 563
	if (git_buf_joinpath(&refs_path, repository->path_repository, GIT_REFS_DIR) < 0)
		return -1;
564

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

	return result;
574
}
575

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

Vicent Marti committed
583 584
	git_oid_fmt(oid, &ref->oid);
	oid[GIT_OID_HEXSZ] = 0;
585

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

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

608
	return 0;
609 610
}

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

623
	if (ref->flags & GIT_PACKREF_HAS_PEEL)
624
		return 0;
625

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

Vicent Marti committed
633
	/*
634
	 * Find the tagged object in the repository
Vicent Marti committed
635
	 */
636 637
	if (git_object_lookup(&object, repo, &ref->oid, GIT_OBJ_ANY) < 0)
		return -1;
638

Vicent Marti committed
639
	/*
640 641 642
	 * 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
643
	 */
644 645
	if (git_object_type(object) == GIT_OBJ_TAG) {
		git_tag *tag = (git_tag *)object;
646

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

		/*
		 * 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
659

660
	git_object_free(object);
661
	return 0;
662
}
663

Vicent Marti committed
664 665 666 667 668 669 670 671 672 673 674 675
/*
 * 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)
676
{
Vicent Marti committed
677
	unsigned int i;
678
	git_buf full_path = GIT_BUF_INIT;
679
	int failed = 0;
Vicent Marti committed
680 681

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

684
		if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0)
685 686
			continue;

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

690 691 692
		if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) {
			if (failed)
				continue;
Vicent Marti committed
693

694 695 696 697 698 699
			giterr_set(GITERR_REFERENCE,
				"Failed to remove loose reference '%s' after packing: %s",
				full_path.ptr, strerror(errno));

			failed = 1;
		}
Vicent Marti committed
700 701 702 703 704 705 706 707 708

		/*
		 * 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.
		 */
	}

709
	git_buf_free(&full_path);
710
	return failed ? -1 : 0;
711
}
712

Vicent Marti committed
713
static int packed_sort(const void *a, const void *b)
714
{
715 716
	const struct packref *ref_a = (const struct packref *)a;
	const struct packref *ref_b = (const struct packref *)b;
Vicent Marti committed
717 718

	return strcmp(ref_a->name, ref_b->name);
719 720
}

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

Vicent Marti committed
732
	assert(repo && repo->references.packfile);
733

734
	total_refs =
735
		(unsigned int)git_strmap_num_entries(repo->references.packfile);
736 737 738

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

Vicent Marti committed
740 741
	/* Load all the packfile into a vector */
	{
742
		struct packref *reference;
Vicent Marti committed
743

744
		/* cannot fail: vector already has the right size */
745
		git_strmap_foreach_value(repo->references.packfile, reference, {
746
			git_vector_insert(&packing_list, reference);
747
		});
748 749
	}

Vicent Marti committed
750 751
	/* sort the vector so the entries appear sorted on the packfile */
	git_vector_sort(&packing_list);
752

Vicent Marti committed
753
	/* Now we can open the file! */
754 755
	if (git_buf_joinpath(&pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE) < 0)
		goto cleanup_memory;
756

757 758
	if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0)
		goto cleanup_packfile;
759

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

Vicent Marti committed
766
	for (i = 0; i < packing_list.length; ++i) {
767
		struct packref *ref = (struct packref *)git_vector_get(&packing_list, i);
768

769 770
		if (packed_find_peel(repo, ref) < 0)
			goto cleanup_packfile;
771

772 773
		if (packed_write_ref(ref, &pack_file) < 0)
			goto cleanup_packfile;
Vicent Marti committed
774
	}
775

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

781 782 783 784
	/* 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;
785

786 787 788 789 790
	 {
		struct stat st;
		if (p_stat(pack_file_path.ptr, &st) == 0)
			repo->references.packfile_time = st.st_mtime;
	 }
791

Vicent Marti committed
792
	git_vector_free(&packing_list);
793
	git_buf_free(&pack_file_path);
794

795 796 797 798 799
	/* we're good now */
	return 0;

cleanup_packfile:
	git_filebuf_cleanup(&pack_file);
800

801 802 803 804 805
cleanup_memory:
	git_vector_free(&packing_list);
	git_buf_free(&pack_file_path);

	return -1;
Vicent Marti committed
806
}
807

808 809 810 811 812 813
struct reference_available_t {
	const char *new_ref;
	const char *old_ref;
	int available;
};

814 815
static int _reference_available_cb(const char *ref, void *data)
{
816
	struct reference_available_t *d;
817

818
	assert(ref && data);
819
	d = (struct reference_available_t *)data;
820

821
	if (!d->old_ref || strcmp(d->old_ref, ref)) {
822 823 824
		size_t reflen = strlen(ref);
		size_t newlen = strlen(d->new_ref);
		size_t cmplen = reflen < newlen ? reflen : newlen;
825
		const char *lead = reflen < newlen ? d->new_ref : ref;
826

827 828 829 830
		if (!strncmp(d->new_ref, ref, cmplen) && lead[cmplen] == '/') {
			d->available = 0;
			return -1;
		}
831 832
	}

833
	return 0;
834 835
}

836
static int reference_path_available(
837 838
	git_repository *repo,
	const char *ref,
839
	const char* old_ref)
840
{
841
	int error;
842
	struct reference_available_t data;
843

844 845 846
	data.new_ref = ref;
	data.old_ref = old_ref;
	data.available = 1;
847

848 849 850 851
	error = git_reference_foreach(
		repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&data);
	if (error < 0)
		return error;
852 853 854

	if (!data.available) {
		giterr_set(GITERR_REFERENCE,
855
			"The path to reference '%s' collides with an existing one", ref);
856
		return -1;
857
	}
858

859
	return 0;
860 861
}

862
static int reference_exists(int *exists, git_repository *repo, const char *ref_name)
863
{
864
	git_buf ref_path = GIT_BUF_INIT;
865

866 867
	if (packed_load(repo) < 0)
		return -1;
868

869 870
	if (git_buf_joinpath(&ref_path, repo->path_repository, ref_name) < 0)
		return -1;
871

872
	if (git_path_isfile(ref_path.ptr) == true ||
873
		git_strmap_exists(repo->references.packfile, ref_path.ptr))
874
	{
875 876 877 878
		*exists = 1;
	} else {
		*exists = 0;
	}
879

880
	git_buf_free(&ref_path);
881 882
	return 0;
}
883

884 885 886 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
/*
 * 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,
916
				"A reference with that name (%s) already exists", refname);
917
			return GIT_EEXISTS;
918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933
		}
	}

	/* 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;
934
}
935

936

937 938 939
static int packed_lookup(git_reference *ref)
{
	struct packref *pack_ref = NULL;
940
	git_strmap *packfile_refs;
941
	khiter_t pos;
942

943 944
	if (packed_load(ref->owner) < 0)
		return -1;
945

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

Vicent Martí committed
952
	if (ref->flags & GIT_REF_SYMBOLIC) {
953
		git__free(ref->target.symbolic);
Vicent Martí committed
954 955
		ref->target.symbolic = NULL;
	}
956 957

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

965
	pack_ref = git_strmap_value_at(packfile_refs, pos);
966

967 968 969 970
	ref->flags = GIT_REF_OID | GIT_REF_PACKED;
	ref->mtime = ref->owner->references.packfile_time;
	git_oid_cpy(&ref->target.oid, &pack_ref->oid);

971
	return 0;
972
}
973

974
static int reference_lookup(git_reference *ref)
975
{
976
	int result;
977

978 979 980
	result = loose_lookup(ref);
	if (result == 0)
		return 0;
981

982 983
	/* 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 */
984
	if (result == GIT_ENOTFOUND) {
985 986 987 988 989
		giterr_clear();
		result = packed_lookup(ref);
		if (result == 0)
			return 0;
	}
990

991
	/* unexpected error; free the reference */
992
	git_reference_free(ref);
993
	return result;
994 995
}

996 997 998 999
/*
 * 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
1000
 */
1001
static int reference_delete(git_reference *ref)
Vicent Marti committed
1002
{
1003
	int result;
1004

Vicent Marti committed
1005 1006
	assert(ref);

1007 1008 1009 1010
	/* 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) {
1011
		git_strmap *packfile_refs;
1012
		struct packref *packref;
1013 1014
		khiter_t pos;

1015
		/* load the existing packfile */
1016
		if (packed_load(ref->owner) < 0)
1017
			return -1;
Vicent Marti committed
1018

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

1027 1028
		packref = git_strmap_value_at(packfile_refs, pos);
		git_strmap_delete_at(packfile_refs, pos);
1029

1030
		git__free(packref);
1031
		if (packed_write(ref->owner) < 0)
1032
			return -1;
Vicent Marti committed
1033

1034 1035 1036 1037
	/* If the reference is loose, we can just remove the reference
	 * from the filesystem */
	} else {
		git_reference *ref_in_pack;
1038
		git_buf full_path = GIT_BUF_INIT;
1039

1040
		if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0)
1041
			return -1;
1042

1043
		result = p_unlink(full_path.ptr);
1044
		git_buf_free(&full_path); /* done with path at this point */
1045 1046

		if (result < 0) {
1047
			giterr_set(GITERR_OS, "Failed to unlink '%s'", full_path.ptr);
1048 1049
			return -1;
		}
1050

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

		giterr_clear();
1059 1060
	}

1061
	return 0;
1062 1063
}

1064
int git_reference_delete(git_reference *ref)
1065
{
1066
	int result = reference_delete(ref);
1067
	git_reference_free(ref);
1068
	return result;
1069 1070
}

1071
int git_reference_lookup(git_reference **ref_out,
1072
	git_repository *repo, const char *name)
1073
{
1074 1075 1076
	return git_reference_lookup_resolved(ref_out, repo, name, 0);
}

1077
int git_reference_name_to_id(
1078 1079 1080 1081 1082 1083 1084 1085
	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;

1086
	git_oid_cpy(out, git_reference_target(ref));
1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098
	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;
1099 1100

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

1102
	*ref_out = NULL;
1103

1104 1105 1106 1107
	if (max_nesting > MAX_NESTING_LEVEL)
		max_nesting = MAX_NESTING_LEVEL;
	else if (max_nesting < 0)
		max_nesting = DEFAULT_NESTING_LEVEL;
1108

1109 1110
	scan = git__calloc(1, sizeof(git_reference));
	GITERR_CHECK_ALLOC(scan);
1111

1112 1113
	scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char));
	GITERR_CHECK_ALLOC(scan->name);
1114

1115
	if ((result = git_reference__normalize_name_lax(
1116 1117 1118 1119 1120
		scan->name,
		GIT_REFNAME_MAX,
		name)) < 0) {
			git_reference_free(scan);
			return result;
1121 1122 1123 1124 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
	}

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

1153 1154 1155
/**
 * Getters
 */
1156
git_ref_t git_reference_type(const git_reference *ref)
Vicent Marti committed
1157 1158
{
	assert(ref);
1159 1160 1161 1162 1163 1164 1165 1166

	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
1167 1168
}

1169
int git_reference_is_packed(git_reference *ref)
Vicent Marti committed
1170 1171
{
	assert(ref);
1172
	return !!(ref->flags & GIT_REF_PACKED);
Vicent Marti committed
1173 1174
}

1175
const char *git_reference_name(const git_reference *ref)
Vicent Marti committed
1176 1177
{
	assert(ref);
1178
	return ref->name;
Vicent Marti committed
1179 1180
}

1181
git_repository *git_reference_owner(const git_reference *ref)
1182
{
1183 1184
	assert(ref);
	return ref->owner;
1185 1186
}

1187
const git_oid *git_reference_target(const git_reference *ref)
Vicent Marti committed
1188 1189 1190
{
	assert(ref);

1191
	if ((ref->flags & GIT_REF_OID) == 0)
Vicent Marti committed
1192 1193
		return NULL;

1194
	return &ref->target.oid;
Vicent Marti committed
1195 1196
}

1197
const char *git_reference_symbolic_target(const git_reference *ref)
1198
{
1199
	assert(ref);
1200

1201
	if ((ref->flags & GIT_REF_SYMBOLIC) == 0)
1202 1203
		return NULL;

1204
	return ref->target.symbolic;
1205 1206
}

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

1218
	if ((error = git_reference__normalize_name_lax(
1219 1220
		normalized,
		sizeof(normalized),
1221 1222
		name)) < 0)
			return error;
1223

1224 1225
	if ((error = reference_can_write(repo, normalized, NULL, force)) < 0)
		return error;
1226

1227
	if (reference_alloc(&ref, repo, normalized) < 0)
1228
		return -1;
1229

1230 1231 1232 1233
	ref->flags |= GIT_REF_SYMBOLIC;

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

1244
	return 0;
1245 1246
}

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

1258
	if ((error = git_reference__normalize_name_lax(
1259 1260
		normalized,
		sizeof(normalized),
1261 1262
		name)) < 0)
			return error;
1263

1264 1265
	if ((error = reference_can_write(repo, normalized, NULL, force)) < 0)
		return error;
1266

1267
	if (reference_alloc(&ref, repo, name) < 0)
1268
		return -1;
1269 1270

	ref->flags |= GIT_REF_OID;
1271 1272

	/* set the oid; this will write the reference on disk */
1273
	if (git_reference_set_target(ref, id) < 0) {
1274 1275 1276
		git_reference_free(ref);
		return -1;
	}
1277

1278 1279 1280 1281
	if (ref_out == NULL) {
		git_reference_free(ref);
	} else {
		*ref_out = ref;
1282 1283
	}

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

1299
	if ((ref->flags & GIT_REF_OID) == 0) {
1300
		giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");
1301 1302
		return -1;
	}
Vicent Marti committed
1303

1304 1305
	assert(ref->owner);

1306
	if (git_repository_odb__weakptr(&odb, ref->owner) < 0)
1307
		return -1;
1308

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

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

1320
	/* Write back to disk */
1321
	return loose_write(ref);
1322 1323
}

Vicent Marti committed
1324 1325 1326 1327 1328 1329 1330
/*
 * 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.
 */
1331
int git_reference_symbolic_set_target(git_reference *ref, const char *target)
Vicent Marti committed
1332
{
1333
	int error;
1334
	char normalized[GIT_REFNAME_MAX];
Vicent Marti committed
1335

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

1342
	if ((error = git_reference__normalize_name_lax(
1343 1344
		normalized,
		sizeof(normalized),
1345 1346
		target)) < 0)
			return error;
Vicent Marti committed
1347

1348 1349
	git__free(ref->target.symbolic);
	ref->target.symbolic = git__strdup(normalized);
1350
	GITERR_CHECK_ALLOC(ref->target.symbolic);
Vicent Marti committed
1351

1352
	return loose_write(ref);
Vicent Marti committed
1353 1354
}

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

1363 1364 1365 1366
	normalization_flags = ref->flags & GIT_REF_SYMBOLIC ?
		GIT_REF_FORMAT_ALLOW_ONELEVEL
		: GIT_REF_FORMAT_NORMAL;

1367
	if ((result = git_reference_normalize_name(
1368 1369 1370
		normalized,
		sizeof(normalized),
		new_name,
1371 1372
		normalization_flags)) < 0)
			return result;
1373

1374 1375
	if ((result = reference_can_write(ref->owner, normalized, ref->name, force)) < 0)
		return result;
1376

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

1382
	/*
1383 1384
	 * Check if we have to update HEAD.
	 */
1385
	if ((should_head_be_updated = git_branch_is_head(ref)) < 0)
1386 1387 1388
		goto cleanup;

	/*
1389
	 * Now delete the old ref and remove an possibly existing directory
1390 1391 1392
	 * 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
1393
	 */
1394
	if (reference_delete(ref) < 0)
1395
		goto cleanup;
1396

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

1408
	if (result < 0)
1409
		goto rollback;
1410

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

	/*
1422
	 * Rename the reflog file, if it exists.
1423
	 */
1424
	if ((git_reference_has_log(ref)) && (git_reflog_rename(ref, new_name) < 0))
1425 1426
		goto cleanup;

1427
	/*
1428 1429 1430 1431 1432 1433 1434
	 * 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;
1435

1436 1437 1438
	git_buf_free(&aux_path);
	return 0;

1439
cleanup:
1440
	git_buf_free(&aux_path);
1441
	return -1;
1442

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

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

1457
	git_buf_free(&aux_path);
1458
	return -1;
1459
}
1460

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

1469
int git_reference_packall(git_repository *repo)
Vicent Marti committed
1470
{
1471 1472 1473
	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 */
1474
		return -1;
Vicent Marti committed
1475

1476
	return 0;
Vicent Marti committed
1477 1478
}

1479 1480 1481
int git_reference_foreach(
	git_repository *repo,
	unsigned int list_flags,
1482
	git_reference_foreach_cb callback,
1483
	void *payload)
1484
{
1485
	int result;
1486
	struct dirent_list_data data;
1487
	git_buf refs_path = GIT_BUF_INIT;
1488

1489
	/* list all the packed references first */
1490 1491
	if (list_flags & GIT_REF_PACKED) {
		const char *ref_name;
1492
		void *ref;
1493
		GIT_UNUSED(ref);
1494

1495
		if (packed_load(repo) < 0)
1496
			return -1;
1497

1498
		git_strmap_foreach(repo->references.packfile, ref_name, ref, {
1499 1500
			if (callback(ref_name, payload))
				return GIT_EUSER;
1501
		});
1502 1503
	}

1504 1505
	/* now list the loose references, trying not to
	 * duplicate the ref names already in the packed-refs file */
1506 1507 1508 1509 1510 1511

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

1514
	if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0)
1515
		return -1;
1516

1517
	result = git_path_direach(&refs_path, _dirent_loose_listall, &data);
1518

1519 1520
	git_buf_free(&refs_path);

1521
	return data.callback_error ? GIT_EUSER : result;
1522 1523
}

1524
static int cb__reflist_add(const char *ref, void *data)
1525 1526 1527 1528
{
	return git_vector_insert((git_vector *)data, git__strdup(ref));
}

1529
int git_reference_list(
1530 1531
	git_strarray *array,
	git_repository *repo,
1532
	unsigned int list_flags)
1533 1534 1535 1536 1537 1538 1539 1540
{
	git_vector ref_list;

	assert(array && repo);

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

1541
	if (git_vector_init(&ref_list, 8, NULL) < 0)
1542
		return -1;
1543

1544
	if (git_reference_foreach(
1545
			repo, list_flags, &cb__reflist_add, (void *)&ref_list) < 0) {
1546
		git_vector_free(&ref_list);
1547
		return -1;
1548 1549
	}

1550 1551
	array->strings = (char **)ref_list.contents;
	array->count = ref_list.length;
1552
	return 0;
1553
}
Vicent Marti committed
1554

1555
int git_reference_reload(git_reference *ref)
1556
{
1557
	return reference_lookup(ref);
1558
}
1559

1560 1561 1562 1563
void git_repository__refcache_free(git_refcache *refs)
{
	assert(refs);

Vicent Marti committed
1564
	if (refs->packfile) {
1565 1566
		struct packref *reference;

1567
		git_strmap_foreach_value(refs->packfile, reference, {
1568 1569
			git__free(reference);
		});
Vicent Marti committed
1570

1571
		git_strmap_free(refs->packfile);
Vicent Marti committed
1572
	}
1573
}
1574

1575
static int is_valid_ref_char(char ch)
1576
{
1577
	if ((unsigned) ch <= ' ')
1578
		return 0;
1579 1580 1581 1582 1583 1584 1585 1586

	switch (ch) {
	case '~':
	case '^':
	case ':':
	case '\\':
	case '?':
	case '[':
1587
	case '*':
1588
		return 0;
1589
	default:
1590
		return 1;
1591 1592 1593
	}
}

1594
static int ensure_segment_validity(const char *name)
1595
{
1596 1597
	const char *current = name;
	char prev = '\0';
1598

1599 1600
	if (*current == '.')
		return -1; /* Refname starts with "." */
1601

1602 1603 1604
	for (current = name; ; current++) {
		if (*current == '\0' || *current == '/')
			break;
1605

1606 1607
		if (!is_valid_ref_char(*current))
			return -1; /* Illegal character in refname */
1608

1609 1610
		if (prev == '.' && *current == '.')
			return -1; /* Refname contains ".." */
1611

1612 1613
		if (prev == '@' && *current == '{')
			return -1; /* Refname contains "@{" */
1614

1615 1616
		prev = *current;
	}
1617

1618
	return (int)(current - name);
1619
}
1620

1621
static bool is_all_caps_and_underscore(const char *name, size_t len)
1622
{
1623
	size_t i;
1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640
	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;
}

1641 1642 1643 1644 1645 1646 1647 1648
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;
1649
	int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
1650 1651 1652
	unsigned int process_flags;
	bool normalize = (buf != NULL);
	assert(name);
1653

1654
	process_flags = flags;
1655 1656
	current = (char *)name;

1657 1658
	if (normalize)
		git_buf_clear(buf);
1659 1660 1661 1662

	while (true) {
		segment_len = ensure_segment_validity(current);
		if (segment_len < 0) {
1663
			if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) &&
1664 1665 1666
					current[0] == '*' &&
					(current[1] == '\0' || current[1] == '/')) {
				/* Accept one wildcard as a full refname component. */
1667
				process_flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN;
1668 1669 1670 1671
				segment_len = 1;
			} else
				goto cleanup;
		}
1672

1673
		if (segment_len > 0) {
1674
			if (normalize) {
1675
				size_t cur_len = git_buf_len(buf);
1676

1677
				git_buf_joinpath(buf, git_buf_cstr(buf), current);
1678
				git_buf_truncate(buf,
1679
					cur_len + segment_len + (segments_count ? 1 : 0));
1680

1681 1682
				if (git_buf_oom(buf)) {
					error = -1;
1683
					goto cleanup;
1684
				}
1685
			}
1686

1687
			segments_count++;
1688
		}
1689

1690 1691
		if (current[segment_len] == '\0')
			break;
1692

1693
		current += segment_len + 1;
1694
	}
1695

1696
	/* A refname can not be empty */
1697
	if (segment_len == 0 && segments_count == 0)
1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711
		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;

	/* A refname can not end with ".lock" */
	if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION))
		goto cleanup;

1712 1713 1714 1715
	if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL))
		goto cleanup;

	if ((segments_count == 1 ) &&
1716
		!(is_all_caps_and_underscore(name, (size_t)segment_len) ||
1717 1718 1719 1720 1721 1722
			((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;
1723

1724
	error = 0;
1725

1726
cleanup:
1727
	if (error == GIT_EINVALIDSPEC)
1728 1729 1730
		giterr_set(
			GITERR_REFERENCE,
			"The given reference name '%s' is not valid", name);
1731

1732 1733 1734
	if (error && normalize)
		git_buf_free(buf);

1735 1736
	return error;
}
1737

1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751
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(
1752
		GITERR_REFERENCE,
1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764
		"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;
1765
}
1766

1767
int git_reference__normalize_name_lax(
1768 1769 1770
	char *buffer_out,
	size_t out_size,
	const char *name)
1771
{
1772 1773 1774 1775 1776
	return git_reference_normalize_name(
		buffer_out,
		out_size,
		name,
		GIT_REF_FORMAT_ALLOW_ONELEVEL);
1777
}
1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793
#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
1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805
/* 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();
1806
		return git_reference_create(NULL, repo, ref_name, oid, 1);
nulltoken committed
1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818
	}

	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 */
1819
		sym_target = git_reference_symbolic_target(ref);
nulltoken committed
1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830

		/* 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();
1831
			res = git_reference_create(NULL, repo, sym_target, oid, 1);
nulltoken committed
1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848
			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 */
1849
	res = git_reference_set_target(ref, oid);
nulltoken committed
1850 1851 1852
	git_reference_free(ref);
	return res;
}
1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 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

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);
}
1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906

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

1908 1909 1910 1911 1912
int git_reference_is_branch(git_reference *ref)
{
	assert(ref);
	return git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) == 0;
}
1913 1914 1915 1916 1917 1918

int git_reference_is_remote(git_reference *ref)
{
	assert(ref);
	return git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR) == 0;
}
1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931

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;

1932
	oid = git_reference_target(ref);
1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954

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

1956 1957
	if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG)
		error = git_object__dup(peeled, target);
1958
	else
1959 1960 1961 1962 1963 1964 1965
		error = git_object_peel(peeled, target, target_type);

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

1967 1968 1969 1970
int git_reference__is_valid_name(
	const char *refname,
	unsigned int flags)
{
nulltoken committed
1971 1972 1973
	int error;

	error = git_reference__normalize_name(NULL, refname, flags) == 0;
1974
	giterr_clear();
nulltoken committed
1975 1976

	return error;
1977 1978
}

1979 1980 1981
int git_reference_is_valid_name(
	const char *refname)
{
1982
	return git_reference__is_valid_name(
1983
		refname,
1984
		GIT_REF_FORMAT_ALLOW_ONELEVEL);
1985
}