refs.c 30.9 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
 */

#include "refs.h"
9

10 11
#include "hash.h"
#include "repository.h"
12
#include "futils.h"
13
#include "filebuf.h"
14
#include "pack.h"
15
#include "reflog.h"
16
#include "refdb.h"
17

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

28 29
bool git_reference__enable_symbolic_ref_target_validation = true;

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 = NULL;
	size_t namelen = strlen(name), reflen;
39

40 41 42 43
	if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) &&
		!GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) &&
		(ref = git__calloc(1, reflen)) != NULL)
		memcpy(ref->name, name, namelen + 1);
44 45 46 47 48

	return ref;
}

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

Edward Thomson committed
53 54
	GIT_ASSERT_ARG_WITH_RETVAL(name, NULL);
	GIT_ASSERT_ARG_WITH_RETVAL(target, NULL);
55

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

60
	ref->type = GIT_REFERENCE_SYMBOLIC;
Vicent Marti committed
61

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

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

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

Edward Thomson committed
77 78
	GIT_ASSERT_ARG_WITH_RETVAL(name, NULL);
	GIT_ASSERT_ARG_WITH_RETVAL(oid, NULL);
79

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

84
	ref->type = GIT_REFERENCE_DIRECT;
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
git_reference *git_reference__realloc(
	git_reference **ptr_to_ref, const char *name)
95
{
96
	size_t namelen, reflen;
97 98
	git_reference *rewrite = NULL;

Edward Thomson committed
99 100
	GIT_ASSERT_ARG_WITH_RETVAL(ptr_to_ref, NULL);
	GIT_ASSERT_ARG_WITH_RETVAL(name, NULL);
101 102 103

	namelen = strlen(name);

104 105
	if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) &&
		!GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) &&
106
		(rewrite = git__realloc(*ptr_to_ref, reflen)) != NULL)
107
		memcpy(rewrite->name, name, namelen + 1);
108

109 110
	*ptr_to_ref = NULL;

111 112 113
	return rewrite;
}

114 115
int git_reference_dup(git_reference **dest, git_reference *source)
{
116
	if (source->type == GIT_REFERENCE_SYMBOLIC)
117 118 119 120
		*dest = git_reference__alloc_symbolic(source->name, source->target.symbolic);
	else
		*dest = git_reference__alloc(source->name, &source->target.oid, &source->peel);

121
	GIT_ERROR_CHECK_ALLOC(*dest);
122

123 124 125
	(*dest)->db = source->db;
	GIT_REFCOUNT_INC((*dest)->db);

126 127 128
	return 0;
}

129
void git_reference_free(git_reference *reference)
130
{
131 132
	if (reference == NULL)
		return;
133

134
	if (reference->type == GIT_REFERENCE_SYMBOLIC)
135
		git__free(reference->target.symbolic);
136

Vicent Marti committed
137 138
	if (reference->db)
		GIT_REFCOUNT_DEC(reference->db, git_refdb__free);
139 140

	git__free(reference);
141
}
142

143
int git_reference_delete(git_reference *ref)
144
{
145 146 147
	const git_oid *old_id = NULL;
	const char *old_target = NULL;

148 149 150 151 152
	if (!strcmp(ref->name, "HEAD")) {
		git_error_set(GIT_ERROR_REFERENCE, "cannot delete HEAD");
		return GIT_ERROR;
	}

153
	if (ref->type == GIT_REFERENCE_DIRECT)
154 155 156 157 158
		old_id = &ref->target.oid;
	else
		old_target = ref->target.symbolic;

	return git_refdb_delete(ref->db, ref->name, old_id, old_target);
159 160
}

161 162 163 164 165 166 167 168 169 170 171
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);
}

172
int git_reference_lookup(git_reference **ref_out,
173
	git_repository *repo, const char *name)
174
{
175 176 177
	return git_reference_lookup_resolved(ref_out, repo, name, 0);
}

178
int git_reference_name_to_id(
179 180 181 182 183 184 185 186
	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;

187
	git_oid_cpy(out, git_reference_target(ref));
188 189 190 191
	git_reference_free(ref);
	return 0;
}

192
static int reference_normalize_for_repo(
193
	git_refname_t out,
194
	git_repository *repo,
195 196
	const char *name,
	bool validate)
197 198
{
	int precompose;
199
	unsigned int flags = GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL;
200

201
	if (!git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) &&
202
		precompose)
203
		flags |= GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE;
204

205
	if (!validate)
206
		flags |= GIT_REFERENCE_FORMAT__VALIDATION_DISABLE;
207

208
	return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags);
209 210
}

