refs.c 21.7 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 "filebuf.h"
13
#include "pack.h"
14
#include "reflog.h"
15
#include "refdb.h"
16

Vicent Marti committed
17 18
#include <git2/tag.h>
#include <git2/object.h>
19
#include <git2/oid.h>
20
#include <git2/branch.h>
21 22
#include <git2/refs.h>
#include <git2/refdb.h>
23
#include <git2/sys/refs.h>
Vicent Marti committed
24

25
GIT__USE_STRMAP;
26

27 28
#define DEFAULT_NESTING_LEVEL	5
#define MAX_NESTING_LEVEL		10
29

30 31 32 33
enum {
	GIT_PACKREF_HAS_PEEL = 1,
	GIT_PACKREF_WAS_LOOSE = 2
};
34

35
static git_reference *alloc_ref(const char *name)
36 37 38
{
	git_reference *ref;
	size_t namelen = strlen(name);
39

40 41 42 43 44 45 46 47 48
	if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL)
		return NULL;

	memcpy(ref->name, name, namelen + 1);

	return ref;
}

git_reference *git_reference__alloc_symbolic(
49
	const char *name, const char *target)
50
{
51
	git_reference *ref;
52

53
	assert(name && target);
54

55
	ref = alloc_ref(name);
56
	if (!ref)
57
		return NULL;
Vicent Marti committed
58

59
	ref->type = GIT_REF_SYMBOLIC;
Vicent Marti committed
60

61 62 63
	if ((ref->target.symbolic = git__strdup(target)) == NULL) {
		git__free(ref);
		return NULL;
64
	}
65

66 67 68 69 70 71 72 73 74 75
	return ref;
}

git_reference *git_reference__alloc(
	const char *name,
	const git_oid *oid,
	const git_oid *peel)
{
	git_reference *ref;

76
	assert(name && oid);
77

78
	ref = alloc_ref(name);
79 80 81 82
	if (!ref)
		return NULL;

	ref->type = GIT_REF_OID;
83
	git_oid_cpy(&ref->target.oid, oid);
84 85

	if (peel != NULL)
86
		git_oid_cpy(&ref->peel, peel);
87

88
	return ref;
89
}
90

91
void git_reference_free(git_reference *reference)
92
{
93 94
	if (reference == NULL)
		return;
95

96
	if (reference->type == GIT_REF_SYMBOLIC)
97
		git__free(reference->target.symbolic);
98

Vicent Marti committed
99 100
	if (reference->db)
		GIT_REFCOUNT_DEC(reference->db, git_refdb__free);
101 102

	git__free(reference);
103
}
104

105
int git_reference_delete(git_reference *ref)
106
{
Vicent Marti committed
107
	return git_refdb_delete(ref->db, ref->name);
108 109
}

110
int git_reference_lookup(git_reference **ref_out,
111
	git_repository *repo, const char *name)
112
{
113 114 115
	return git_reference_lookup_resolved(ref_out, repo, name, 0);
}

116
int git_reference_name_to_id(
117 118 119 120 121 122 123 124
	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;

125
	git_oid_cpy(out, git_reference_target(ref));
126 127 128 129 130 131 132 133 134 135
	git_reference_free(ref);
	return 0;
}

int git_reference_lookup_resolved(
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	int max_nesting)
{
136 137 138 139 140
	char scan_name[GIT_REFNAME_MAX];
	git_ref_t scan_type;
	int error = 0, nesting;
	git_reference *ref = NULL;
	git_refdb *refdb;
141 142

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

144
	*ref_out = NULL;
145

146 147 148 149
	if (max_nesting > MAX_NESTING_LEVEL)
		max_nesting = MAX_NESTING_LEVEL;
	else if (max_nesting < 0)
		max_nesting = DEFAULT_NESTING_LEVEL;
150

151 152
	strncpy(scan_name, name, GIT_REFNAME_MAX);
	scan_type = GIT_REF_SYMBOLIC;
153

154 155
	if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
		return -1;
156

157 158
	if ((error = git_reference__normalize_name_lax(scan_name, GIT_REFNAME_MAX, name)) < 0)
		return error;
159 160

	for (nesting = max_nesting;
161
		 nesting >= 0 && scan_type == GIT_REF_SYMBOLIC;
162 163
		 nesting--)
	{
164 165 166 167
		if (nesting != max_nesting) {
			strncpy(scan_name, ref->target.symbolic, GIT_REFNAME_MAX);
			git_reference_free(ref);
		}
168

169 170
		if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0)
			return error;
171

172
		scan_type = ref->type;
173 174
	}

