refs.c 27.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 "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>
24
#include <git2/signature.h>
25
#include <git2/commit.h>
Vicent Marti committed
26

27
GIT__USE_STRMAP;
28

29 30
#define DEFAULT_NESTING_LEVEL	5
#define MAX_NESTING_LEVEL		10
31

32 33 34 35
enum {
	GIT_PACKREF_HAS_PEEL = 1,
	GIT_PACKREF_WAS_LOOSE = 2
};
36

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

42 43 44 45 46 47 48 49 50
	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(
51
	const char *name, const char *target)
52
{
53
	git_reference *ref;
54

55
	assert(name && target);
56

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

61
	ref->type = GIT_REF_SYMBOLIC;
Vicent Marti committed
62

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

68 69 70 71 72 73 74 75 76 77
	return ref;
}

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

78
	assert(name && oid);
79

80
	ref = alloc_ref(name);
81 82 83 84
	if (!ref)
		return NULL;

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

	if (peel != NULL)
88
		git_oid_cpy(&ref->peel, peel);
89

90
	return ref;
91
}
92

93 94 95 96 97 98 99 100 101 102 103
git_reference *git_reference__set_name(
	git_reference *ref, const char *name)
{
	size_t namelen = strlen(name);
	git_reference *rewrite =
		git__realloc(ref, sizeof(git_reference) + namelen + 1);
	if (rewrite != NULL)
		memcpy(rewrite->name, name, namelen + 1);
	return rewrite;
}

104
void git_reference_free(git_reference *reference)
105
{
106 107
	if (reference == NULL)
		return;
108

109
	if (reference->type == GIT_REF_SYMBOLIC)
110
		git__free(reference->target.symbolic);
111

Vicent Marti committed
112 113
	if (reference->db)
		GIT_REFCOUNT_DEC(reference->db, git_refdb__free);
114 115

	git__free(reference);
116
}
117

118
int git_reference_delete(git_reference *ref)
119
{
120 121 122 123 124 125 126 127 128
	const git_oid *old_id = NULL;
	const char *old_target = NULL;

	if (ref->type == GIT_REF_OID)
		old_id = &ref->target.oid;
	else
		old_target = ref->target.symbolic;

	return git_refdb_delete(ref->db, ref->name, old_id, old_target);
129 130
}

131 132 133 134 135 136 137 138 139 140 141
int git_reference_remove(git_repository *repo, const char *name)
{
	git_refdb *db;
	int error;

	if ((error = git_repository_refdb__weakptr(&db, repo)) < 0)
		return error;

	return git_refdb_delete(db, name, NULL, NULL);
}

142
int git_reference_lookup(git_reference **ref_out,
143
	git_repository *repo, const char *name)
144
{
145 146 147
	return git_reference_lookup_resolved(ref_out, repo, name, 0);
}

148
int git_reference_name_to_id(
149 150 151 152 153 154 155 156
	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;

157
	git_oid_cpy(out, git_reference_target(ref));
158 159 160 161
	git_reference_free(ref);
	return 0;
}

162
static int reference_normalize_for_repo(
163
	git_refname_t out,
164 165 166 167 168 169 170 171 172 173
	git_repository *repo,
	const char *name)
{
	int precompose;
	unsigned int flags = GIT_REF_FORMAT_ALLOW_ONELEVEL;

	if (!git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) &&
		precompose)
		flags |= GIT_REF_FORMAT__PRECOMPOSE_UNICODE;

174
	return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags);
175 176
}

177 178 179 180 181 182
int git_reference_lookup_resolved(
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	int max_nesting)
{
183
	git_refname_t scan_name;
184 185 186 187
	git_ref_t scan_type;
	int error = 0, nesting;
	git_reference *ref = NULL;
	git_refdb *refdb;
188 189

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

191
	*ref_out = NULL;
192

193 194 195 196
	if (max_nesting > MAX_NESTING_LEVEL)
		max_nesting = MAX_NESTING_LEVEL;
	else if (max_nesting < 0)
		max_nesting = DEFAULT_NESTING_LEVEL;
197

198
	scan_type = GIT_REF_SYMBOLIC;
199

200
	if ((error = reference_normalize_for_repo(scan_name, repo, name)) < 0)
201
		return error;
202

203
	if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
204
		return error;
205 206

	for (nesting = max_nesting;
207
		 nesting >= 0 && scan_type == GIT_REF_SYMBOLIC;
208 209
		 nesting--)
	{
210
		if (nesting != max_nesting) {
211
			strncpy(scan_name, ref->target.symbolic, sizeof(scan_name));
212 213
			git_reference_free(ref);
		}
214

215 216
		if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0)
			return error;
217

218
		scan_type = ref->type;
219 220
	}