211 212 213 214 215 216
int git_reference_lookup_resolved(
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	int max_nesting)
{
217
	git_refname_t normalized;
218
	git_refdb *refdb;
219
	int error = 0;
220

Edward Thomson committed
221 222 223
	GIT_ASSERT_ARG(ref_out);
	GIT_ASSERT_ARG(repo);
	GIT_ASSERT_ARG(name);
224

225 226 227
	if ((error = reference_normalize_for_repo(normalized, repo, name, true)) < 0 ||
	    (error = git_repository_refdb__weakptr(&refdb, repo)) < 0 ||
	    (error = git_refdb_resolve(ref_out, refdb, normalized, max_nesting)) < 0)
228
		return error;
229

230 231 232 233 234 235 236 237 238 239 240 241
	/*
	 * The resolved reference may be a symbolic reference in case its
	 * target doesn't exist. If the user asked us to resolve (e.g.
	 * `max_nesting != 0`), then we need to return an error in case we got
	 * a symbolic reference back.
	 */
	if (max_nesting && git_reference_type(*ref_out) == GIT_REFERENCE_SYMBOLIC) {
		git_reference_free(*ref_out);
		*ref_out = NULL;
		return GIT_ENOTFOUND;
	}

242
	return 0;
243 244
}

245 246
int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname)
{
247
	int error = 0, i, valid;
248 249 250 251
	bool fallbackmode = true, foundvalid = false;
	git_reference *ref;
	git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_INIT;

252
	static const char *formatters[] = {
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
		"%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);

273 274
		if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0 ||
		    (error = git_reference_name_is_valid(&valid, git_buf_cstr(&refnamebuf))) < 0)
275 276
			goto cleanup;

277
		if (!valid) {
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
			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 */
298
		git_error_set(GIT_ERROR_REFERENCE,
299
			"could not use '%s' as valid reference name", git_buf_cstr(&name));
300 301
	}

302
	if (error == GIT_ENOTFOUND)
303
		git_error_set(GIT_ERROR_REFERENCE, "no reference found for shorthand '%s'", refname);
304

305 306
	git_buf_dispose(&name);
	git_buf_dispose(&refnamebuf);
307 308 309
	return error;
}

310 311 312
/**
 * Getters
 */
313
git_reference_t git_reference_type(const git_reference *ref)
Vicent Marti committed
314
{
Edward Thomson committed
315
	GIT_ASSERT_ARG(ref);
316
	return ref->type;
Vicent Marti committed
317 318
}

319
const char *git_reference_name(const git_reference *ref)
Vicent Marti committed
320
{
Edward Thomson committed
321
	GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL);
322
	return ref->name;
Vicent Marti committed
323 324
}

325
git_repository *git_reference_owner(const git_reference *ref)
326
{
Edward Thomson committed
327
	GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL);
328
	return ref->db->repo;
329 330
}

331
const git_oid *git_reference_target(const git_reference *ref)
Vicent Marti committed
332
{
Edward Thomson committed
333
	GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL);
Vicent Marti committed
334

335
	if (ref->type != GIT_REFERENCE_DIRECT)
Vicent Marti committed
336 337
		return NULL;

338
	return &ref->target.oid;
339 340 341 342
}

const git_oid *git_reference_target_peel(const git_reference *ref)
{
Edward Thomson committed
343
	GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL);
344

345
	if (ref->type != GIT_REFERENCE_DIRECT || git_oid_is_zero(&ref->peel))
346 347
		return NULL;

348
	return &ref->peel;
Vicent Marti committed
349 350
}

351
const char *git_reference_symbolic_target(const git_reference *ref)
352
{
Edward Thomson committed
353
	GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL);
354

355
	if (ref->type != GIT_REFERENCE_SYMBOLIC)
356 357
		return NULL;

358
	return ref->target.symbolic;
359 360
}

361
static int reference__create(
362 363 364
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
365 366
	const git_oid *oid,
	const char *symbolic,
367 368
	int force,
	const git_signature *signature,
369
	const char *log_message,
370 371
	const git_oid *old_id,
	const char *old_target)
