]> git.proxmox.com Git - libgit2.git/blame - src/revparse.c
Merge pull request #1203 from phkelley/reverse_dak
[libgit2.git] / src / revparse.c
CommitLineData
ac250c56
BS
1/*
2 * Copyright (C) 2009-2012 the libgit2 contributors
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7
8#include <assert.h>
9
10#include "common.h"
11#include "buffer.h"
244d2f6b 12#include "tree.h"
ac250c56 13
a346992f 14#include "git2.h"
ac250c56 15
e7279381 16static int disambiguate_refname(git_reference **out, git_repository *repo, const char *refname)
ac250c56 17{
e7279381 18 int error, i;
19 bool fallbackmode = true;
20 git_reference *ref;
21 git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_INIT;
22
e28dd29b 23 static const char* formatters[] = {
e7279381 24 "%s",
74a24005 25 GIT_REFS_DIR "%s",
26 GIT_REFS_TAGS_DIR "%s",
27 GIT_REFS_HEADS_DIR "%s",
28 GIT_REFS_REMOTES_DIR "%s",
29 GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE,
e28dd29b 30 NULL
31 };
e7279381 32
33 if (*refname)
34 git_buf_puts(&name, refname);
35 else {
b8748c12 36 git_buf_puts(&name, GIT_HEAD_FILE);
e7279381 37 fallbackmode = false;
38 }
39
40 for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) {
41
42 git_buf_clear(&refnamebuf);
43
44 if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0)
45 goto cleanup;
46
77e06d7e 47 if (!git_reference_is_valid_name(git_buf_cstr(&refnamebuf))) {
cc146626 48 error = GIT_EINVALIDSPEC;
77e06d7e 49 continue;
50 }
51
e7279381 52 error = git_reference_lookup_resolved(&ref, repo, git_buf_cstr(&refnamebuf), -1);
53
54 if (!error) {
55 *out = ref;
56 error = 0;
57 goto cleanup;
58 }
59
60 if (error != GIT_ENOTFOUND)
61 goto cleanup;
62 }
63
64cleanup:
65 git_buf_free(&name);
66 git_buf_free(&refnamebuf);
67 return error;
68}
69
8f17ed80 70static int maybe_sha_or_abbrev(git_object**out, git_repository *repo, const char *spec)
71{
72 git_oid oid;
73 size_t speclen = strlen(spec);
74
75 if (git_oid_fromstrn(&oid, spec, speclen) < 0)
76 return GIT_ENOTFOUND;
77
78 return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY);
79}
3e82d6c6 80
b8748c12 81static int build_regex(regex_t *regex, const char *pattern)
82{
83 int error;
84
85 if (*pattern == '\0') {
86 giterr_set(GITERR_REGEX, "Empty pattern");
cc146626 87 return GIT_EINVALIDSPEC;
b8748c12 88 }
89
90 error = regcomp(regex, pattern, REG_EXTENDED);
91 if (!error)
92 return 0;
93
cc146626 94 error = giterr_set_regex(regex, error);
95
b8748c12 96 regfree(regex);
97
cc146626 98 return error;
b8748c12 99}
100
3e82d6c6 101static int maybe_describe(git_object**out, git_repository *repo, const char *spec)
e7279381 102{
e28dd29b 103 const char *substr;
b8748c12 104 int error;
105 regex_t regex;
e28dd29b 106
e28dd29b 107 substr = strstr(spec, "-g");
e28dd29b 108
3e82d6c6 109 if (substr == NULL)
110 return GIT_ENOTFOUND;
111
b8748c12 112 if (build_regex(&regex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)
113 return -1;
3e82d6c6 114
b8748c12 115 error = regexec(&regex, spec, 0, NULL, 0);
116 regfree(&regex);
117
118 if (error)
3e82d6c6 119 return GIT_ENOTFOUND;
120
8f17ed80 121 return maybe_sha_or_abbrev(out, repo, substr+2);
3e82d6c6 122}
123
8f17ed80 124static int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec)
3e82d6c6 125{
126 int error;
127 git_reference *ref;
128
129 error = maybe_describe(out, repo, spec);
130 if (!error)
131 return 0;
132
133 if (error < 0 && error != GIT_ENOTFOUND)
134 return error;
135
e7279381 136 error = disambiguate_refname(&ref, repo, spec);
137 if (!error) {
2508cc66 138 error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJ_ANY);
e7279381 139 git_reference_free(ref);
b5f90115 140 return error;
e28dd29b 141 }
142
d1b7921a 143 if (error < 0 && error != GIT_ENOTFOUND)
144 return error;
145
146 error = maybe_sha_or_abbrev(out, repo, spec);
147 if (!error)
148 return 0;
149
3e82d6c6 150 if (error < 0 && error != GIT_ENOTFOUND)
e7279381 151 return error;
e28dd29b 152
153 giterr_set(GITERR_REFERENCE, "Refspec '%s' not found.", spec);
4de89ce7 154 return GIT_ENOTFOUND;
ac250c56
BS
155}
156
b8748c12 157static int try_parse_numeric(int *n, const char *curly_braces_content)
a346992f 158{
a8122b5d 159 int32_t content;
b8748c12 160 const char *end_ptr;
161
162 if (git__strtol32(&content, curly_braces_content, &end_ptr, 10) < 0)
163 return -1;
29f72aa6 164
b8748c12 165 if (*end_ptr != '\0')
166 return -1;
29f72aa6 167
a8122b5d 168 *n = (int)content;
b8748c12 169 return 0;
a346992f
BS
170}
171
cc146626 172static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
ac250c56 173{
b8748c12 174 git_reference *ref = NULL;
e28dd29b 175 git_reflog *reflog = NULL;
b8748c12 176 regex_t preg;
a8122b5d
RB
177 int error = -1;
178 size_t i, numentries, cur;
e28dd29b 179 const git_reflog_entry *entry;
b8748c12 180 const char *msg;
181 regmatch_t regexmatches[2];
e28dd29b 182 git_buf buf = GIT_BUF_INIT;
e28dd29b 183
b8748c12 184 cur = position;
e28dd29b 185
b8748c12 186 if (*identifier != '\0' || *base_ref != NULL)
cc146626 187 return GIT_EINVALIDSPEC;
e28dd29b 188
b8748c12 189 if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0)
190 return -1;
cab65c2b 191
b8748c12 192 if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0)
193 goto cleanup;
e28dd29b 194
b8748c12 195 if (git_reflog_read(&reflog, ref) < 0)
196 goto cleanup;
197
198 numentries = git_reflog_entrycount(reflog);
199
b15df1d9 200 for (i = 0; i < numentries; i++) {
b8748c12 201 entry = git_reflog_entry_byindex(reflog, i);
2508cc66 202 msg = git_reflog_entry_message(entry);
a8122b5d 203
b8748c12 204 if (regexec(&preg, msg, 2, regexmatches, 0))
205 continue;
206
207 cur--;
208
209 if (cur > 0)
210 continue;
a8122b5d 211
b8748c12 212 git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so);
213
214 if ((error = disambiguate_refname(base_ref, repo, git_buf_cstr(&buf))) == 0)
215 goto cleanup;
216
217 if (error < 0 && error != GIT_ENOTFOUND)
218 goto cleanup;
219
220 error = maybe_sha_or_abbrev(out, repo, git_buf_cstr(&buf));
221
222 goto cleanup;
223 }
a8122b5d 224
b8748c12 225 error = GIT_ENOTFOUND;
226
227cleanup:
228 git_reference_free(ref);
229 git_buf_free(&buf);
230 regfree(&preg);
231 git_reflog_free(reflog);
232 return error;
233}
234
a8122b5d 235static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t identifier)
b8748c12 236{
237 git_reflog *reflog;
238 int error = -1;
a8122b5d 239 size_t numentries;
b8748c12 240 const git_reflog_entry *entry;
241 bool search_by_pos = (identifier <= 100000000);
242
243 if (git_reflog_read(&reflog, ref) < 0)
244 return -1;
245
a8122b5d 246 numentries = git_reflog_entrycount(reflog);
b8748c12 247
248 if (search_by_pos) {
249 if (numentries < identifier + 1) {
250 giterr_set(
251 GITERR_REFERENCE,
a8122b5d
RB
252 "Reflog for '%s' has only "PRIuZ" entries, asked for "PRIuZ,
253 git_reference_name(ref), numentries, identifier);
b8748c12 254
255 error = GIT_ENOTFOUND;
256 goto cleanup;
e28dd29b 257 }
b8748c12 258
259 entry = git_reflog_entry_byindex(reflog, identifier);
2508cc66 260 git_oid_cpy(oid, git_reflog_entry_id_new(entry));
b8748c12 261 error = 0;
262 goto cleanup;
263
e28dd29b 264 } else {
a8122b5d 265 size_t i;
b8748c12 266 git_time commit_time;
e28dd29b 267
b15df1d9 268 for (i = 0; i < numentries; i++) {
b8748c12 269 entry = git_reflog_entry_byindex(reflog, i);
270 commit_time = git_reflog_entry_committer(entry)->when;
a8122b5d
RB
271
272 if (commit_time.time > (git_time_t)identifier)
b8748c12 273 continue;
e7279381 274
2508cc66 275 git_oid_cpy(oid, git_reflog_entry_id_new(entry));
b8748c12 276 error = 0;
e7279381 277 goto cleanup;
278 }
279
b8748c12 280 error = GIT_ENOTFOUND;
281 }
e28dd29b 282
b8748c12 283cleanup:
284 git_reflog_free(reflog);
285 return error;
286}
e28dd29b 287
a8122b5d 288static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
b8748c12 289{
290 git_reference *ref;
291 git_oid oid;
292 int error = -1;
e28dd29b 293
b8748c12 294 if (*base_ref == NULL) {
295 if ((error = disambiguate_refname(&ref, repo, identifier)) < 0)
296 return error;
297 } else {
298 ref = *base_ref;
299 *base_ref = NULL;
300 }
e28dd29b 301
b8748c12 302 if (position == 0) {
2508cc66 303 error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJ_ANY);
b8748c12 304 goto cleanup;
305 }
e28dd29b 306
b8748c12 307 if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0)
308 goto cleanup;
e28dd29b 309
b8748c12 310 error = git_object_lookup(out, repo, &oid, GIT_OBJ_ANY);
311
312cleanup:
313 git_reference_free(ref);
314 return error;
315}
316
317static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo)
318{
319 git_reference *tracking, *ref;
320 int error = -1;
321
322 if (*base_ref == NULL) {
323 if ((error = disambiguate_refname(&ref, repo, identifier)) < 0)
324 return error;
325 } else {
326 ref = *base_ref;
327 *base_ref = NULL;
e28dd29b 328 }
329
cc146626 330 if (!git_reference_is_branch(ref)) {
331 error = GIT_EINVALIDSPEC;
332 goto cleanup;
333 }
334
fb910281 335 if ((error = git_branch_tracking(&tracking, ref)) < 0)
b8748c12 336 goto cleanup;
337
338 *base_ref = tracking;
339
e7279381 340cleanup:
b8748c12 341 git_reference_free(ref);
342 return error;
343}
344
b8457baa 345static 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)
b8748c12 346{
347 bool is_numeric;
7e48635d 348 int parsed = 0, error = -1;
b8748c12 349 git_buf identifier = GIT_BUF_INIT;
350 git_time_t timestamp;
351
352 assert(*out == NULL);
353
354 if (git_buf_put(&identifier, spec, identifier_len) < 0)
355 return -1;
356
357 is_numeric = !try_parse_numeric(&parsed, curly_braces_content);
358
359 if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) {
cc146626 360 error = GIT_EINVALIDSPEC;
b8748c12 361 goto cleanup;
362 }
363
364 if (is_numeric) {
365 if (parsed < 0)
cc146626 366 error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, git_buf_cstr(&identifier), -parsed);
b8748c12 367 else
368 error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), parsed);
369
370 goto cleanup;
371 }
372
373 if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) {
374 error = retrieve_remote_tracking_reference(ref, git_buf_cstr(&identifier), repo);
375
376 goto cleanup;
377 }
378
379 if (git__date_parse(&timestamp, curly_braces_content) < 0)
380 goto cleanup;
381
a8122b5d 382 error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), (size_t)timestamp);
b8748c12 383
384cleanup:
385 git_buf_free(&identifier);
386 return error;
023c6f69
BS
387}
388
9d7bdf71
BS
389static git_otype parse_obj_type(const char *str)
390{
b8748c12 391 if (!strcmp(str, "commit"))
392 return GIT_OBJ_COMMIT;
393
394 if (!strcmp(str, "tree"))
395 return GIT_OBJ_TREE;
a8122b5d 396
b8748c12 397 if (!strcmp(str, "blob"))
398 return GIT_OBJ_BLOB;
399
400 if (!strcmp(str, "tag"))
401 return GIT_OBJ_TAG;
402
e28dd29b 403 return GIT_OBJ_BAD;
9d7bdf71
BS
404}
405
b8748c12 406static int dereference_to_non_tag(git_object **out, git_object *obj)
023c6f69 407{
b8748c12 408 if (git_object_type(obj) == GIT_OBJ_TAG)
409 return git_tag_peel(out, (git_tag *)obj);
e28dd29b 410
b8748c12 411 return git_object__dup(out, obj);
412}
e28dd29b 413
b8748c12 414static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n)
415{
416 git_object *temp_commit = NULL;
417 int error;
e28dd29b 418
cc146626 419 if ((error = git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT)) < 0)
420 return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
421 GIT_EINVALIDSPEC : error;
e28dd29b 422
e28dd29b 423 if (n == 0) {
b8748c12 424 *out = temp_commit;
e28dd29b 425 return 0;
426 }
427
b8748c12 428 error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1);
e28dd29b 429
b8748c12 430 git_object_free(temp_commit);
431 return error;
023c6f69 432}
ac250c56 433
b8748c12 434static int handle_linear_syntax(git_object **out, git_object *obj, int n)
38533d5a 435{
b8748c12 436 git_object *temp_commit = NULL;
437 int error;
e28dd29b 438
cc146626 439 if ((error = git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT)) < 0)
440 return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
441 GIT_EINVALIDSPEC : error;
e28dd29b 442
b8748c12 443 error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n);
e28dd29b 444
b8748c12 445 git_object_free(temp_commit);
446 return error;
38533d5a
BS
447}
448
b8748c12 449static int handle_colon_syntax(
450 git_object **out,
e28dd29b 451 git_object *obj,
452 const char *path)
244d2f6b 453{
b8748c12 454 git_object *tree;
bb89cf94 455 int error = -1;
456 git_tree_entry *entry = NULL;
244d2f6b 457
cc146626 458 if ((error = git_object_peel(&tree, obj, GIT_OBJ_TREE)) < 0)
459 return error == GIT_ENOTFOUND ? GIT_EINVALIDSPEC : error;
244d2f6b 460
b8748c12 461 if (*path == '\0') {
462 *out = tree;
463 return 0;
464 }
053b5096 465
bb89cf94 466 /*
467 * TODO: Handle the relative path syntax
468 * (:./relative/path and :../relative/path)
469 */
470 if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0)
471 goto cleanup;
472
b8748c12 473 error = git_tree_entry_to_object(out, git_object_owner(tree), entry);
053b5096 474
bb89cf94 475cleanup:
476 git_tree_entry_free(entry);
b8748c12 477 git_object_free(tree);
bb89cf94 478
479 return error;
244d2f6b
BS
480}
481
b8748c12 482static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex)
734efe4b 483{
b8748c12 484 int error;
485 git_oid oid;
486 git_object *obj;
487
488 while (!(error = git_revwalk_next(&oid, walk))) {
e28dd29b 489
b8748c12 490 if ((error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT) < 0) &&
491 (error != GIT_ENOTFOUND))
492 return -1;
493
494 if (!regexec(regex, git_commit_message((git_commit*)obj), 0, NULL, 0)) {
495 *out = obj;
496 return 0;
497 }
498
499 git_object_free(obj);
e28dd29b 500 }
501
f335ecd6 502 if (error < 0 && error == GIT_ITEROVER)
b8748c12 503 error = GIT_ENOTFOUND;
504
505 return error;
506}
507
508static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern)
509{
510 regex_t preg;
511 git_revwalk *walk = NULL;
cc146626 512 int error;
b8748c12 513
cc146626 514 if ((error = build_regex(&preg, pattern)) < 0)
515 return error;
b8748c12 516
cc146626 517 if ((error = git_revwalk_new(&walk, repo)) < 0)
b8748c12 518 goto cleanup;
519
520 git_revwalk_sorting(walk, GIT_SORT_TIME);
521
522 if (spec_oid == NULL) {
523 // TODO: @carlosmn: The glob should be refs/* but this makes git_revwalk_next() fails
cc146626 524 if ((error = git_revwalk_push_glob(walk, GIT_REFS_HEADS_DIR "*")) < 0)
b8748c12 525 goto cleanup;
cc146626 526 } else if ((error = git_revwalk_push(walk, spec_oid)) < 0)
b8748c12 527 goto cleanup;
528
529 error = walk_and_search(out, walk, &preg);
530
531cleanup:
532 regfree(&preg);
533 git_revwalk_free(walk);
534
535 return error;
536}
537
538static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content)
539{
540 git_otype expected_type;
541
542 if (*curly_braces_content == '\0')
543 return dereference_to_non_tag(out, obj);
544
545 if (*curly_braces_content == '/')
546 return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1);
547
548 expected_type = parse_obj_type(curly_braces_content);
549
550 if (expected_type == GIT_OBJ_BAD)
cc146626 551 return GIT_EINVALIDSPEC;
b8748c12 552
e2c81fca 553 return git_object_peel(out, obj, expected_type);
b8748c12 554}
555
b8457baa 556static int extract_curly_braces_content(git_buf *buf, const char *spec, size_t *pos)
b8748c12 557{
558 git_buf_clear(buf);
559
560 assert(spec[*pos] == '^' || spec[*pos] == '@');
561
562 (*pos)++;
563
564 if (spec[*pos] == '\0' || spec[*pos] != '{')
cc146626 565 return GIT_EINVALIDSPEC;
b8748c12 566
567 (*pos)++;
568
569 while (spec[*pos] != '}') {
570 if (spec[*pos] == '\0')
cc146626 571 return GIT_EINVALIDSPEC;
b8748c12 572
573 git_buf_putc(buf, spec[(*pos)++]);
574 }
575
576 (*pos)++;
577
578 return 0;
579}
580
b8457baa 581static int extract_path(git_buf *buf, const char *spec, size_t *pos)
b8748c12 582{
583 git_buf_clear(buf);
584
585 assert(spec[*pos] == ':');
586
587 (*pos)++;
588
589 if (git_buf_puts(buf, spec + *pos) < 0)
590 return -1;
591
592 *pos += git_buf_len(buf);
593
594 return 0;
595}
596
b8457baa 597static int extract_how_many(int *n, const char *spec, size_t *pos)
b8748c12 598{
599 const char *end_ptr;
600 int parsed, accumulated;
601 char kind = spec[*pos];
602
603 assert(spec[*pos] == '^' || spec[*pos] == '~');
604
605 accumulated = 0;
606
607 do {
608 do {
609 (*pos)++;
610 accumulated++;
611 } while (spec[(*pos)] == kind && kind == '~');
612
613 if (git__isdigit(spec[*pos])) {
614 if ((git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0) < 0)
cc146626 615 return GIT_EINVALIDSPEC;
b8748c12 616
617 accumulated += (parsed - 1);
618 *pos = end_ptr - spec;
e28dd29b 619 }
b8748c12 620
621 } while (spec[(*pos)] == kind && kind == '~');
622
623 *n = accumulated;
624
625 return 0;
626}
627
628static int object_from_reference(git_object **object, git_reference *reference)
629{
630 git_reference *resolved = NULL;
631 int error;
632
633 if (git_reference_resolve(&resolved, reference) < 0)
634 return -1;
635
2508cc66 636 error = git_object_lookup(object, reference->owner, git_reference_target(resolved), GIT_OBJ_ANY);
b8748c12 637 git_reference_free(resolved);
638
639 return error;
640}
641
b8457baa 642static 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)
b8748c12 643{
644 int error;
645 git_buf identifier = GIT_BUF_INIT;
646
647 if (*object != NULL)
648 return 0;
649
650 if (*reference != NULL) {
651 if ((error = object_from_reference(object, *reference)) < 0)
652 return error;
653
654 git_reference_free(*reference);
655 *reference = NULL;
656 return 0;
e28dd29b 657 }
658
b8748c12 659 if (!allow_empty_identifier && identifier_len == 0)
cc146626 660 return GIT_EINVALIDSPEC;
b8748c12 661
662 if (git_buf_put(&identifier, spec, identifier_len) < 0)
663 return -1;
664
665 error = revparse_lookup_object(object, repo, git_buf_cstr(&identifier));
666 git_buf_free(&identifier);
667
668 return error;
669}
670
cc146626 671static int ensure_base_rev_is_not_known_yet(git_object *object)
b8748c12 672{
673 if (object == NULL)
674 return 0;
675
cc146626 676 return GIT_EINVALIDSPEC;
b8748c12 677}
678
b8457baa 679static bool any_left_hand_identifier(git_object *object, git_reference *reference, size_t identifier_len)
b8748c12 680{
681 if (object != NULL)
682 return true;
683
684 if (reference != NULL)
685 return true;
686
687 if (identifier_len > 0)
688 return true;
689
690 return false;
691}
692
cc146626 693static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference)
b8748c12 694{
cc146626 695 if (!ensure_base_rev_is_not_known_yet(object) && reference == NULL)
b8748c12 696 return 0;
697
cc146626 698 return GIT_EINVALIDSPEC;
734efe4b
BS
699}
700
ac250c56
BS
701int git_revparse_single(git_object **out, git_repository *repo, const char *spec)
702{
b8457baa 703 size_t pos = 0, identifier_len = 0;
b8748c12 704 int error = -1, n;
705 git_buf buf = GIT_BUF_INIT;
706
707 git_reference *reference = NULL;
708 git_object *base_rev = NULL;
e28dd29b 709
710 assert(out && repo && spec);
711
b8748c12 712 *out = NULL;
e28dd29b 713
279b45b0 714 while (spec[pos]) {
b8748c12 715 switch (spec[pos]) {
716 case '^':
717 if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
718 goto cleanup;
e28dd29b 719
b8748c12 720 if (spec[pos+1] == '{') {
721 git_object *temp_object = NULL;
e28dd29b 722
b8748c12 723 if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
724 goto cleanup;
e28dd29b 725
b8748c12 726 if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
727 goto cleanup;
728
729 git_object_free(base_rev);
730 base_rev = temp_object;
e28dd29b 731 } else {
b8748c12 732 git_object *temp_object = NULL;
733
734 if ((error = extract_how_many(&n, spec, &pos)) < 0)
735 goto cleanup;
736
737 if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0)
738 goto cleanup;
739
740 git_object_free(base_rev);
741 base_rev = temp_object;
e28dd29b 742 }
e28dd29b 743 break;
744
b8748c12 745 case '~':
746 {
747 git_object *temp_object = NULL;
748
749 if ((error = extract_how_many(&n, spec, &pos)) < 0)
750 goto cleanup;
751
752 if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
753 goto cleanup;
754
755 if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0)
756 goto cleanup;
757
758 git_object_free(base_rev);
759 base_rev = temp_object;
e28dd29b 760 break;
b8748c12 761 }
e28dd29b 762
b8748c12 763 case ':':
764 {
765 git_object *temp_object = NULL;
766
767 if ((error = extract_path(&buf, spec, &pos)) < 0)
768 goto cleanup;
769
770 if (any_left_hand_identifier(base_rev, reference, identifier_len)) {
771 if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0)
772 goto cleanup;
773
774 if ((error = handle_colon_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
775 goto cleanup;
e28dd29b 776 } else {
b8748c12 777 if (*git_buf_cstr(&buf) == '/') {
778 if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_buf_cstr(&buf) + 1)) < 0)
779 goto cleanup;
780 } else {
781
782 /*
783 * TODO: support merge-stage path lookup (":2:Makefile")
784 * and plain index blob lookup (:i-am/a/blob)
785 */
786 giterr_set(GITERR_INVALID, "Unimplemented");
787 error = GIT_ERROR;
788 goto cleanup;
789 }
e28dd29b 790 }
b8748c12 791
792 git_object_free(base_rev);
793 base_rev = temp_object;
e28dd29b 794 break;
b8748c12 795 }
796
797 case '@':
798 {
5912d74c 799 if (spec[pos+1] == '{') {
800 git_object *temp_object = NULL;
e28dd29b 801
5912d74c 802 if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
803 goto cleanup;
b8748c12 804
cc146626 805 if ((error = ensure_base_rev_is_not_known_yet(base_rev)) < 0)
5912d74c 806 goto cleanup;
b8748c12 807
5912d74c 808 if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_buf_cstr(&buf))) < 0)
809 goto cleanup;
b8748c12 810
5912d74c 811 if (temp_object != NULL)
812 base_rev = temp_object;
813 break;
814 } else {
815 /* Fall through */
816 }
e28dd29b 817 }
818
b8748c12 819 default:
cc146626 820 if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference)) < 0)
b8748c12 821 goto cleanup;
822
823 pos++;
824 identifier_len++;
e28dd29b 825 }
279b45b0 826 }
e28dd29b 827
279b45b0 828 if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
b8748c12 829 goto cleanup;
830
831 *out = base_rev;
832 error = 0;
e28dd29b 833
b8748c12 834cleanup:
cc146626 835 if (error) {
836 if (error == GIT_EINVALIDSPEC)
837 giterr_set(GITERR_INVALID,
838 "Failed to parse revision specifier - Invalid pattern '%s'", spec);
839
b8748c12 840 git_object_free(base_rev);
cc146626 841 }
b8748c12 842 git_reference_free(reference);
843 git_buf_free(&buf);
844 return error;
ac250c56 845}