175
	if (scan_type != GIT_REF_OID && max_nesting != 0) {
176 177
		giterr_set(GITERR_REFERENCE,
			"Cannot resolve reference (>%u levels deep)", max_nesting);
178
		git_reference_free(ref);
179 180 181
		return -1;
	}

182
	*ref_out = ref;
183
	return 0;
184 185
}

186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname)
{
	int error = 0, i;
	bool fallbackmode = true, foundvalid = false;
	git_reference *ref;
	git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_INIT;

	static const char* formatters[] = {
		"%s",
		GIT_REFS_DIR "%s",
		GIT_REFS_TAGS_DIR "%s",
		GIT_REFS_HEADS_DIR "%s",
		GIT_REFS_REMOTES_DIR "%s",
		GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE,
		NULL
	};

	if (*refname)
		git_buf_puts(&name, refname);
	else {
		git_buf_puts(&name, GIT_HEAD_FILE);
		fallbackmode = false;
	}

	for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) {

		git_buf_clear(&refnamebuf);

		if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0)
			goto cleanup;

		if (!git_reference_is_valid_name(git_buf_cstr(&refnamebuf))) {
			error = GIT_EINVALIDSPEC;
			continue;
		}
		foundvalid = true;

		error = git_reference_lookup_resolved(&ref, repo, git_buf_cstr(&refnamebuf), -1);

		if (!error) {
			*out = ref;
			error = 0;
			goto cleanup;
		}

		if (error != GIT_ENOTFOUND)
			goto cleanup;
	}

cleanup:
	if (error && !foundvalid) {
		/* never found a valid reference name */
		giterr_set(GITERR_REFERENCE,
			"Could not use '%s' as valid reference name", git_buf_cstr(&name));
	}

	git_buf_free(&name);
	git_buf_free(&refnamebuf);
	return error;
}

247 248 249
/**
 * Getters
 */
250
git_ref_t git_reference_type(const git_reference *ref)
Vicent Marti committed
251 252
{
	assert(ref);
253
	return ref->type;
Vicent Marti committed
254 255
}

256
const char *git_reference_name(const git_reference *ref)
Vicent Marti committed
257 258
{
	assert(ref);
259
	return ref->name;
Vicent Marti committed
260 261
}

262
git_repository *git_reference_owner(const git_reference *ref)
263
{
264
	assert(ref);
265
	return ref->db->repo;
266 267
}

268
const git_oid *git_reference_target(const git_reference *ref)
Vicent Marti committed
269 270 271
{
	assert(ref);

272
	if (ref->type != GIT_REF_OID)
Vicent Marti committed
273 274
		return NULL;

275
	return &ref->target.oid;
276 277 278 279 280 281
}

const git_oid *git_reference_target_peel(const git_reference *ref)
{
	assert(ref);

282
	if (ref->type != GIT_REF_OID || git_oid_iszero(&ref->peel))
283 284
		return NULL;

285
	return &ref->peel;
Vicent Marti committed
286 287
}

288
const char *git_reference_symbolic_target(const git_reference *ref)
289
{
290
	assert(ref);
291

292
	if (ref->type != GIT_REF_SYMBOLIC)
293 294
		return NULL;

295
	return ref->target.symbolic;
296 297
}

298
static int reference__create(
299 300 301
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
302 303
	const git_oid *oid,
	const char *symbolic,
304
	int force)