372
{
373
	git_refname_t normalized;
374
	git_refdb *refdb;
375
	git_reference *ref = NULL;
376
	int error = 0;
377

Edward Thomson committed
378 379 380
	GIT_ASSERT_ARG(repo);
	GIT_ASSERT_ARG(name);
	GIT_ASSERT_ARG(symbolic || signature);
381

382 383 384
	if (ref_out)
		*ref_out = NULL;

385
	error = reference_normalize_for_repo(normalized, repo, name, true);
Vicent Marti committed
386 387 388 389 390
	if (error < 0)
		return error;

	error = git_repository_refdb__weakptr(&refdb, repo);
	if (error < 0)
391
		return error;
392 393

	if (oid != NULL) {
Edward Thomson committed
394
		GIT_ASSERT(symbolic == NULL);
395

396
		if (!git_object__is_valid(repo, oid, GIT_OBJECT_ANY)) {
397
			git_error_set(GIT_ERROR_REFERENCE,
398
				"target OID for the reference doesn't exist on the repository");
399 400 401
			return -1;
		}

402
		ref = git_reference__alloc(normalized, oid, NULL);
403
	} else {
404
		git_refname_t normalized_target;
405

406 407
		error = reference_normalize_for_repo(normalized_target, repo,
			symbolic, git_reference__enable_symbolic_ref_target_validation);
408 409

		if (error < 0)
410 411
			return error;

412
		ref = git_reference__alloc_symbolic(normalized, normalized_target);
413
	}
414

415
	GIT_ERROR_CHECK_ALLOC(ref);
416

417
	if ((error = git_refdb_write(refdb, ref, force, signature, log_message, old_id, old_target)) < 0) {
418
		git_reference_free(ref);
419
		return error;
420
	}
421

422
	if (ref_out == NULL)
423
		git_reference_free(ref);
424
	else
425
		*ref_out = ref;
426

427
	return 0;
428 429
}

430
static int refs_configured_ident(git_signature **out, const git_repository *repo)
431 432 433 434 435 436 437 438
{
	if (repo->ident_name && repo->ident_email)
		return git_signature_now(out, repo->ident_name, repo->ident_email);

	/* if not configured let us fall-through to the next method  */
	return -1;
}

439
int git_reference__log_signature(git_signature **out, git_repository *repo)
440
{
441
	int error;
442
	git_signature *who;
443

444
	if(((error = refs_configured_ident(&who, repo)) < 0) &&
445
	   ((error = git_signature_default(&who, repo)) < 0) &&
446
	   ((error = git_signature_now(&who, "unknown", "unknown")) < 0))
447
		return error;
448

449 450
	*out = who;
	return 0;
451 452
}

453
int git_reference_create_matching(
454 455 456 457 458
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	const git_oid *id,
	int force,
459 460
	const git_oid *old_id,
	const char *log_message)
461

462
{
463 464
	int error;
	git_signature *who = NULL;
465

Edward Thomson committed
466
	GIT_ASSERT_ARG(id);
467

468 469
	if ((error = git_reference__log_signature(&who, repo)) < 0)
		return error;
470 471

	error = reference__create(
472
		ref_out, repo, name, id, NULL, force, who, log_message, old_id, NULL);
Vicent Marti committed
473

474 475
	git_signature_free(who);
	return error;
476
}
477

478 479 480 481 482 483 484 485
int git_reference_create(
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	const git_oid *id,
	int force,
	const char *log_message)
{
486
        return git_reference_create_matching(ref_out, repo, name, id, force, NULL, log_message);
487 488
}

489
int git_reference_symbolic_create_matching(
490 491 492 493 494
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	const char *target,
	int force,
495 496
	const char *old_target,
	const char *log_message)
497
{
498 499 500
	int error;
	git_signature *who = NULL;

Edward Thomson committed
501
	GIT_ASSERT_ARG(target);
502

503 504
	if ((error = git_reference__log_signature(&who, repo)) < 0)
		return error;
505

506
	error = reference__create(
507
		ref_out, repo, name, NULL, target, force, who, log_message, NULL, old_target);
508 509 510

	git_signature_free(who);
	return error;
511 512
}

513 514 515 516 517 518 519 520
int git_reference_symbolic_create(
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	const char *target,
	int force,
	const char *log_message)
{
521
	return git_reference_symbolic_create_matching(ref_out, repo, name, target, force, NULL, log_message);
522 523
}

524 525
static int ensure_is_an_updatable_direct_reference(git_reference *ref)
{
526
	if (ref->type == GIT_REFERENCE_DIRECT)
527
		return 0;
528

529
	git_error_set(GIT_ERROR_REFERENCE, "cannot set OID on symbolic reference");
530
	return -1;
531
}
532

533
int git_reference_set_target(
534 535
	git_reference **out,
	git_reference *ref,
536
	const git_oid *id,
537
	const char *log_message)
538 539
{
	int error;
540
	git_repository *repo;
541

Edward Thomson committed
542 543 544
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(ref);
	GIT_ASSERT_ARG(id);
545

546 547
	repo = ref->db->repo;

548 549 550
	if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0)
		return error;

551
	return git_reference_create_matching(out, repo, ref->name, id, 1, &ref->target.oid, log_message);
552 553
}

554 555
static int ensure_is_an_updatable_symbolic_reference(git_reference *ref)
{
556
	if (ref->type == GIT_REFERENCE_SYMBOLIC)
557 558
		return 0;

559
	git_error_set(GIT_ERROR_REFERENCE, "cannot set symbolic target on a direct reference");
560 561 562
	return -1;
}

