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

8 9
#include "common.h"

10
#include "str.h"
11
#include "tree.h"
12
#include "refdb.h"
13
#include "regexp.h"
14
#include "date.h"
15

Ben Straub committed
16
#include "git2.h"
17

18
static int maybe_sha_or_abbrev(git_object **out, git_repository *repo, const char *spec, size_t speclen)
19 20 21 22 23 24
{
	git_oid oid;

	if (git_oid_fromstrn(&oid, spec, speclen) < 0)
		return GIT_ENOTFOUND;

25
	return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJECT_ANY);
26
}
27

28
static int maybe_sha(git_object **out, git_repository *repo, const char *spec)
29 30 31 32 33 34 35 36 37
{
	size_t speclen = strlen(spec);

	if (speclen != GIT_OID_HEXSZ)
		return GIT_ENOTFOUND;

	return maybe_sha_or_abbrev(out, repo, spec, speclen);
}

38
static int maybe_abbrev(git_object **out, git_repository *repo, const char *spec)
39 40 41 42 43 44
{
	size_t speclen = strlen(spec);

	return maybe_sha_or_abbrev(out, repo, spec, speclen);
}

45
static int build_regex(git_regexp *regex, const char *pattern)
46 47 48 49
{
	int error;

	if (*pattern == '\0') {
50
		git_error_set(GIT_ERROR_REGEX, "empty pattern");
51
		return GIT_EINVALIDSPEC;
52 53
	}

54
	error = git_regexp_compile(regex, pattern, 0);
55 56
	if (!error)
		return 0;
57

58
	git_regexp_dispose(regex);
59

60
	return error;
61 62
}

63
static int maybe_describe(git_object**out, git_repository *repo, const char *spec)
64
{
65
	const char *substr;
66
	int error;
67
	git_regexp regex;
68 69 70

	substr = strstr(spec, "-g");

71 72
	if (substr == NULL)
		return GIT_ENOTFOUND;
73

74 75
	if (build_regex(&regex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)
		return -1;
76

77 78
	error = git_regexp_match(&regex, spec);
	git_regexp_dispose(&regex);
79 80

	if (error)
81 82
		return GIT_ENOTFOUND;

83
	return maybe_abbrev(out, repo, substr+2);
84 85
}

86 87 88 89 90
static int revparse_lookup_object(
	git_object **object_out,
	git_reference **reference_out,
	git_repository *repo,
	const char *spec)
91 92 93 94
{
	int error;
	git_reference *ref;

95
	if ((error = maybe_sha(object_out, repo, spec)) != GIT_ENOTFOUND)
96 97
		return error;

98
	error = git_reference_dwim(&ref, repo, spec);
99
	if (!error) {
100 101

		error = git_object_lookup(
102
			object_out, repo, git_reference_target(ref), GIT_OBJECT_ANY);
103 104 105 106

		if (!error)
			*reference_out = ref;

107
		return error;
108 109
	}

110
	if (error != GIT_ENOTFOUND)
111
		return error;
112

113 114 115
	if ((strlen(spec) < GIT_OID_HEXSZ) &&
		((error = maybe_abbrev(object_out, repo, spec)) != GIT_ENOTFOUND))
			return error;
116

117
	if ((error = maybe_describe(object_out, repo, spec)) != GIT_ENOTFOUND)
118 119
		return error;

120
	git_error_set(GIT_ERROR_REFERENCE, "revspec '%s' not found", spec);
121
	return GIT_ENOTFOUND;
122 123
}

124
static int try_parse_numeric(int *n, const char *curly_braces_content)
Ben Straub committed
125
{
126
	int32_t content;
127 128
	const char *end_ptr;

129 130
	if (git__strntol32(&content, curly_braces_content, strlen(curly_braces_content),
			   &end_ptr, 10) < 0)
131
		return -1;
132

133 134
	if (*end_ptr != '\0')
		return -1;
135

136
	*n = (int)content;
137
	return 0;
Ben Straub committed
138 139
}

140
static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
141
{
142
	git_reference *ref = NULL;
143
	git_reflog *reflog = NULL;
144
	git_regexp preg;
145 146
	int error = -1;
	size_t i, numentries, cur;
147
	const git_reflog_entry *entry;
148
	const char *msg;
149
	git_str buf = GIT_STR_INIT;
150

151
	cur = position;
152

153
	if (*identifier != '\0' || *base_ref != NULL)
154
		return GIT_EINVALIDSPEC;
155

156 157
	if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0)
		return -1;
158

159 160
	if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0)
		goto cleanup;