221
	if (scan_type != GIT_REF_OID && max_nesting != 0) {
222 223
		giterr_set(GITERR_REFERENCE,
			"Cannot resolve reference (>%u levels deep)", max_nesting);
224
		git_reference_free(ref);
225 226 227
		return -1;
	}

228
	*ref_out = ref;
229
	return 0;
230 231
}

232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
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;
}

293 294 295
/**
 * Getters
 */
296
git_ref_t git_reference_type(const git_reference *ref)
Vicent Marti committed
297 298
{
	assert(ref);
299
	return ref->type;
Vicent Marti committed
300 301
}

302
const char *git_reference_name(const git_reference *ref)
Vicent Marti committed
303 304
{
	assert(ref);
305
	return ref->name;
Vicent Marti committed
306 307
}

308
git_repository *git_reference_owner(const git_reference *ref)
309
{
310
	assert(ref);
311
	return ref->db->repo;
312 313
}

314
const git_oid *git_reference_target(const git_reference *ref)
Vicent Marti committed
315 316 317
{
	assert(ref);

318
	if (ref->type != GIT_REF_OID)
Vicent Marti committed
319 320
		return NULL;

321
	return &ref->target.oid;
322 323 324 325 326 327
}

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

328
	if (ref->type != GIT_REF_OID || git_oid_iszero(&ref->peel))
329 330
		return NULL;

331
	return &ref->peel;
Vicent Marti committed
332 333
}

334
const char *git_reference_symbolic_target(const git_reference *ref)
335
{
336
	assert(ref);
337

338
	if (ref->type != GIT_REF_SYMBOLIC)
339 340
		return NULL;

341
	return ref->target.symbolic;
342 343
}

344
static int reference__create(
345 346 347
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
348 349
	const git_oid *oid,
	const char *symbolic,
350 351
	int force,
	const git_signature *signature,
352
	const char *log_message,
353 354
	const git_oid *old_id,
	const char *old_target)
355
{
356
	git_refname_t normalized;
357
	git_refdb *refdb;
358
	git_reference *ref = NULL;
359
	int error = 0;
360

361
	assert(repo && name);
362
	assert(symbolic || signature);
363

364 365 366
	if (ref_out)
		*ref_out = NULL;

367
	error = reference_normalize_for_repo(normalized, repo, name);
Vicent Marti committed
368 369 370 371 372
	if (error < 0)
		return error;

	error = git_repository_refdb__weakptr(&refdb, repo);
	if (error < 0)
373
		return error;
374 375

	if (oid != NULL) {
376 377
		git_odb *odb;

378
		assert(symbolic == NULL);
379 380 381 382 383 384 385 386 387 388 389

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

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

390
		ref = git_reference__alloc(normalized, oid, NULL);
391
	} else {
392
		git_refname_t normalized_target;
393

394
		if ((error = reference_normalize_for_repo(normalized_target, repo, symbolic)) < 0)
395 396
			return error;

397
		ref = git_reference__alloc_symbolic(normalized, normalized_target);
398
	}
399

400
	GITERR_CHECK_ALLOC(ref);
401

402
	if ((error = git_refdb_write(refdb, ref, force, signature, log_message, old_id, old_target)) < 0) {
403
		git_reference_free(ref);
404
		return error;
405
	}
406

407
	if (ref_out == NULL)
408
		git_reference_free(ref);
409
	else
410
		*ref_out = ref;
411

412
	return 0;
413 414
}

415
int git_reference__log_signature(git_signature **out, git_repository *repo)
416
{
417
	int error;
418
	git_signature *who;
419

420 421
	if(((error = git_signature_default(&who, repo)) < 0) &&
	   ((error = git_signature_now(&who, "unknown", "unknown")) < 0))
422
		return error;
423

424 425
	*out = who;
	return 0;
426 427
}