305 306
{
	char normalized[GIT_REFNAME_MAX];
307
	git_refdb *refdb;
308
	git_reference *ref = NULL;
309
	int error = 0;
310

311 312 313
	if (ref_out)
		*ref_out = NULL;

Vicent Marti committed
314 315 316 317 318 319
	error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name);
	if (error < 0)
		return error;

	error = git_repository_refdb__weakptr(&refdb, repo);
	if (error < 0)
320
		return error;
321 322 323

	if (oid != NULL) {
		assert(symbolic == NULL);
Vicent Marti committed
324
		ref = git_reference__alloc(normalized, oid, NULL);
325
	} else {
Vicent Marti committed
326
		ref = git_reference__alloc_symbolic(normalized, symbolic);
327
	}
328

329
	GITERR_CHECK_ALLOC(ref);
330

Vicent Marti committed
331
	if ((error = git_refdb_write(refdb, ref, force)) < 0) {
332
		git_reference_free(ref);
333
		return error;
334
	}
335

336
	if (ref_out == NULL)
337
		git_reference_free(ref);
338
	else
339
		*ref_out = ref;
340

341
	return 0;
342 343
}

344
int git_reference_create(
345 346 347
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
348
	const git_oid *oid,
349
	int force)
350
{
351 352
	git_odb *odb;
	int error = 0;
353

354
	assert(repo && name && oid);
355

356 357
	/* Sanity check the reference being created - target must exist. */
	if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
358
		return error;
359

360 361 362
	if (!git_odb_exists(odb, oid)) {
		giterr_set(GITERR_REFERENCE,
			"Target OID for the reference doesn't exist on the repository");
363 364
		return -1;
	}
365

366
	return reference__create(ref_out, repo, name, oid, NULL, force);
367
}
Vicent Marti committed
368

369 370 371 372 373 374 375 376 377
int git_reference_symbolic_create(
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	const char *target,
	int force)
{
	char normalized[GIT_REFNAME_MAX];
	int error = 0;
Vicent Marti committed
378

379
	assert(repo && name && target);
380

381 382 383
	if ((error = git_reference__normalize_name_lax(
		normalized, sizeof(normalized), target)) < 0)
		return error;
384

385 386
	return reference__create(ref_out, repo, name, NULL, normalized, force);
}
387

388 389 390 391 392 393
int git_reference_set_target(
	git_reference **out,
	git_reference *ref,
	const git_oid *id)
{
	assert(out && ref && id);
394

395 396
	if (ref->type != GIT_REF_OID) {
		giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");
397 398
		return -1;
	}
Vicent Marti committed
399

400
	return git_reference_create(out, ref->db->repo, ref->name, id, 1);
401 402
}

403 404 405 406
int git_reference_symbolic_set_target(
	git_reference **out,
	git_reference *ref,
	const char *target)
Vicent Marti committed
407
{
408
	assert(out && ref && target);
409

410
	if (ref->type != GIT_REF_SYMBOLIC) {
411
		giterr_set(GITERR_REFERENCE,
412 413 414
			"Cannot set symbolic target on a direct reference");
		return -1;
	}
415

416
	return git_reference_symbolic_create(out, ref->db->repo, ref->name, target, 1);
Vicent Marti committed
417 418
}

419 420 421 422 423
int git_reference_rename(
	git_reference **out,
	git_reference *ref,
	const char *new_name,
	int force)