161

162
	if (git_reflog_read(&reflog, repo, GIT_HEAD_FILE) < 0)
163 164 165 166
		goto cleanup;

	numentries  = git_reflog_entrycount(reflog);

167
	for (i = 0; i < numentries; i++) {
168 169
		git_regmatch regexmatches[2];

170
		entry = git_reflog_entry_byindex(reflog, i);
171
		msg = git_reflog_entry_message(entry);
172 173
		if (!msg)
			continue;
174

175
		if (git_regexp_search(&preg, msg, 2, regexmatches) < 0)
176 177 178 179 180 181
			continue;

		cur--;

		if (cur > 0)
			continue;
182

183
		if ((git_str_put(&buf, msg+regexmatches[1].start, regexmatches[1].end - regexmatches[1].start)) < 0)
184
			goto cleanup;
185

186
		if ((error = git_reference_dwim(base_ref, repo, git_str_cstr(&buf))) == 0)
187 188 189 190 191
			goto cleanup;

		if (error < 0 && error != GIT_ENOTFOUND)
			goto cleanup;

192
		error = maybe_abbrev(out, repo, git_str_cstr(&buf));
193 194 195

		goto cleanup;
	}
196

197 198 199 200
	error = GIT_ENOTFOUND;

cleanup:
	git_reference_free(ref);
201
	git_str_dispose(&buf);
202
	git_regexp_dispose(&preg);
203 204 205 206
	git_reflog_free(reflog);
	return error;
}

207
static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t identifier)
208 209
{
	git_reflog *reflog;
210
	size_t numentries;
211
	const git_reflog_entry *entry = NULL;
212 213
	bool search_by_pos = (identifier <= 100000000);

214
	if (git_reflog_read(&reflog, git_reference_owner(ref), git_reference_name(ref)) < 0)
215 216
		return -1;

217
	numentries = git_reflog_entrycount(reflog);
218 219

	if (search_by_pos) {
220 221
		if (numentries < identifier + 1)
			goto notfound;
222 223

		entry = git_reflog_entry_byindex(reflog, identifier);
224
		git_oid_cpy(oid, git_reflog_entry_id_new(entry));
225
	} else {
226
		size_t i;
227
		git_time commit_time;
228

229
		for (i = 0; i < numentries; i++) {
230 231
			entry = git_reflog_entry_byindex(reflog, i);
			commit_time = git_reflog_entry_committer(entry)->when;
232 233

			if (commit_time.time > (git_time_t)identifier)
234
				continue;
235

236
			git_oid_cpy(oid, git_reflog_entry_id_new(entry));
237
			break;
238 239
		}

240 241 242 243
		if (i == numentries) {
			if (entry == NULL)
				goto notfound;

244 245 246
			/*
			 * TODO: emit a warning (log for 'branch' only goes back to ...)
			 */
247 248
			git_oid_cpy(oid, git_reflog_entry_id_new(entry));
		}
249
	}
250

251
	git_reflog_free(reflog);
252 253 254
	return 0;

notfound:
255 256
	git_error_set(
		GIT_ERROR_REFERENCE,
257
		"reflog for '%s' has only %"PRIuZ" entries, asked for %"PRIuZ,
258 259 260 261
		git_reference_name(ref), numentries, identifier);

	git_reflog_free(reflog);
	return GIT_ENOTFOUND;
262
}
263