563 564 565
int git_reference_symbolic_set_target(
	git_reference **out,
	git_reference *ref,
566 567
	const char *target,
	const char *log_message)
Vicent Marti committed
568
{
569 570
	int error;

Edward Thomson committed
571 572 573
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(ref);
	GIT_ASSERT_ARG(target);
574

575 576
	if ((error = ensure_is_an_updatable_symbolic_reference(ref)) < 0)
		return error;
577

578
	return git_reference_symbolic_create_matching(
579
		out, ref->db->repo, ref->name, target, 1, ref->target.symbolic, log_message);
Vicent Marti committed
580 581
}

582 583 584
typedef struct {
    const char *old_name;
    git_refname_t new_name;
585
} refs_update_head_payload;
586

587
static int refs_update_head(git_repository *worktree, void *_payload)
588
{
589 590
	refs_update_head_payload *payload = (refs_update_head_payload *)_payload;
	git_reference *head = NULL, *updated = NULL;
591 592
	int error;

593
	if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0)
594
		goto out;
595

596
	if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC ||
597
	    git__strcmp(git_reference_symbolic_target(head), payload->old_name) != 0)
598
		goto out;
599

600 601
	/* Update HEAD if it was pointing to the reference being renamed */
	if ((error = git_reference_symbolic_set_target(&updated, head, payload->new_name, NULL)) < 0) {
602
		git_error_set(GIT_ERROR_REFERENCE, "failed to update HEAD after renaming reference");
603 604 605 606
		goto out;
	}

out:
607
	git_reference_free(updated);
608 609 610 611
	git_reference_free(head);
	return error;
}

612 613 614 615
int git_reference_rename(
	git_reference **out,
	git_reference *ref,
	const char *new_name,
616 617
	int force,
	const char *log_message)
618
{
619
	refs_update_head_payload payload;
620
	git_signature *signature = NULL;
621
	git_repository *repo;
622
	int error;
623

Edward Thomson committed
624 625
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(ref);
626

627
	repo = git_reference_owner(ref);
628

629 630 631 632 633 634
	if ((error = git_reference__log_signature(&signature, repo)) < 0 ||
	    (error = reference_normalize_for_repo(payload.new_name, repo, new_name, true)) < 0 ||
	    (error = git_refdb_rename(out, ref->db, ref->name, payload.new_name, force, signature, log_message)) < 0)
		goto out;

	payload.old_name = ref->name;
635

636 637 638 639 640 641
	/* We may have to update any HEAD that was pointing to the renamed reference. */
	if ((error = git_repository_foreach_worktree(repo, refs_update_head, &payload)) < 0)
		goto out;

out:
	git_signature_free(signature);
642 643 644
	return error;
}

645
int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
Vicent Marti committed
646
{
647
	switch (git_reference_type(ref)) {
648
	case GIT_REFERENCE_DIRECT:
649
		return git_reference_lookup(ref_out, ref->db->repo, ref->name);
650

651
	case GIT_REFERENCE_SYMBOLIC:
652 653 654
		return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1);

	default:
655
		git_error_set(GIT_ERROR_REFERENCE, "invalid reference");
656 657
		return -1;
	}
Vicent Marti committed
658 659
}

660 661
int git_reference_foreach(
	git_repository *repo,
662
	git_reference_foreach_cb callback,
663
	void *payload)
664
{
665
	git_reference_iterator *iter;
666
	git_reference *ref;
667 668
	int error;

669 670
	if ((error = git_reference_iterator_new(&iter, repo)) < 0)
		return error;
671

672 673
	while (!(error = git_reference_next(&ref, iter))) {
		if ((error = callback(ref, payload)) != 0) {
674
			git_error_set_after_callback(error);
675 676 677
			break;
		}
	}
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692

	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;
693 694
	int error;

695 696
	if ((error = git_reference_iterator_new(&iter, repo)) < 0)
		return error;
697

698 699
	while (!(error = git_reference_next_name(&refname, iter))) {
		if ((error = callback(refname, payload)) != 0) {
700
			git_error_set_after_callback(error);
701 702 703
			break;
		}
	}
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721

	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;

722 723
	if ((error = git_reference_iterator_glob_new(&iter, repo, glob)) < 0)
		return error;
724

725 726
	while (!(error = git_reference_next_name(&refname, iter))) {
		if ((error = callback(refname, payload)) != 0) {
727
			git_error_set_after_callback(error);
728 729 730
			break;
		}
	}
731 732 733 734 735 736

	if (error == GIT_ITEROVER)
		error = 0;

	git_reference_iterator_free(iter);
	return error;
737 738
}

739 740 741 742 743 744 745
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;

746
	return git_refdb_iterator(out, refdb, NULL);