424
{
425
	unsigned int normalization_flags;
426
	char normalized[GIT_REFNAME_MAX];
427
	bool should_head_be_updated = false;
428
	int error = 0;
429
	int reference_has_log;
430

431 432 433
	normalization_flags = ref->type == GIT_REF_SYMBOLIC ?
		GIT_REF_FORMAT_ALLOW_ONELEVEL : GIT_REF_FORMAT_NORMAL;

434
	if ((error = git_reference_normalize_name(
Vicent Marti committed
435
			normalized, sizeof(normalized), new_name, normalization_flags)) < 0)
436
		return error;
437

438
	/* Check if we have to update HEAD. */
439
	if ((error = git_branch_is_head(ref)) < 0)
Vicent Marti committed
440
		return error;
441

442 443
	should_head_be_updated = (error > 0);

Vicent Marti committed
444 445
	if ((error = git_refdb_rename(out, ref->db, ref->name, new_name, force)) < 0)
		return error;
446

447
	/* Update HEAD it was poiting to the reference being renamed. */
Vicent Marti committed
448 449
	if (should_head_be_updated &&
		(error = git_repository_set_head(ref->db->repo, new_name)) < 0) {
450
		giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference");
Vicent Marti committed
451
		return error;
452 453
	}

454
	/* Rename the reflog file, if it exists. */
455
	reference_has_log = git_reference_has_log(ref);
Vicent Marti committed
456 457
	if (reference_has_log < 0)
		return reference_has_log;
458

Vicent Marti committed
459 460
	if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0)
		return error;
461

Vicent Marti committed
462
	return 0;
463
}
464

465
int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
Vicent Marti committed
466
{
467 468
	switch (git_reference_type(ref)) {
	case GIT_REF_OID:
469
		return git_reference_lookup(ref_out, ref->db->repo, ref->name);
470

471 472 473 474 475 476 477
	case GIT_REF_SYMBOLIC:
		return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1);

	default:
		giterr_set(GITERR_REFERENCE, "Invalid reference");
		return -1;
	}
Vicent Marti committed
478 479
}

480 481
int git_reference_foreach(
	git_repository *repo,
482
	git_reference_foreach_cb callback,
483
	void *payload)
484
{
485
	git_reference_iterator *iter;
486
	git_reference *ref;
487 488 489 490
	int error;

	if (git_reference_iterator_new(&iter, repo) < 0)
		return -1;
491

492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
	while ((error = git_reference_next(&ref, iter)) == 0) {
		if (callback(ref, payload)) {
			error = GIT_EUSER;
			goto out;
		}
	}

	if (error == GIT_ITEROVER)
		error = 0;

out:
	git_reference_iterator_free(iter);
	return error;
}

int git_reference_foreach_name(
	git_repository *repo,
	git_reference_foreach_name_cb callback,
	void *payload)
{
	git_reference_iterator *iter;
	const char *refname;
514 515 516 517
	int error;

	if (git_reference_iterator_new(&iter, repo) < 0)
		return -1;
518

519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
	while ((error = git_reference_next_name(&refname, iter)) == 0) {
		if (callback(refname, payload)) {
			error = GIT_EUSER;
			goto out;
		}
	}

	if (error == GIT_ITEROVER)
		error = 0;

out:
	git_reference_iterator_free(iter);
	return error;
}

int git_reference_foreach_glob(
	git_repository *repo,
	const char *glob,
	git_reference_foreach_name_cb callback,
	void *payload)
{
	git_reference_iterator *iter;
	const char *refname;
	int error;

	if (git_reference_iterator_glob_new(&iter, repo, glob) < 0)
		return -1;

	while ((error = git_reference_next_name(&refname, iter)) == 0) {
		if (callback(refname, payload)) {
549 550 551 552 553 554 555 556 557 558 559
			error = GIT_EUSER;
			goto out;
		}
	}

	if (error == GIT_ITEROVER)
		error = 0;

out:
	git_reference_iterator_free(iter);
	return error;
560 561
}

562 563 564 565 566 567 568
int git_reference_iterator_new(git_reference_iterator **out, git_repository *repo)
{
	git_refdb *refdb;

	if (git_repository_refdb__weakptr(&refdb, repo) < 0)
		return -1;

569
	return git_refdb_iterator(out, refdb, NULL);
570 571
}

572 573
int git_reference_iterator_glob_new(
	git_reference_iterator **out, git_repository *repo, const char *glob)
574
{
575
	git_refdb *refdb;
576

577 578 579
	if (git_repository_refdb__weakptr(&refdb, repo) < 0)
		return -1;

580
	return git_refdb_iterator(out, refdb, glob);
581 582
}