264
static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
265 266 267 268
{
	git_reference *ref;
	git_oid oid;
	int error = -1;
269

270
	if (*base_ref == NULL) {
271
		if ((error = git_reference_dwim(&ref, repo, identifier)) < 0)
272 273 274 275 276
			return error;
	} else {
		ref = *base_ref;
		*base_ref = NULL;
	}
277

278
	if (position == 0) {
279
		error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJECT_ANY);
280 281
		goto cleanup;
	}
282

283 284
	if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0)
		goto cleanup;
285

286
	error = git_object_lookup(out, repo, &oid, GIT_OBJECT_ANY);
287 288 289 290 291 292 293 294 295 296 297 298

cleanup:
	git_reference_free(ref);
	return error;
}

static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo)
{
	git_reference *tracking, *ref;
	int error = -1;

	if (*base_ref == NULL) {
299
		if ((error = git_reference_dwim(&ref, repo, identifier)) < 0)
300 301 302 303
			return error;
	} else {
		ref = *base_ref;
		*base_ref = NULL;
304 305
	}

306 307 308 309 310
	if (!git_reference_is_branch(ref)) {
		error = GIT_EINVALIDSPEC;
		goto cleanup;
	}

311
	if ((error = git_branch_upstream(&tracking, ref)) < 0)
312
		goto cleanup;
313

314 315
	*base_ref = tracking;

316
cleanup:
317 318 319 320
	git_reference_free(ref);
	return error;
}

321
static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, size_t identifier_len, git_repository *repo, const char *curly_braces_content)
322 323
{
	bool is_numeric;
324
	int parsed = 0, error = -1;
325
	git_str identifier = GIT_STR_INIT;
326 327
	git_time_t timestamp;

328
	GIT_ASSERT(*out == NULL);
329

330
	if (git_str_put(&identifier, spec, identifier_len) < 0)
331 332 333 334 335
		return -1;

	is_numeric = !try_parse_numeric(&parsed, curly_braces_content);

	if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) {
336
		error = GIT_EINVALIDSPEC;
337 338 339 340 341
		goto cleanup;
	}

	if (is_numeric) {
		if (parsed < 0)
342
			error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, git_str_cstr(&identifier), -parsed);
343
		else
344
			error = retrieve_revobject_from_reflog(out, ref, repo, git_str_cstr(&identifier), parsed);
345 346 347 348 349

		goto cleanup;
	}

	if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) {
350
		error = retrieve_remote_tracking_reference(ref, git_str_cstr(&identifier), repo);
351 352 353 354

		goto cleanup;
	}

355
	if (git_date_parse(&timestamp, curly_braces_content) < 0) {
356
		error = GIT_EINVALIDSPEC;
357
		goto cleanup;
358
	}
359

360
	error = retrieve_revobject_from_reflog(out, ref, repo, git_str_cstr(&identifier), (size_t)timestamp);
361 362

cleanup:
363
	git_str_dispose(&identifier);
364
	return error;
365 366
}

367
static git_object_t parse_obj_type(const char *str)
368
{
369
	if (!strcmp(str, "commit"))
370
		return GIT_OBJECT_COMMIT;
371 372

	if (!strcmp(str, "tree"))
373
		return GIT_OBJECT_TREE;
374

375
	if (!strcmp(str, "blob"))
376
		return GIT_OBJECT_BLOB;
377 378

	if (!strcmp(str, "tag"))
379
		return GIT_OBJECT_TAG;
380

381
	return GIT_OBJECT_INVALID;
382 383
}

384
static int dereference_to_non_tag(git_object **out, git_object *obj)
385
{
386
	if (git_object_type(obj) == GIT_OBJECT_TAG)
387
		return git_tag_peel(out, (git_tag *)obj);
388

389
	return git_object_dup(out, obj);
390
}
391