747 748
}

749 750
int git_reference_iterator_glob_new(
	git_reference_iterator **out, git_repository *repo, const char *glob)
751
{
752
	git_refdb *refdb;
753

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

757
	return git_refdb_iterator(out, refdb, glob);
758 759
}

Vicent Marti committed
760
int git_reference_next(git_reference **out, git_reference_iterator *iter)
761
{
762 763 764 765 766 767
	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);
768 769 770 771
}

void git_reference_iterator_free(git_reference_iterator *iter)
{
772 773 774
	if (iter == NULL)
		return;

775
	git_refdb_iterator_free(iter);
776 777
}

778
static int cb__reflist_add(const char *ref, void *data)
779
{
780
	char *name = git__strdup(ref);
781
	GIT_ERROR_CHECK_ALLOC(name);
782
	return git_vector_insert((git_vector *)data, name);
783 784
}

785
int git_reference_list(
786
	git_strarray *array,
787
	git_repository *repo)
788 789 790
{
	git_vector ref_list;

Edward Thomson committed
791 792
	GIT_ASSERT_ARG(array);
	GIT_ASSERT_ARG(repo);
793 794 795 796

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

797
	if (git_vector_init(&ref_list, 8, NULL) < 0)
798
		return -1;
799

800
	if (git_reference_foreach_name(
801
			repo, &cb__reflist_add, (void *)&ref_list) < 0) {
802
		git_vector_free(&ref_list);
803
		return -1;
804 805
	}

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

808
	return 0;
809
}
Vicent Marti committed
810

811
static int is_valid_ref_char(char ch)
812
{
813
	if ((unsigned) ch <= ' ')
814
		return 0;
815 816 817 818 819 820 821 822

	switch (ch) {
	case '~':
	case '^':
	case ':':
	case '\\':
	case '?':
	case '[':
823
		return 0;
824
	default:
825
		return 1;
826 827 828
	}
}

829
static int ensure_segment_validity(const char *name, char may_contain_glob)
830
{
831 832
	const char *current = name;
	char prev = '\0';
833 834
	const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION);
	int segment_len;
835

836 837
	if (*current == '.')
		return -1; /* Refname starts with "." */
838

839 840 841
	for (current = name; ; current++) {
		if (*current == '\0' || *current == '/')
			break;
842

843 844
		if (!is_valid_ref_char(*current))
			return -1; /* Illegal character in refname */
845

846 847
		if (prev == '.' && *current == '.')
			return -1; /* Refname contains ".." */
848

849 850
		if (prev == '@' && *current == '{')
			return -1; /* Refname contains "@{" */
851

852 853 854 855 856 857
		if (*current == '*') {
			if (!may_contain_glob)
				return -1;
			may_contain_glob = 0;
		}

858 859
		prev = *current;
	}
860

861 862
	segment_len = (int)(current - name);

863
	/* A refname component can not end with ".lock" */
864
	if (segment_len >= lock_len &&
865
		!memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len))
866 867
			return -1;

868
	return segment_len;
869
}
870