428
int git_reference_create_matching(
429 430 431 432 433
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	const git_oid *id,
	int force,
434
	const git_oid *old_id,
435
	const git_signature *signature,
436
	const char *log_message)
437

438
{
439 440 441
	int error;
	git_signature *who = NULL;
	
442
	assert(id);
443

444
	if (!signature) {
445
		if ((error = git_reference__log_signature(&who, repo)) < 0)
446 447 448 449 450 451
			return error;
		else
			signature = who;
	}

	error = reference__create(
452
		ref_out, repo, name, id, NULL, force, signature, log_message, old_id, NULL);
Vicent Marti committed
453

454 455
	git_signature_free(who);
	return error;
456
}
457

458 459 460 461 462 463 464 465 466
int git_reference_create(
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	const git_oid *id,
	int force,
	const git_signature *signature,
	const char *log_message)
{
467
        return git_reference_create_matching(ref_out, repo, name, id, force, NULL, signature, log_message);
468 469
}

470
int git_reference_symbolic_create_matching(
471 472 473 474 475
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	const char *target,
	int force,
476
	const char *old_target,
477
	const git_signature *signature,
478
	const char *log_message)
479
{
480 481 482 483 484 485
	int error;
	git_signature *who = NULL;

	assert(target);

	if (!signature) {
486
		if ((error = git_reference__log_signature(&who, repo)) < 0)
487 488 489 490
			return error;
		else
			signature = who;
	}
491

492
	error = reference__create(
493
		ref_out, repo, name, NULL, target, force, signature, log_message, NULL, old_target);
494 495 496

	git_signature_free(who);
	return error;
497 498
}

499 500 501 502 503 504 505 506 507
int git_reference_symbolic_create(
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	const char *target,
	int force,
	const git_signature *signature,
	const char *log_message)
{
508
	return git_reference_symbolic_create_matching(ref_out, repo, name, target, force, NULL, signature, log_message);
509 510
}

511 512 513 514
static int ensure_is_an_updatable_direct_reference(git_reference *ref)
{
	if (ref->type == GIT_REF_OID)
		return 0;
515

516 517
	giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");
	return -1;
518
}
519

520
int git_reference_set_target(
521 522
	git_reference **out,
	git_reference *ref,
523 524
	const git_oid *id,
	const git_signature *signature,
525
	const char *log_message)
526 527
{
	int error;
528
	git_repository *repo;
529 530 531

	assert(out && ref && id);

532 533
	repo = ref->db->repo;

534 535 536
	if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0)
		return error;

537
	return git_reference_create_matching(out, repo, ref->name, id, 1, &ref->target.oid, signature, log_message);
538 539
}

540 541 542 543 544 545 546 547 548
static int ensure_is_an_updatable_symbolic_reference(git_reference *ref)
{
	if (ref->type == GIT_REF_SYMBOLIC)
		return 0;

	giterr_set(GITERR_REFERENCE, "Cannot set symbolic target on a direct reference");
	return -1;
}

549 550 551
int git_reference_symbolic_set_target(
	git_reference **out,
	git_reference *ref,
552 553 554
	const char *target,
	const git_signature *signature,
	const char *log_message)
Vicent Marti committed
555
{
556 557
	int error;

558
	assert(out && ref && target);
559

560 561
	if ((error = ensure_is_an_updatable_symbolic_reference(ref)) < 0)
		return error;
562

563
	return git_reference_symbolic_create_matching(
564
		out, ref->db->repo, ref->name, target, 1, ref->target.symbolic, signature, log_message);
Vicent Marti committed
565 566
}

567 568
static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force,
				 const git_signature *signature, const char *message)
569
{
570
	git_refname_t normalized;
571
	bool should_head_be_updated = false;
572
	int error = 0;
573 574

	assert(ref && new_name && signature);
575

576
	if ((error = reference_normalize_for_repo(
577
			normalized, git_reference_owner(ref), new_name)) < 0)
578
		return error;
579

580

581
	/* Check if we have to update HEAD. */
582
	if ((error = git_branch_is_head(ref)) < 0)
Vicent Marti committed
583
		return error;
584

585 586
	should_head_be_updated = (error > 0);

587
	if ((error = git_refdb_rename(out, ref->db, ref->name, normalized, force, signature, message)) < 0)
Vicent Marti committed
588
		return error;
589

590
	/* Update HEAD it was pointing to the reference being renamed */
Vicent Marti committed
591
	if (should_head_be_updated &&
592
		(error = git_repository_set_head(ref->db->repo, normalized, signature, message)) < 0) {
593
		giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference");
Vicent Marti committed
594
		return error;
595 596
	}

597 598 599
	return 0;
}