392 393 394 395
static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n)
{
	git_object *temp_commit = NULL;
	int error;
396

397
	if ((error = git_object_peel(&temp_commit, obj, GIT_OBJECT_COMMIT)) < 0)
398 399
		return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
			GIT_EINVALIDSPEC : error;
400 401

	if (n == 0) {
402
		*out = temp_commit;
403 404 405
		return 0;
	}

406
	error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1);
407

408 409
	git_object_free(temp_commit);
	return error;
410
}
411

412
static int handle_linear_syntax(git_object **out, git_object *obj, int n)
413
{
414 415
	git_object *temp_commit = NULL;
	int error;
416

417
	if ((error = git_object_peel(&temp_commit, obj, GIT_OBJECT_COMMIT)) < 0)
418 419
		return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
			GIT_EINVALIDSPEC : error;
420

421
	error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n);
422

423 424
	git_object_free(temp_commit);
	return error;
425 426
}

427 428
static int handle_colon_syntax(
	git_object **out,
429 430
	git_object *obj,
	const char *path)
431
{
432
	git_object *tree;
433 434
	int error = -1;
	git_tree_entry *entry = NULL;
435

436
	if ((error = git_object_peel(&tree, obj, GIT_OBJECT_TREE)) < 0)
437
		return error == GIT_ENOTFOUND ? GIT_EINVALIDSPEC : error;
438

439 440 441 442
	if (*path == '\0') {
		*out = tree;
		return 0;
	}
443

444 445 446 447 448 449 450
	/*
	 * TODO: Handle the relative path syntax
	 * (:./relative/path and :../relative/path)
	 */
	if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0)
		goto cleanup;

451
	error = git_tree_entry_to_object(out, git_object_owner(tree), entry);
452

453 454
cleanup:
	git_tree_entry_free(entry);
455
	git_object_free(tree);
456 457

	return error;
458 459
}

460
static int walk_and_search(git_object **out, git_revwalk *walk, git_regexp *regex)
461
{
462 463 464
	int error;
	git_oid oid;
	git_object *obj;
465

466
	while (!(error = git_revwalk_next(&oid, walk))) {
467

468
		error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJECT_COMMIT);
469
		if ((error < 0) && (error != GIT_ENOTFOUND))
470 471
			return -1;

472
		if (!git_regexp_match(regex, git_commit_message((git_commit*)obj))) {
473 474 475 476 477
			*out = obj;
			return 0;
		}

		git_object_free(obj);
478 479
	}

Russell Belfer committed
480
	if (error < 0 && error == GIT_ITEROVER)
481 482 483 484 485 486 487
		error = GIT_ENOTFOUND;

	return error;
}

static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern)
{
488
	git_regexp preg;
489
	git_revwalk *walk = NULL;
490
	int error;
491

492 493
	if ((error = build_regex(&preg, pattern)) < 0)
		return error;
494

495
	if ((error = git_revwalk_new(&walk, repo)) < 0)
496 497 498 499 500
		goto cleanup;

	git_revwalk_sorting(walk, GIT_SORT_TIME);

	if (spec_oid == NULL) {
501
		if ((error = git_revwalk_push_glob(walk, "refs/*")) < 0)
502
			goto cleanup;
503
	} else if ((error = git_revwalk_push(walk, spec_oid)) < 0)
504 505 506
			goto cleanup;

	error = walk_and_search(out, walk, &preg);
507

508
cleanup:
509
	git_regexp_dispose(&preg);
510 511 512 513 514 515 516
	git_revwalk_free(walk);

	return error;
}

static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content)
{
517
	git_object_t expected_type;
518 519 520 521 522 523 524 525 526

	if (*curly_braces_content == '\0')
		return dereference_to_non_tag(out, obj);

	if (*curly_braces_content == '/')
		return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1);

	expected_type = parse_obj_type(curly_braces_content);

527
	if (expected_type == GIT_OBJECT_INVALID)
528
		return GIT_EINVALIDSPEC;
529

530
	return git_object_peel(out, obj, expected_type);
531 532
}