Vicent Marti committed
583
int git_reference_next(git_reference **out, git_reference_iterator *iter)
584
{
585 586 587 588 589 590
	return git_refdb_iterator_next(out, iter);
}

int git_reference_next_name(const char **out, git_reference_iterator *iter)
{
	return git_refdb_iterator_next_name(out, iter);
591 592 593 594 595
}

void git_reference_iterator_free(git_reference_iterator *iter)
{
	git_refdb_iterator_free(iter);
596 597
}

598
static int cb__reflist_add(const char *ref, void *data)
599 600 601 602
{
	return git_vector_insert((git_vector *)data, git__strdup(ref));
}

603
int git_reference_list(
604
	git_strarray *array,
605
	git_repository *repo)
606 607 608 609 610 611 612 613
{
	git_vector ref_list;

	assert(array && repo);

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

614
	if (git_vector_init(&ref_list, 8, NULL) < 0)
615
		return -1;
616

617
	if (git_reference_foreach_name(
618
			repo, &cb__reflist_add, (void *)&ref_list) < 0) {
619
		git_vector_free(&ref_list);
620
		return -1;
621 622
	}

623 624
	array->strings = (char **)ref_list.contents;
	array->count = ref_list.length;
625
	return 0;
626
}
Vicent Marti committed
627

628
static int is_valid_ref_char(char ch)
629
{
630
	if ((unsigned) ch <= ' ')
631
		return 0;
632 633 634 635 636 637 638 639

	switch (ch) {
	case '~':
	case '^':
	case ':':
	case '\\':
	case '?':
	case '[':
640
	case '*':
641
		return 0;
642
	default:
643
		return 1;
644 645 646
	}
}

647
static int ensure_segment_validity(const char *name)
648
{
649 650
	const char *current = name;
	char prev = '\0';
651 652
	const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION);
	int segment_len;
653

654 655
	if (*current == '.')
		return -1; /* Refname starts with "." */
656

657 658 659
	for (current = name; ; current++) {
		if (*current == '\0' || *current == '/')
			break;
660

661 662
		if (!is_valid_ref_char(*current))
			return -1; /* Illegal character in refname */
663

664 665
		if (prev == '.' && *current == '.')
			return -1; /* Refname contains ".." */
666

667 668
		if (prev == '@' && *current == '{')
			return -1; /* Refname contains "@{" */
669

670 671
		prev = *current;
	}
672

673 674
	segment_len = (int)(current - name);

675
	/* A refname component can not end with ".lock" */
676
	if (segment_len >= lock_len &&
677
		!memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len))
678 679
			return -1;

680
	return segment_len;
681
}
682

683
static bool is_all_caps_and_underscore(const char *name, size_t len)
684
{
685
	size_t i;
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702
	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;
}

703 704 705 706 707 708 709 710
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;
711
	int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
712 713 714
	unsigned int process_flags;
	bool normalize = (buf != NULL);
	assert(name);
715

716
	process_flags = flags;
717 718
	current = (char *)name;

719 720 721
	if (*current == '/')
		goto cleanup;

722 723
	if (normalize)
		git_buf_clear(buf);
724 725 726 727

	while (true) {
		segment_len = ensure_segment_validity(current);
		if (segment_len < 0) {
728
			if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) &&
729 730 731
					current[0] == '*' &&
					(current[1] == '\0' || current[1] == '/')) {
				/* Accept one wildcard as a full refname component. */
732
				process_flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN;
733 734 735 736
				segment_len = 1;
			} else
				goto cleanup;
		}
737

738
		if (segment_len > 0) {
739
			if (normalize) {
740
				size_t cur_len = git_buf_len(buf);
741

742
				git_buf_joinpath(buf, git_buf_cstr(buf), current);
743
				git_buf_truncate(buf,
744
					cur_len + segment_len + (segments_count ? 1 : 0));
745

746 747
				if (git_buf_oom(buf)) {
					error = -1;
748
					goto cleanup;
749
				}
750
			}
751

752
			segments_count++;
753
		}