600

601 602 603 604
int git_reference_rename(
	git_reference **out,
	git_reference *ref,
	const char *new_name,
605 606 607
	int force,
	const git_signature *signature,
	const char *log_message)
608
{
609
	git_signature *who = (git_signature*)signature;
610
	int error;
611

612
	/* Should we return an error if there is no default? */
613 614
	if (!who &&
	    ((error = git_signature_default(&who, ref->db->repo)) < 0) &&
615
	    ((error = git_signature_now(&who, "unknown", "unknown")) < 0)) {
Vicent Marti committed
616
		return error;
617
	}
618

619
	error = reference__rename(out, ref, new_name, force, who, log_message);
620

621 622
	if (!signature)
		git_signature_free(who);
623 624 625 626

	return error;
}

627
int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
Vicent Marti committed
628
{
629 630
	switch (git_reference_type(ref)) {
	case GIT_REF_OID:
631
		return git_reference_lookup(ref_out, ref->db->repo, ref->name);
632

633 634 635 636 637 638 639
	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
640 641
}

642 643
int git_reference_foreach(
	git_repository *repo,
644
	git_reference_foreach_cb callback,
645
	void *payload)
646
{
647
	git_reference_iterator *iter;
648
	git_reference *ref;
649 650
	int error;

651 652
	if ((error = git_reference_iterator_new(&iter, repo)) < 0)
		return error;
653

654 655
	while (!(error = git_reference_next(&ref, iter))) {
		if ((error = callback(ref, payload)) != 0) {
656
			giterr_set_after_callback(error);
657 658 659
			break;
		}
	}
660 661 662 663 664 665 666 667 668 669 670 671 672 673 674

	if (error == GIT_ITEROVER)
		error = 0;

	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;
675 676
	int error;

677 678
	if ((error = git_reference_iterator_new(&iter, repo)) < 0)
		return error;
679

680 681
	while (!(error = git_reference_next_name(&refname, iter))) {
		if ((error = callback(refname, payload)) != 0) {
682
			giterr_set_after_callback(error);
683 684 685
			break;
		}
	}
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703

	if (error == GIT_ITEROVER)
		error = 0;

	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;

704 705
	if ((error = git_reference_iterator_glob_new(&iter, repo, glob)) < 0)
		return error;
706

707 708
	while (!(error = git_reference_next_name(&refname, iter))) {
		if ((error = callback(refname, payload)) != 0) {
709
			giterr_set_after_callback(error);
710 711 712
			break;
		}
	}
713 714 715 716 717 718

	if (error == GIT_ITEROVER)
		error = 0;

	git_reference_iterator_free(iter);
	return error;
719 720
}

721 722 723 724 725 726 727
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;

728
	return git_refdb_iterator(out, refdb, NULL);
729 730
}

731 732
int git_reference_iterator_glob_new(
	git_reference_iterator **out, git_repository *repo, const char *glob)
733
{
734
	git_refdb *refdb;
735

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

739
	return git_refdb_iterator(out, refdb, glob);
740 741
}

Vicent Marti committed
742
int git_reference_next(git_reference **out, git_reference_iterator *iter)
743
{
744 745 746 747 748 749
	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);
750 751 752 753
}

void git_reference_iterator_free(git_reference_iterator *iter)
{
754 755 756
	if (iter == NULL)
		return;

757
	git_refdb_iterator_free(iter);
758 759
}

760
static int cb__reflist_add(const char *ref, void *data)
761
{
762 763 764
	char *name = git__strdup(ref);
	GITERR_CHECK_ALLOC(name);
	return git_vector_insert((git_vector *)data, name);
765 766
}

767
int git_reference_list(
768
	git_strarray *array,
769
	git_repository *repo)
770 771 772 773 774 775 776 777
{
	git_vector ref_list;

	assert(array && repo);

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

778
	if (git_vector_init(&ref_list, 8, NULL) < 0)
779
		return -1;
780

781
	if (git_reference_foreach_name(
782
			repo, &cb__reflist_add, (void *)&ref_list) < 0) {
783
		git_vector_free(&ref_list);
784
		return -1;
785 786
	}

787 788
	array->strings = (char **)git_vector_detach(&array->count, NULL, &ref_list);

789
	return 0;
790
}
Vicent Marti committed
791