533
static int extract_curly_braces_content(git_str *buf, const char *spec, size_t *pos)
534
{
535
	git_str_clear(buf);
536

537
	GIT_ASSERT_ARG(spec[*pos] == '^' || spec[*pos] == '@');
538 539 540 541

	(*pos)++;

	if (spec[*pos] == '\0' || spec[*pos] != '{')
542
		return GIT_EINVALIDSPEC;
543 544 545 546 547

	(*pos)++;

	while (spec[*pos] != '}') {
		if (spec[*pos] == '\0')
548
			return GIT_EINVALIDSPEC;
549

550
		if (git_str_putc(buf, spec[(*pos)++]) < 0)
551
			return -1;
552 553 554 555 556 557 558
	}

	(*pos)++;

	return 0;
}

559
static int extract_path(git_str *buf, const char *spec, size_t *pos)
560
{
561
	git_str_clear(buf);
562

563
	GIT_ASSERT_ARG(spec[*pos] == ':');
564 565 566

	(*pos)++;

567
	if (git_str_puts(buf, spec + *pos) < 0)
568 569
		return -1;

570
	*pos += git_str_len(buf);
571 572 573 574

	return 0;
}

575
static int extract_how_many(int *n, const char *spec, size_t *pos)
576 577 578 579 580
{
	const char *end_ptr;
	int parsed, accumulated;
	char kind = spec[*pos];

581
	GIT_ASSERT_ARG(spec[*pos] == '^' || spec[*pos] == '~');
582 583 584 585 586 587 588 589 590 591

	accumulated = 0;

	do {
		do {
			(*pos)++;
			accumulated++;
		} while (spec[(*pos)] == kind && kind == '~');

		if (git__isdigit(spec[*pos])) {
592
			if (git__strntol32(&parsed, spec + *pos, strlen(spec + *pos), &end_ptr, 10) < 0)
593
				return GIT_EINVALIDSPEC;
594 595 596

			accumulated += (parsed - 1);
			*pos = end_ptr - spec;
597
		}
598

599
	} while (spec[(*pos)] == kind && kind == '~');
600 601 602 603 604 605 606 607 608 609 610 611 612 613

	*n = accumulated;

	return 0;
}

static int object_from_reference(git_object **object, git_reference *reference)
{
	git_reference *resolved = NULL;
	int error;

	if (git_reference_resolve(&resolved, reference) < 0)
		return -1;

614
	error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJECT_ANY);
615 616 617 618 619
	git_reference_free(resolved);

	return error;
}

620
static int ensure_base_rev_loaded(git_object **object, git_reference **reference, const char *spec, size_t identifier_len, git_repository *repo, bool allow_empty_identifier)
621 622
{
	int error;
623
	git_str identifier = GIT_STR_INIT;
624 625 626 627

	if (*object != NULL)
		return 0;

628 629
	if (*reference != NULL)
		return object_from_reference(object, *reference);
630

631
	if (!allow_empty_identifier && identifier_len == 0)
632
		return GIT_EINVALIDSPEC;
633

634
	if (git_str_put(&identifier, spec, identifier_len) < 0)
635 636
		return -1;

637 638
	error = revparse_lookup_object(object, reference, repo, git_str_cstr(&identifier));
	git_str_dispose(&identifier);
639 640 641 642

	return error;
}

643
static int ensure_base_rev_is_not_known_yet(git_object *object)
644 645 646 647
{
	if (object == NULL)
		return 0;

648
	return GIT_EINVALIDSPEC;
649 650
}

651
static bool any_left_hand_identifier(git_object *object, git_reference *reference, size_t identifier_len)
652 653 654 655 656 657 658 659 660 661 662 663 664
{
	if (object != NULL)
		return true;

	if (reference != NULL)
		return true;

	if (identifier_len > 0)
		return true;

	return false;
}

665
static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference)
666
{
667
	if (!ensure_base_rev_is_not_known_yet(object) && reference == NULL)
668 669
		return 0;

670
	return GIT_EINVALIDSPEC;
671 672
}