871
static bool is_all_caps_and_underscore(const char *name, size_t len)
872
{
873
	size_t i;
874 875
	char c;

Edward Thomson committed
876 877
	GIT_ASSERT_ARG(name);
	GIT_ASSERT_ARG(len > 0);
878 879 880 881 882 883 884 885 886 887 888 889 890 891

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

892
/* Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 */
893 894 895 896 897
int git_reference__normalize_name(
	git_buf *buf,
	const char *name,
	unsigned int flags)
{
898
	const char *current;
899
	int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
900 901
	unsigned int process_flags;
	bool normalize = (buf != NULL);
902
	bool validate = (flags & GIT_REFERENCE_FORMAT__VALIDATION_DISABLE) == 0;
903 904

#ifdef GIT_USE_ICONV
905
	git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
906
#endif
907

Edward Thomson committed
908
	GIT_ASSERT_ARG(name);
909

910
	process_flags = flags;
911 912
	current = (char *)name;

913
	if (validate && *current == '/')
914 915
		goto cleanup;

916 917
	if (normalize)
		git_buf_clear(buf);
918

919
#ifdef GIT_USE_ICONV
920
	if ((flags & GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE) != 0) {
921 922 923 924
		size_t namelen = strlen(current);
		if ((error = git_path_iconv_init_precompose(&ic)) < 0 ||
			(error = git_path_iconv(&ic, &current, &namelen)) < 0)
			goto cleanup;
925
		error = GIT_EINVALIDSPEC;
926
	}
927
#endif
928

929 930 931 932 933 934 935
	if (!validate) {
		git_buf_sets(buf, current);

		error = git_buf_oom(buf) ? -1 : 0;
		goto cleanup;
	}

936
	while (true) {
937 938 939 940 941
		char may_contain_glob = process_flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN;

		segment_len = ensure_segment_validity(current, may_contain_glob);
		if (segment_len < 0)
			goto cleanup;
942

943
		if (segment_len > 0) {
944 945 946 947 948 949 950
			/*
			 * There may only be one glob in a pattern, thus we reset
			 * the pattern-flag in case the current segment has one.
			 */
			if (memchr(current, '*', segment_len))
				process_flags &= ~GIT_REFERENCE_FORMAT_REFSPEC_PATTERN;

951
			if (normalize) {
952
				size_t cur_len = git_buf_len(buf);
953

954
				git_buf_joinpath(buf, git_buf_cstr(buf), current);
955
				git_buf_truncate(buf,
956
					cur_len + segment_len + (segments_count ? 1 : 0));
957

958 959
				if (git_buf_oom(buf)) {
					error = -1;
960
					goto cleanup;
961
				}
962
			}
963

964
			segments_count++;
965
		}
966

967 968
		/* No empty segment is allowed when not normalizing */
		if (segment_len == 0 && !normalize)
969
			goto cleanup;
970

971 972
		if (current[segment_len] == '\0')
			break;
973

974
		current += segment_len + 1;
975
	}
976

977
	/* A refname can not be empty */
978
	if (segment_len == 0 && segments_count == 0)
979 980 981 982 983 984 985 986 987 988
		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;

989
	if ((segments_count == 1 ) && !(flags & GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL))
990 991 992
		goto cleanup;

	if ((segments_count == 1 ) &&
993
	    !(flags & GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND) &&
994
		!(is_all_caps_and_underscore(name, (size_t)segment_len) ||
995
			((flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))
996 997 998 999 1000
			goto cleanup;

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

1002
	error = 0;
1003

1004
cleanup:
1005
	if (error == GIT_EINVALIDSPEC)
1006 1007
		git_error_set(
			GIT_ERROR_REFERENCE,
1008
			"the given reference name '%s' is not valid", name);
1009

1010
	if (error && normalize)
1011
		git_buf_dispose(buf);
1012

1013
#ifdef GIT_USE_ICONV
1014
	git_path_iconv_clear(&ic);
1015
#endif
1016

1017 1018
	return error;
}
1019

1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
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) {
1033 1034
		git_error_set(
		GIT_ERROR_REFERENCE,
1035
		"the provided buffer is too short to hold the normalization of '%s'", name);
1036 1037 1038 1039
		error = GIT_EBUFS;
		goto cleanup;
	}

1040 1041
	if ((error = git_buf_copy_cstr(buffer_out, buffer_size, &buf)) < 0)
		goto cleanup;
1042 1043 1044 1045

	error = 0;

cleanup:
1046
	git_buf_dispose(&buf);
1047
	return error;
1048
}
1049

1050
#define GIT_REFERENCE_TYPEMASK (GIT_REFERENCE_DIRECT | GIT_REFERENCE_SYMBOLIC)
1051

Jacques Germishuys committed
1052 1053 1054
int git_reference_cmp(
	const git_reference *ref1,
	const git_reference *ref2)
1055
{
1056
	git_reference_t type1, type2;
Edward Thomson committed
1057 1058 1059

	GIT_ASSERT_ARG(ref1);
	GIT_ASSERT_ARG(ref2);
1060

1061 1062 1063
	type1 = git_reference_type(ref1);
	type2 = git_reference_type(ref2);

1064
	/* let's put symbolic refs before OIDs */
1065
	if (type1 != type2)
1066
		return (type1 == GIT_REFERENCE_SYMBOLIC) ? -1 : 1;
1067

1068
	if (type1 == GIT_REFERENCE_SYMBOLIC)
1069 1070
		return strcmp(ref1->target.symbolic, ref2->target.symbolic);

1071
	return git_oid__cmp(&ref1->target.oid, &ref2->target.oid);
1072 1073
}

1074 1075 1076 1077 1078 1079 1080 1081
/*
 * 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,
1082
	const git_oid *oid,
1083
	const git_signature *sig,
1084
	const char *log_message)
1085
{
1086
	git_reference *ref = NULL, *ref2 = NULL;
1087
	git_signature *who = NULL;
1088
	git_refdb *refdb = NULL;
1089 1090 1091 1092
	const git_signature *to_use;
	int error = 0;

	if (!sig && (error = git_reference__log_signature(&who, repo)) < 0)
1093
		goto out;
1094 1095 1096

	to_use = sig ? sig : who;

1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110
	if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
		goto out;

	if ((error = git_refdb_resolve(&ref, refdb, ref_name, -1)) < 0) {
		if (error == GIT_ENOTFOUND) {
			git_error_clear();
			error = reference__create(&ref2, repo, ref_name, oid, NULL, 0, to_use,
						  log_message, NULL, NULL);
		}
		goto out;
	}

	/* In case the resolved reference is symbolic, then it's a dangling symref. */
	if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) {
1111
		error = reference__create(&ref2, repo, ref->target.symbolic, oid, NULL, 0, to_use,
1112
					  log_message, NULL, NULL);
1113
	} else {
1114
		error = reference__create(&ref2, repo, ref->name, oid, NULL, 1, to_use,
1115 1116 1117
					  log_message, &ref->target.oid, NULL);
	}