792
static int is_valid_ref_char(char ch)
793
{
794
	if ((unsigned) ch <= ' ')
795
		return 0;
796 797 798 799 800 801 802 803

	switch (ch) {
	case '~':
	case '^':
	case ':':
	case '\\':
	case '?':
	case '[':
804
	case '*':
805
		return 0;
806
	default:
807
		return 1;
808 809 810
	}
}

811
static int ensure_segment_validity(const char *name)
812
{
813 814
	const char *current = name;
	char prev = '\0';
815 816
	const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION);
	int segment_len;
817

818 819
	if (*current == '.')
		return -1; /* Refname starts with "." */
820

821 822 823
	for (current = name; ; current++) {
		if (*current == '\0' || *current == '/')
			break;
824

825 826
		if (!is_valid_ref_char(*current))
			return -1; /* Illegal character in refname */
827

828 829
		if (prev == '.' && *current == '.')
			return -1; /* Refname contains ".." */
830

831 832
		if (prev == '@' && *current == '{')
			return -1; /* Refname contains "@{" */
833

834 835
		prev = *current;
	}
836

837 838
	segment_len = (int)(current - name);

839
	/* A refname component can not end with ".lock" */
840
	if (segment_len >= lock_len &&
841
		!memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len))
842 843
			return -1;

844
	return segment_len;
845
}
846

847
static bool is_all_caps_and_underscore(const char *name, size_t len)
848
{
849
	size_t i;
850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866
	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;
}

867
/* Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 */
868 869 870 871 872 873
int git_reference__normalize_name(
	git_buf *buf,
	const char *name,
	unsigned int flags)
{
	char *current;
874
	int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
875 876
	unsigned int process_flags;
	bool normalize = (buf != NULL);
877 878

#ifdef GIT_USE_ICONV
879
	git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
880
#endif
881

882
	assert(name);
883

884
	process_flags = flags;
885 886
	current = (char *)name;

887 888 889
	if (*current == '/')
		goto cleanup;

890 891
	if (normalize)
		git_buf_clear(buf);
892

893
#ifdef GIT_USE_ICONV
894 895 896 897 898
	if ((flags & GIT_REF_FORMAT__PRECOMPOSE_UNICODE) != 0) {
		size_t namelen = strlen(current);
		if ((error = git_path_iconv_init_precompose(&ic)) < 0 ||
			(error = git_path_iconv(&ic, &current, &namelen)) < 0)
			goto cleanup;
899
		error = GIT_EINVALIDSPEC;
900
	}
901
#endif
902

903 904 905
	while (true) {
		segment_len = ensure_segment_validity(current);
		if (segment_len < 0) {
906
			if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) &&
907 908 909
					current[0] == '*' &&
					(current[1] == '\0' || current[1] == '/')) {
				/* Accept one wildcard as a full refname component. */
910
				process_flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN;
911 912 913 914
				segment_len = 1;
			} else
				goto cleanup;
		}
915

916
		if (segment_len > 0) {
917
			if (normalize) {
918
				size_t cur_len = git_buf_len(buf);
919

920
				git_buf_joinpath(buf, git_buf_cstr(buf), current);
921
				git_buf_truncate(buf,
922
					cur_len + segment_len + (segments_count ? 1 : 0));
923

924 925
				if (git_buf_oom(buf)) {
					error = -1;
926
					goto cleanup;
927
				}
928
			}
929

930
			segments_count++;
931
		}
932

933 934
		/* No empty segment is allowed when not normalizing */
		if (segment_len == 0 && !normalize)
935
			goto cleanup;
936

937 938
		if (current[segment_len] == '\0')
			break;
939

940
		current += segment_len + 1;
941
	}
942

943
	/* A refname can not be empty */
944
	if (segment_len == 0 && segments_count == 0)
945 946 947 948 949 950 951 952 953 954
		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;