673
static int revparse(
674 675
	git_object **object_out,
	git_reference **reference_out,
Russell Belfer committed
676
	size_t *identifier_len_out,
677 678
	git_repository *repo,
	const char *spec)
679
{
680
	size_t pos = 0, identifier_len = 0;
681
	int error = -1, n;
682
	git_str buf = GIT_STR_INIT;
683 684 685

	git_reference *reference = NULL;
	git_object *base_rev = NULL;
686

687 688
	bool should_return_reference = true;

689 690 691 692
	GIT_ASSERT_ARG(object_out);
	GIT_ASSERT_ARG(reference_out);
	GIT_ASSERT_ARG(repo);
	GIT_ASSERT_ARG(spec);
693

694 695
	*object_out = NULL;
	*reference_out = NULL;
696

697
	while (spec[pos]) {
698 699
		switch (spec[pos]) {
		case '^':
700 701
			should_return_reference = false;

702
			if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
703
				goto cleanup;
704

705 706
			if (spec[pos+1] == '{') {
				git_object *temp_object = NULL;
707

708 709
				if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
					goto cleanup;
710

711
				if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_str_cstr(&buf))) < 0)
712 713 714 715
					goto cleanup;

				git_object_free(base_rev);
				base_rev = temp_object;
716
			} else {
717 718 719 720 721 722 723 724 725 726
				git_object *temp_object = NULL;

				if ((error = extract_how_many(&n, spec, &pos)) < 0)
					goto cleanup;

				if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0)
					goto cleanup;

				git_object_free(base_rev);
				base_rev = temp_object;
727 728 729
			}
			break;

730 731 732 733
		case '~':
		{
			git_object *temp_object = NULL;

734 735
			should_return_reference = false;

736 737 738
			if ((error = extract_how_many(&n, spec, &pos)) < 0)
				goto cleanup;

739
			if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
740 741 742 743 744 745 746
				goto cleanup;

			if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0)
				goto cleanup;

			git_object_free(base_rev);
			base_rev = temp_object;
747
			break;
748
		}
749

750 751 752 753
		case ':':
		{
			git_object *temp_object = NULL;

754 755
			should_return_reference = false;

756 757 758 759
			if ((error = extract_path(&buf, spec, &pos)) < 0)
				goto cleanup;

			if (any_left_hand_identifier(base_rev, reference, identifier_len)) {
760
				if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0)
761 762
					goto cleanup;

763
				if ((error = handle_colon_syntax(&temp_object, base_rev, git_str_cstr(&buf))) < 0)
764
					goto cleanup;
765
			} else {
766 767
				if (*git_str_cstr(&buf) == '/') {
					if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_str_cstr(&buf) + 1)) < 0)
768 769 770 771 772 773 774
						goto cleanup;
				} else {

					/*
					 * TODO: support merge-stage path lookup (":2:Makefile")
					 * and plain index blob lookup (:i-am/a/blob)
					 */
775
					git_error_set(GIT_ERROR_INVALID, "unimplemented");
776 777 778
					error = GIT_ERROR;
					goto cleanup;
				}
779
			}
780 781 782

			git_object_free(base_rev);
			base_rev = temp_object;
783
			break;
784 785 786
		}

		case '@':
787 788
			if (spec[pos+1] == '{') {
				git_object *temp_object = NULL;
789

790 791
				if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
					goto cleanup;
792

793
				if ((error = ensure_base_rev_is_not_known_yet(base_rev)) < 0)
794
					goto cleanup;
795

796
				if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_str_cstr(&buf))) < 0)
797
					goto cleanup;
798

799 800 801
				if (temp_object != NULL)
					base_rev = temp_object;
				break;
802 803 804
			} else if (spec[pos+1] == '\0') {
				spec = "HEAD";
				break;
805
			}
806
			/* fall through */
807

808
		default:
809
			if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference)) < 0)
810 811 812 813
				goto cleanup;

			pos++;
			identifier_len++;