1118
out:
1119 1120
	git_reference_free(ref2);
	git_reference_free(ref);
1121 1122
	git_signature_free(who);
	return error;
1123 1124
}

1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136
static const char *commit_type(const git_commit *commit)
{
	unsigned int count = git_commit_parentcount(commit);

	if (count >= 2)
		return " (merge)";
	else if (count == 0)
		return " (initial)";
	else
		return "";
}

1137 1138 1139 1140 1141 1142 1143 1144 1145 1146
int git_reference__update_for_commit(
	git_repository *repo,
	git_reference *ref,
	const char *ref_name,
	const git_oid *id,
	const char *operation)
{
	git_reference *ref_new = NULL;
	git_commit *commit = NULL;
	git_buf reflog_msg = GIT_BUF_INIT;
1147
	const git_signature *who;
1148 1149 1150 1151 1152
	int error;

	if ((error = git_commit_lookup(&commit, repo, id)) < 0 ||
		(error = git_buf_printf(&reflog_msg, "%s%s: %s",
			operation ? operation : "commit",
1153
			commit_type(commit),
1154 1155 1156
			git_commit_summary(commit))) < 0)
		goto done;

1157 1158 1159 1160 1161 1162 1163 1164 1165
	who = git_commit_committer(commit);

	if (ref) {
		if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0)
			return error;

		error = reference__create(&ref_new, repo, ref->name, id, NULL, 1, who,
					  git_buf_cstr(&reflog_msg), &ref->target.oid, NULL);
	}
1166 1167
	else
		error = git_reference__update_terminal(
1168
			repo, ref_name, id, who, git_buf_cstr(&reflog_msg));
1169 1170 1171

done:
	git_reference_free(ref_new);
1172
	git_buf_dispose(&reflog_msg);
1173 1174 1175 1176
	git_commit_free(commit);
	return error;
}

1177
int git_reference_has_log(git_repository *repo, const char *refname)
1178
{
1179 1180
	int error;
	git_refdb *refdb;
1181

Edward Thomson committed
1182 1183
	GIT_ASSERT_ARG(repo);
	GIT_ASSERT_ARG(refname);
1184

1185 1186
	if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
		return error;
1187

1188
	return git_refdb_has_log(refdb, refname);
1189
}
1190

1191 1192 1193 1194 1195
int git_reference_ensure_log(git_repository *repo, const char *refname)
{
	int error;
	git_refdb *refdb;

Edward Thomson committed
1196 1197
	GIT_ASSERT_ARG(repo);
	GIT_ASSERT_ARG(refname);
1198 1199 1200

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

1202
	return git_refdb_ensure_log(refdb, refname);
1203
}
1204

1205 1206 1207 1208 1209
int git_reference__is_branch(const char *ref_name)
{
	return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0;
}

1210
int git_reference_is_branch(const git_reference *ref)
1211
{
Edward Thomson committed
1212
	GIT_ASSERT_ARG(ref);
1213
	return git_reference__is_branch(ref->name);
1214
}
1215

1216 1217 1218 1219 1220
int git_reference__is_remote(const char *ref_name)
{
	return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0;
}

Jacques Germishuys committed
1221
int git_reference_is_remote(const git_reference *ref)
1222
{
Edward Thomson committed
1223
	GIT_ASSERT_ARG(ref);
1224
	return git_reference__is_remote(ref->name);
1225
}
1226

1227 1228 1229 1230 1231
int git_reference__is_tag(const char *ref_name)
{
	return git__prefixcmp(ref_name, GIT_REFS_TAGS_DIR) == 0;
}

Jacques Germishuys committed
1232
int git_reference_is_tag(const git_reference *ref)
1233
{
Edward Thomson committed
1234
	GIT_ASSERT_ARG(ref);
1235 1236 1237
	return git_reference__is_tag(ref->name);
}

1238 1239 1240 1241 1242
int git_reference__is_note(const char *ref_name)
{
	return git__prefixcmp(ref_name, GIT_REFS_NOTES_DIR) == 0;
}

Jacques Germishuys committed
1243
int git_reference_is_note(const git_reference *ref)
1244
{
Edward Thomson committed
1245
	GIT_ASSERT_ARG(ref);
1246 1247 1248
	return git_reference__is_note(ref->name);
}