955 956 957 958
	if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL))
		goto cleanup;

	if ((segments_count == 1 ) &&
959
	    !(flags & GIT_REF_FORMAT_REFSPEC_SHORTHAND) &&
960
		!(is_all_caps_and_underscore(name, (size_t)segment_len) ||
961 962 963 964 965 966
			((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;
967

968
	error = 0;
969

970
cleanup:
971
	if (error == GIT_EINVALIDSPEC)
972 973 974
		giterr_set(
			GITERR_REFERENCE,
			"The given reference name '%s' is not valid", name);
975

976 977 978
	if (error && normalize)
		git_buf_free(buf);

979
#ifdef GIT_USE_ICONV
980
	git_path_iconv_clear(&ic);
981
#endif
982

983 984
	return error;
}
985

986 987 988 989 990 991 992 993 994 995 996 997 998 999
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(
1000
		GITERR_REFERENCE,
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
		"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;
1013
}
1014

1015 1016
#define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC)

Jacques Germishuys committed
1017 1018 1019
int git_reference_cmp(
	const git_reference *ref1,
	const git_reference *ref2)
1020
{
1021
	git_ref_t type1, type2;
1022 1023
	assert(ref1 && ref2);

1024 1025 1026
	type1 = git_reference_type(ref1);
	type2 = git_reference_type(ref2);

1027
	/* let's put symbolic refs before OIDs */
1028 1029
	if (type1 != type2)
		return (type1 == GIT_REF_SYMBOLIC) ? -1 : 1;
1030

1031
	if (type1 == GIT_REF_SYMBOLIC)
1032 1033
		return strcmp(ref1->target.symbolic, ref2->target.symbolic);

1034
	return git_oid__cmp(&ref1->target.oid, &ref2->target.oid);
1035 1036
}

1037 1038 1039 1040
static int reference__update_terminal(
	git_repository *repo,
	const char *ref_name,
	const git_oid *oid,
1041 1042 1043
	int nesting,
	const git_signature *signature,
	const char *log_message)
nulltoken committed
1044 1045
{
	git_reference *ref;
1046
	int error = 0;
nulltoken committed
1047

1048 1049
	if (nesting > MAX_NESTING_LEVEL) {
		giterr_set(GITERR_REFERENCE, "Reference chain too deep (%d)", nesting);
1050
		return GIT_ENOTFOUND;
1051
	}
1052

1053
	error = git_reference_lookup(&ref, repo, ref_name);
nulltoken committed
1054

1055 1056
	/* If we haven't found the reference at all, create a new reference. */
	if (error == GIT_ENOTFOUND) {
nulltoken committed
1057
		giterr_clear();
1058
		return git_reference_create(NULL, repo, ref_name, oid, 0, signature, log_message);
nulltoken committed
1059
	}
1060

1061 1062
	if (error < 0)
		return error;
1063

1064
	/* If the ref is a symbolic reference, follow its target. */
nulltoken committed
1065
	if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
1066
		error = reference__update_terminal(repo, git_reference_symbolic_target(ref), oid,
1067
			nesting+1, signature, log_message);
nulltoken committed
1068
		git_reference_free(ref);
1069
	} else {
Ben Straub committed
1070 1071 1072
		/* If we're not moving the target, don't recreate the ref */
		if (0 != git_oid_cmp(git_reference_target(ref), oid))
			error = git_reference_create(NULL, repo, ref_name, oid, 1, signature, log_message);
1073
		git_reference_free(ref);
nulltoken committed
1074
	}
1075

1076
	return error;
nulltoken committed
1077
}
1078

1079 1080 1081 1082 1083 1084 1085 1086
/*
 * 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,
1087 1088 1089
	const git_oid *oid,
	const git_signature *signature,
	const char *log_message)
1090
{
1091
	return reference__update_terminal(repo, ref_name, oid, 0, signature, log_message);
1092 1093
}

1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127
int git_reference__update_for_commit(
	git_repository *repo,
	git_reference *ref,
	const char *ref_name,
	const git_oid *id,
	const git_signature *committer,
	const char *operation)
{
	git_reference *ref_new = NULL;
	git_commit *commit = NULL;
	git_buf reflog_msg = GIT_BUF_INIT;
	int error;

	if ((error = git_commit_lookup(&commit, repo, id)) < 0 ||
		(error = git_buf_printf(&reflog_msg, "%s%s: %s",
			operation ? operation : "commit",
			git_commit_parentcount(commit) == 0 ? " (initial)" : "",
			git_commit_summary(commit))) < 0)
		goto done;

	if (ref)
		error = git_reference_set_target(
			&ref_new, ref, id, committer, git_buf_cstr(&reflog_msg));
	else
		error = git_reference__update_terminal(
			repo, ref_name, id, committer, git_buf_cstr(&reflog_msg));

done:
	git_reference_free(ref_new);
	git_buf_free(&reflog_msg);
	git_commit_free(commit);
	return error;
}

1128
int git_reference_has_log(git_repository *repo, const char *refname)
1129
{
1130 1131
	int error;
	git_refdb *refdb;
1132

1133
	assert(repo && refname);
1134

1135 1136
	if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
		return error;
1137

1138
	return git_refdb_has_log(refdb, refname);
1139
}
1140

1141 1142 1143 1144 1145 1146 1147 1148 1149
int git_reference_ensure_log(git_repository *repo, const char *refname)
{
	int error;
	git_refdb *refdb;

	assert(repo && refname);

	if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
		return error;
1150

1151
	return git_refdb_ensure_log(refdb, refname);
1152
}
1153

1154 1155 1156 1157 1158
int git_reference__is_branch(const char *ref_name)
{
	return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0;
}

1159
int git_reference_is_branch(const git_reference *ref)
1160 1161
{
	assert(ref);
1162
	return git_reference__is_branch(ref->name);
1163
}
1164

1165 1166 1167 1168 1169
int git_reference__is_remote(const char *ref_name)
{
	return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0;
}

Jacques Germishuys committed
1170
int git_reference_is_remote(const git_reference *ref)
1171 1172
{
	assert(ref);
1173
	return git_reference__is_remote(ref->name);
1174
}
1175

1176 1177 1178 1179 1180
int git_reference__is_tag(const char *ref_name)
{
	return git__prefixcmp(ref_name, GIT_REFS_TAGS_DIR) == 0;
}

Jacques Germishuys committed
1181
int git_reference_is_tag(const git_reference *ref)
1182 1183 1184 1185 1186
{
	assert(ref);
	return git_reference__is_tag(ref->name);
}

1187 1188 1189 1190 1191
int git_reference__is_note(const char *ref_name)
{
	return git__prefixcmp(ref_name, GIT_REFS_NOTES_DIR) == 0;
}

Jacques Germishuys committed
1192
int git_reference_is_note(const git_reference *ref)
1193 1194 1195 1196 1197
{
	assert(ref);
	return git_reference__is_note(ref->name);
}

1198 1199 1200 1201 1202 1203 1204 1205 1206
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(
1207 1208 1209
	git_object **peeled,
	git_reference *ref,
	git_otype target_type)
1210 1211 1212 1213 1214 1215 1216
{
	git_reference *resolved = NULL;
	git_object *target = NULL;
	int error;

	assert(ref);

1217 1218 1219 1220 1221 1222 1223
	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");
	}

1224
	if (!git_oid_iszero(&resolved->peel)) {
1225
		error = git_object_lookup(&target,
1226
			git_reference_owner(ref), &resolved->peel, GIT_OBJ_ANY);
1227 1228
	} else {
		error = git_object_lookup(&target,
1229
			git_reference_owner(ref), &resolved->target.oid, GIT_OBJ_ANY);
1230
	}
1231

1232
	if (error < 0) {
1233 1234 1235
		peel_error(error, ref, "Cannot retrieve reference target");
		goto cleanup;
	}
1236

1237
	if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG)
1238
		error = git_object_dup(peeled, target);
1239
	else
1240 1241 1242 1243
		error = git_object_peel(peeled, target, target_type);

cleanup:
	git_object_free(target);
1244 1245 1246 1247

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

1248 1249
	return error;
}
1250

1251
int git_reference__is_valid_name(const char *refname, unsigned int flags)
1252
{
1253 1254 1255 1256
	if (git_reference__normalize_name(NULL, refname, flags) < 0) {
		giterr_clear();
		return false;
	}
nulltoken committed
1257

1258
	return true;
1259 1260
}

1261
int git_reference_is_valid_name(const char *refname)
1262
{
1263
	return git_reference__is_valid_name(refname, GIT_REF_FORMAT_ALLOW_ONELEVEL);
1264
}
1265

Jacques Germishuys committed
1266
const char *git_reference_shorthand(const git_reference *ref)
1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281
{
	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;
}