754

755 756
		/* No empty segment is allowed when not normalizing */
		if (segment_len == 0 && !normalize)
757
			goto cleanup;
758

759 760
		if (current[segment_len] == '\0')
			break;
761

762
		current += segment_len + 1;
763
	}
764

765
	/* A refname can not be empty */
766
	if (segment_len == 0 && segments_count == 0)
767 768 769 770 771 772 773 774 775 776
		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;

777 778 779 780
	if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL))
		goto cleanup;

	if ((segments_count == 1 ) &&
781
	    !(flags & GIT_REF_FORMAT_REFSPEC_SHORTHAND) &&
782
		!(is_all_caps_and_underscore(name, (size_t)segment_len) ||
783 784 785 786 787 788
			((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;
789

790
	error = 0;
791

792
cleanup:
793
	if (error == GIT_EINVALIDSPEC)
794 795 796
		giterr_set(
			GITERR_REFERENCE,
			"The given reference name '%s' is not valid", name);
797

798 799 800
	if (error && normalize)
		git_buf_free(buf);

801 802
	return error;
}
803

804 805 806 807 808 809 810 811 812 813 814 815 816 817
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(
818
		GITERR_REFERENCE,
819 820 821 822 823 824 825 826 827 828 829 830
		"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;
831
}
832

833
int git_reference__normalize_name_lax(
834 835 836
	char *buffer_out,
	size_t out_size,
	const char *name)
837
{
838 839 840 841 842
	return git_reference_normalize_name(
		buffer_out,
		out_size,
		name,
		GIT_REF_FORMAT_ALLOW_ONELEVEL);
843
}
844 845 846 847
#define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC)

int git_reference_cmp(git_reference *ref1, git_reference *ref2)
{
848
	git_ref_t type1, type2;
849 850
	assert(ref1 && ref2);

851 852 853
	type1 = git_reference_type(ref1);
	type2 = git_reference_type(ref2);

854
	/* let's put symbolic refs before OIDs */
855 856
	if (type1 != type2)
		return (type1 == GIT_REF_SYMBOLIC) ? -1 : 1;
857

858
	if (type1 == GIT_REF_SYMBOLIC)
859 860
		return strcmp(ref1->target.symbolic, ref2->target.symbolic);

861
	return git_oid__cmp(&ref1->target.oid, &ref2->target.oid);
862 863
}

864 865 866 867 868
static int reference__update_terminal(
	git_repository *repo,
	const char *ref_name,
	const git_oid *oid,
	int nesting)
nulltoken committed
869 870
{
	git_reference *ref;
871
	int error = 0;
nulltoken committed
872

873 874
	if (nesting > MAX_NESTING_LEVEL) {
		giterr_set(GITERR_REFERENCE, "Reference chain too deep (%d)", nesting);
875
		return GIT_ENOTFOUND;
876
	}
877

878
	error = git_reference_lookup(&ref, repo, ref_name);
nulltoken committed
879

880 881
	/* If we haven't found the reference at all, create a new reference. */
	if (error == GIT_ENOTFOUND) {
nulltoken committed
882
		giterr_clear();
883
		return git_reference_create(NULL, repo, ref_name, oid, 0);
nulltoken committed
884
	}
885

886 887
	if (error < 0)
		return error;
888

889
	/* If the ref is a symbolic reference, follow its target. */
nulltoken committed
890
	if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
891 892
		error = reference__update_terminal(repo, git_reference_symbolic_target(ref), oid,
			nesting+1);
nulltoken committed
893
		git_reference_free(ref);
894 895 896
	} else {
		git_reference_free(ref);
		error = git_reference_create(NULL, repo, ref_name, oid, 1);
nulltoken committed
897
	}
898

899
	return error;
nulltoken committed
900
}
901

902 903 904 905 906 907 908 909 910
/*
 * Starting with the reference given by `ref_name`, follows symbolic
 * references until a direct reference is found and updated the OID
 * on that direct reference to `oid`.
 */
int git_reference__update_terminal(
	git_repository *repo,
	const char *ref_name,
	const git_oid *oid)
911
{
912
	return reference__update_terminal(repo, ref_name, oid, 0);
913 914
}

915 916 917 918 919 920 921 922
int git_reference_has_log(
	git_reference *ref)
{
	git_buf path = GIT_BUF_INIT;
	int result;

	assert(ref);

923 924
	if (git_buf_join_n(&path, '/', 3, ref->db->repo->path_repository,
		GIT_REFLOG_DIR, ref->name) < 0)
925 926 927 928 929 930 931
		return -1;

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

	return result;
}
932

933 934 935 936 937
int git_reference__is_branch(const char *ref_name)
{
	return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0;
}

938 939 940
int git_reference_is_branch(git_reference *ref)
{
	assert(ref);
941
	return git_reference__is_branch(ref->name);
942
}
943

944 945 946 947 948
int git_reference__is_remote(const char *ref_name)
{
	return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0;
}

949 950 951
int git_reference_is_remote(git_reference *ref)
{
	assert(ref);
952
	return git_reference__is_remote(ref->name);
953
}
954

955 956 957 958 959 960 961 962 963 964 965
int git_reference__is_tag(const char *ref_name)
{
	return git__prefixcmp(ref_name, GIT_REFS_TAGS_DIR) == 0;
}

int git_reference_is_tag(git_reference *ref)
{
	assert(ref);
	return git_reference__is_tag(ref->name);
}

966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984
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;
}

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

985 986 987 988 989 990 991
	if (ref->type == GIT_REF_OID) {
		resolved = ref;
	} else {
		if ((error = git_reference_resolve(&resolved, ref)) < 0)
			return peel_error(error, ref, "Cannot resolve reference");
	}

992
	if (!git_oid_iszero(&resolved->peel)) {
993
		error = git_object_lookup(&target,
994
			git_reference_owner(ref), &resolved->peel, GIT_OBJ_ANY);
995 996
	} else {
		error = git_object_lookup(&target,
997
			git_reference_owner(ref), &resolved->target.oid, GIT_OBJ_ANY);
998
	}
999

1000
	if (error < 0) {
1001 1002 1003
		peel_error(error, ref, "Cannot retrieve reference target");
		goto cleanup;
	}
1004

1005
	if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG)