1249
static int peel_error(int error, const git_reference *ref, const char *msg)
1250
{
1251 1252
	git_error_set(
		GIT_ERROR_INVALID,
1253
		"the reference '%s' cannot be peeled - %s", git_reference_name(ref), msg);
1254 1255 1256 1257
	return error;
}

int git_reference_peel(
1258
	git_object **peeled,
1259
	const git_reference *ref,
1260
	git_object_t target_type)
1261
{
1262 1263
	const git_reference *resolved = NULL;
	git_reference *allocated = NULL;
1264 1265 1266
	git_object *target = NULL;
	int error;

Edward Thomson committed
1267
	GIT_ASSERT_ARG(ref);
1268

1269
	if (ref->type == GIT_REFERENCE_DIRECT) {
1270 1271
		resolved = ref;
	} else {
1272
		if ((error = git_reference_resolve(&allocated, ref)) < 0)
1273
			return peel_error(error, ref, "Cannot resolve reference");
1274 1275

		resolved = allocated;
1276 1277
	}

1278 1279 1280 1281 1282 1283
	/*
	 * If we try to peel an object to a tag, we cannot use
	 * the fully peeled object, as that will always resolve
	 * to a commit. So we only want to use the peeled value
	 * if it is not zero and the target is not a tag.
	 */
1284
	if (target_type != GIT_OBJECT_TAG && !git_oid_is_zero(&resolved->peel)) {
1285
		error = git_object_lookup(&target,
1286
			git_reference_owner(ref), &resolved->peel, GIT_OBJECT_ANY);
1287 1288
	} else {
		error = git_object_lookup(&target,
1289
			git_reference_owner(ref), &resolved->target.oid, GIT_OBJECT_ANY);
1290
	}
1291

1292
	if (error < 0) {
1293 1294 1295
		peel_error(error, ref, "Cannot retrieve reference target");
		goto cleanup;
	}
1296

1297
	if (target_type == GIT_OBJECT_ANY && git_object_type(target) != GIT_OBJECT_TAG)
1298
		error = git_object_dup(peeled, target);
1299
	else
1300 1301 1302 1303
		error = git_object_peel(peeled, target, target_type);

cleanup:
	git_object_free(target);
1304
	git_reference_free(allocated);
1305

1306 1307
	return error;
}
1308

1309 1310 1311 1312
int git_reference__name_is_valid(
	int *valid,
	const char *refname,
	unsigned int flags)
1313
{
1314
	int error;
nulltoken committed
1315

1316 1317
	GIT_ASSERT(valid && refname);

1318 1319 1320 1321 1322 1323 1324 1325 1326 1327
	*valid = 0;

	error = git_reference__normalize_name(NULL, refname, flags);

	if (!error)
		*valid = 1;
	else if (error == GIT_EINVALIDSPEC)
		error = 0;

	return error;
1328 1329
}

1330 1331 1332 1333 1334
int git_reference_name_is_valid(int *valid, const char *refname)
{
	return git_reference__name_is_valid(valid, refname, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL);
}

1335
const char *git_reference__shorthand(const char *name)
1336 1337 1338 1339 1340 1341 1342 1343 1344 1345
{
	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);

Aaron Franke committed
1346
	/* No shorthands are available, so just return the name. */
1347 1348
	return name;
}
1349 1350 1351 1352 1353

const char *git_reference_shorthand(const git_reference *ref)
{
	return git_reference__shorthand(ref->name);
}
1354 1355 1356 1357 1358

int git_reference__is_unborn_head(bool *unborn, const git_reference *ref, git_repository *repo)
{
	int error;
	git_reference *tmp_ref;
Edward Thomson committed
1359 1360 1361 1362

	GIT_ASSERT_ARG(unborn);
	GIT_ASSERT_ARG(ref);
	GIT_ASSERT_ARG(repo);
1363

1364
	if (ref->type == GIT_REFERENCE_DIRECT) {
1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380
		*unborn = 0;
		return 0;
	}

	error = git_reference_lookup_resolved(&tmp_ref, repo, ref->name, -1);
	git_reference_free(tmp_ref);

	if (error != 0 && error != GIT_ENOTFOUND)
		return error;
	else if (error == GIT_ENOTFOUND && git__strcmp(ref->name, GIT_HEAD_FILE) == 0)
		*unborn = true;
	else
		*unborn = false;

	return 0;
}
1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395

/* Deprecated functions */

#ifndef GIT_DEPRECATE_HARD

int git_reference_is_valid_name(const char *refname)
{
	int valid = 0;

	git_reference__name_is_valid(&valid, refname, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL);

	return valid;
}

#endif