814
		}
815
	}
816

817
	if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
818 819
		goto cleanup;

820 821 822 823 824
	if (!should_return_reference) {
		git_reference_free(reference);
		reference = NULL;
	}

825 826 827
	*object_out = base_rev;
	*reference_out = reference;
	*identifier_len_out = identifier_len;
828
	error = 0;
829

830
cleanup:
831 832
	if (error) {
		if (error == GIT_EINVALIDSPEC)
833
			git_error_set(GIT_ERROR_INVALID,
834
				"failed to parse revision specifier - Invalid pattern '%s'", spec);
835

836
		git_object_free(base_rev);
837
		git_reference_free(reference);
838
	}
839

840
	git_str_dispose(&buf);
841
	return error;
842
}
843

844 845 846 847 848 849
int git_revparse_ext(
	git_object **object_out,
	git_reference **reference_out,
	git_repository *repo,
	const char *spec)
{
Russell Belfer committed
850 851
	int error;
	size_t identifier_len;
852 853 854
	git_object *obj = NULL;
	git_reference *ref = NULL;

855
	if ((error = revparse(&obj, &ref, &identifier_len, repo, spec)) < 0)
856 857 858 859
		goto cleanup;

	*object_out = obj;
	*reference_out = ref;
Russell Belfer committed
860
	GIT_UNUSED(identifier_len);
861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891

	return 0;

cleanup:
	git_object_free(obj);
	git_reference_free(ref);
	return error;
}

int git_revparse_single(git_object **out, git_repository *repo, const char *spec)
{
	int error;
	git_object *obj = NULL;
	git_reference *ref = NULL;

	*out = NULL;

	if ((error = git_revparse_ext(&obj, &ref, repo, spec)) < 0)
		goto cleanup;

	git_reference_free(ref);

	*out = obj;

	return 0;

cleanup:
	git_object_free(obj);
	git_reference_free(ref);
	return error;
}
892 893

int git_revparse(
894
	git_revspec *revspec,
Vicent Marti committed
895 896
	git_repository *repo,
	const char *spec)
897
{
898
	const char *dotdot;
899 900
	int error = 0;

901 902 903
	GIT_ASSERT_ARG(revspec);
	GIT_ASSERT_ARG(repo);
	GIT_ASSERT_ARG(spec);
Vicent Marti committed
904

905
	memset(revspec, 0x0, sizeof(*revspec));
906 907 908 909

	if ((dotdot = strstr(spec, "..")) != NULL) {
		char *lstr;
		const char *rstr;
910
		revspec->flags = GIT_REVSPEC_RANGE;
911

912 913 914 915 916 917 918
		/*
		 * Following git.git, don't allow '..' because it makes command line
		 * arguments which can be either paths or revisions ambiguous when the
		 * path is almost certainly intended. The empty range '...' is still
		 * allowed.
		 */
		if (!git__strcmp(spec, "..")) {
919
			git_error_set(GIT_ERROR_INVALID, "Invalid pattern '..'");
920 921 922
			return GIT_EINVALIDSPEC;
		}

Vicent Marti committed
923
		lstr = git__substrdup(spec, dotdot - spec);
924 925
		rstr = dotdot + 2;
		if (dotdot[2] == '.') {
926
			revspec->flags |= GIT_REVSPEC_MERGE_BASE;
927 928 929
			rstr++;
		}

930 931 932 933 934 935 936 937 938 939 940
		error = git_revparse_single(
			&revspec->from,
			repo,
			*lstr == '\0' ? "HEAD" : lstr);

		if (!error) {
			error = git_revparse_single(
				&revspec->to,
				repo,
				*rstr == '\0' ? "HEAD" : rstr);
		}
941 942

		git__free((void*)lstr);
943
	} else {
944
		revspec->flags = GIT_REVSPEC_SINGLE;
945
		error = git_revparse_single(&revspec->from, repo, spec);
946 947 948 949
	}

	return error;
}