1006
		error = git_object_dup(peeled, target);
1007
	else
1008 1009 1010 1011
		error = git_object_peel(peeled, target, target_type);

cleanup:
	git_object_free(target);
1012 1013 1014 1015

	if (resolved != ref)
		git_reference_free(resolved);

1016 1017
	return error;
}
1018

1019 1020 1021 1022
int git_reference__is_valid_name(
	const char *refname,
	unsigned int flags)
{
nulltoken committed
1023 1024 1025
	int error;

	error = git_reference__normalize_name(NULL, refname, flags) == 0;
1026
	giterr_clear();
nulltoken committed
1027 1028

	return error;
1029 1030
}

1031 1032 1033
int git_reference_is_valid_name(
	const char *refname)
{
1034
	return git_reference__is_valid_name(
1035
		refname,
1036
		GIT_REF_FORMAT_ALLOW_ONELEVEL);
1037
}
1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054

const char *git_reference_shorthand(git_reference *ref)
{
	const char *name = ref->name;

	if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR))
		return name + strlen(GIT_REFS_HEADS_DIR);
	else if (!git__prefixcmp(name, GIT_REFS_TAGS_DIR))
		return name + strlen(GIT_REFS_TAGS_DIR);
	else if (!git__prefixcmp(name, GIT_REFS_REMOTES_DIR))
		return name + strlen(GIT_REFS_REMOTES_DIR);
	else if (!git__prefixcmp(name, GIT_REFS_DIR))
		return name + strlen(GIT_REFS_DIR);

	/* No shorthands are avaiable, so just return the name */
	return name;
}