]> git.proxmox.com Git - libgit2.git/blame - src/revparse.c
New upstream version 1.3.0+dfsg.1
[libgit2.git] / src / revparse.c
CommitLineData
ac250c56 1/*
359fc2d2 2 * Copyright (C) the libgit2 contributors. All rights reserved.
ac250c56
BS
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
eae0bfdc
PP
8#include "common.h"
9
ac250c56 10#include "buffer.h"
244d2f6b 11#include "tree.h"
d00d5464 12#include "refdb.h"
22a2d3d5 13#include "regexp.h"
ac250c56 14
a346992f 15#include "git2.h"
ac250c56 16
c25aa7cd 17static int maybe_sha_or_abbrev(git_object **out, git_repository *repo, const char *spec, size_t speclen)
8f17ed80 18{
19 git_oid oid;
8f17ed80 20
21 if (git_oid_fromstrn(&oid, spec, speclen) < 0)
22 return GIT_ENOTFOUND;
23
ac3d33df 24 return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJECT_ANY);
8f17ed80 25}
3e82d6c6 26
c25aa7cd 27static int maybe_sha(git_object **out, git_repository *repo, const char *spec)
0e8e5a61 28{
29 size_t speclen = strlen(spec);
30
31 if (speclen != GIT_OID_HEXSZ)
32 return GIT_ENOTFOUND;
33
34 return maybe_sha_or_abbrev(out, repo, spec, speclen);
35}
36
c25aa7cd 37static int maybe_abbrev(git_object **out, git_repository *repo, const char *spec)
0e8e5a61 38{
39 size_t speclen = strlen(spec);
40
41 return maybe_sha_or_abbrev(out, repo, spec, speclen);
42}
43
22a2d3d5 44static int build_regex(git_regexp *regex, const char *pattern)
b8748c12 45{
46 int error;
47
48 if (*pattern == '\0') {
ac3d33df 49 git_error_set(GIT_ERROR_REGEX, "empty pattern");
cc146626 50 return GIT_EINVALIDSPEC;
b8748c12 51 }
52
22a2d3d5 53 error = git_regexp_compile(regex, pattern, 0);
b8748c12 54 if (!error)
55 return 0;
8480eef7 56
22a2d3d5 57 git_regexp_dispose(regex);
b8748c12 58
cc146626 59 return error;
b8748c12 60}
61
3e82d6c6 62static int maybe_describe(git_object**out, git_repository *repo, const char *spec)
e7279381 63{
e28dd29b 64 const char *substr;
b8748c12 65 int error;
22a2d3d5 66 git_regexp regex;
e28dd29b 67
e28dd29b 68 substr = strstr(spec, "-g");
e28dd29b 69
3e82d6c6 70 if (substr == NULL)
71 return GIT_ENOTFOUND;
8480eef7 72
b8748c12 73 if (build_regex(&regex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)
74 return -1;
3e82d6c6 75
22a2d3d5
UG
76 error = git_regexp_match(&regex, spec);
77 git_regexp_dispose(&regex);
b8748c12 78
79 if (error)
3e82d6c6 80 return GIT_ENOTFOUND;
81
0e8e5a61 82 return maybe_abbrev(out, repo, substr+2);
3e82d6c6 83}
84
f672cd2a 85static int revparse_lookup_object(
86 git_object **object_out,
87 git_reference **reference_out,
88 git_repository *repo,
89 const char *spec)
3e82d6c6 90{
91 int error;
92 git_reference *ref;
93
1634df8c 94 if ((error = maybe_sha(object_out, repo, spec)) != GIT_ENOTFOUND)
0e8e5a61 95 return error;
96
98d633cc 97 error = git_reference_dwim(&ref, repo, spec);
e7279381 98 if (!error) {
f672cd2a 99
100 error = git_object_lookup(
ac3d33df 101 object_out, repo, git_reference_target(ref), GIT_OBJECT_ANY);
f672cd2a 102
103 if (!error)
104 *reference_out = ref;
105
b5f90115 106 return error;
e28dd29b 107 }
108
1634df8c 109 if (error != GIT_ENOTFOUND)
545b479a 110 return error;
111
a8d67afe 112 if ((strlen(spec) < GIT_OID_HEXSZ) &&
113 ((error = maybe_abbrev(object_out, repo, spec)) != GIT_ENOTFOUND))
114 return error;
d1b7921a 115
1634df8c 116 if ((error = maybe_describe(object_out, repo, spec)) != GIT_ENOTFOUND)
e7279381 117 return error;
e28dd29b 118
ac3d33df 119 git_error_set(GIT_ERROR_REFERENCE, "revspec '%s' not found", spec);
4de89ce7 120 return GIT_ENOTFOUND;
ac250c56
BS
121}
122
b8748c12 123static int try_parse_numeric(int *n, const char *curly_braces_content)
a346992f 124{
a8122b5d 125 int32_t content;
b8748c12 126 const char *end_ptr;
127
6c7cee42
RD
128 if (git__strntol32(&content, curly_braces_content, strlen(curly_braces_content),
129 &end_ptr, 10) < 0)
b8748c12 130 return -1;
29f72aa6 131
b8748c12 132 if (*end_ptr != '\0')
133 return -1;
29f72aa6 134
a8122b5d 135 *n = (int)content;
b8748c12 136 return 0;
a346992f
BS
137}
138
cc146626 139static 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 140{
b8748c12 141 git_reference *ref = NULL;
e28dd29b 142 git_reflog *reflog = NULL;
22a2d3d5 143 git_regexp preg;
a8122b5d
RB
144 int error = -1;
145 size_t i, numentries, cur;
e28dd29b 146 const git_reflog_entry *entry;
b8748c12 147 const char *msg;
e28dd29b 148 git_buf buf = GIT_BUF_INIT;
e28dd29b 149
b8748c12 150 cur = position;
e28dd29b 151
b8748c12 152 if (*identifier != '\0' || *base_ref != NULL)
cc146626 153 return GIT_EINVALIDSPEC;
e28dd29b 154
b8748c12 155 if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0)
156 return -1;
cab65c2b 157
b8748c12 158 if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0)
159 goto cleanup;
e28dd29b 160
b976f3c2 161 if (git_reflog_read(&reflog, repo, GIT_HEAD_FILE) < 0)
b8748c12 162 goto cleanup;
163
164 numentries = git_reflog_entrycount(reflog);
165
b15df1d9 166 for (i = 0; i < numentries; i++) {
22a2d3d5
UG
167 git_regmatch regexmatches[2];
168
b8748c12 169 entry = git_reflog_entry_byindex(reflog, i);
2508cc66 170 msg = git_reflog_entry_message(entry);
e7c66fc8
L
171 if (!msg)
172 continue;
a8122b5d 173
22a2d3d5 174 if (git_regexp_search(&preg, msg, 2, regexmatches) < 0)
b8748c12 175 continue;
176
177 cur--;
178
179 if (cur > 0)
180 continue;
a8122b5d 181
22a2d3d5
UG
182 if ((git_buf_put(&buf, msg+regexmatches[1].start, regexmatches[1].end - regexmatches[1].start)) < 0)
183 goto cleanup;
b8748c12 184
98d633cc 185 if ((error = git_reference_dwim(base_ref, repo, git_buf_cstr(&buf))) == 0)
b8748c12 186 goto cleanup;
187
188 if (error < 0 && error != GIT_ENOTFOUND)
189 goto cleanup;
190
0e8e5a61 191 error = maybe_abbrev(out, repo, git_buf_cstr(&buf));
b8748c12 192
193 goto cleanup;
194 }
a8122b5d 195
b8748c12 196 error = GIT_ENOTFOUND;
197
198cleanup:
199 git_reference_free(ref);
ac3d33df 200 git_buf_dispose(&buf);
22a2d3d5 201 git_regexp_dispose(&preg);
b8748c12 202 git_reflog_free(reflog);
203 return error;
204}
205
a8122b5d 206static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t identifier)
b8748c12 207{
208 git_reflog *reflog;
a8122b5d 209 size_t numentries;
b8748c12 210 const git_reflog_entry *entry;
211 bool search_by_pos = (identifier <= 100000000);
212
b976f3c2 213 if (git_reflog_read(&reflog, git_reference_owner(ref), git_reference_name(ref)) < 0)
b8748c12 214 return -1;
215
a8122b5d 216 numentries = git_reflog_entrycount(reflog);
b8748c12 217
218 if (search_by_pos) {
1e71354e
VM
219 if (numentries < identifier + 1)
220 goto notfound;
b8748c12 221
222 entry = git_reflog_entry_byindex(reflog, identifier);
2508cc66 223 git_oid_cpy(oid, git_reflog_entry_id_new(entry));
e28dd29b 224 } else {
a8122b5d 225 size_t i;
b8748c12 226 git_time commit_time;
e28dd29b 227
b15df1d9 228 for (i = 0; i < numentries; i++) {
b8748c12 229 entry = git_reflog_entry_byindex(reflog, i);
230 commit_time = git_reflog_entry_committer(entry)->when;
a8122b5d
RB
231
232 if (commit_time.time > (git_time_t)identifier)
b8748c12 233 continue;
e7279381 234
2508cc66 235 git_oid_cpy(oid, git_reflog_entry_id_new(entry));
1e71354e 236 break;
e7279381 237 }
238
1e71354e
VM
239 if (i == numentries)
240 goto notfound;
b8748c12 241 }
e28dd29b 242
b8748c12 243 git_reflog_free(reflog);
1e71354e
VM
244 return 0;
245
246notfound:
ac3d33df
JK
247 git_error_set(
248 GIT_ERROR_REFERENCE,
909d5494 249 "reflog for '%s' has only %"PRIuZ" entries, asked for %"PRIuZ,
1e71354e
VM
250 git_reference_name(ref), numentries, identifier);
251
252 git_reflog_free(reflog);
253 return GIT_ENOTFOUND;
b8748c12 254}
e28dd29b 255
a8122b5d 256static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
b8748c12 257{
258 git_reference *ref;
259 git_oid oid;
260 int error = -1;
e28dd29b 261
b8748c12 262 if (*base_ref == NULL) {
98d633cc 263 if ((error = git_reference_dwim(&ref, repo, identifier)) < 0)
b8748c12 264 return error;
265 } else {
266 ref = *base_ref;
267 *base_ref = NULL;
268 }
e28dd29b 269
b8748c12 270 if (position == 0) {
ac3d33df 271 error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJECT_ANY);
b8748c12 272 goto cleanup;
273 }
e28dd29b 274
b8748c12 275 if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0)
276 goto cleanup;
e28dd29b 277
ac3d33df 278 error = git_object_lookup(out, repo, &oid, GIT_OBJECT_ANY);
b8748c12 279
280cleanup:
281 git_reference_free(ref);
282 return error;
283}
284
285static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo)
286{
287 git_reference *tracking, *ref;
288 int error = -1;
289
290 if (*base_ref == NULL) {
98d633cc 291 if ((error = git_reference_dwim(&ref, repo, identifier)) < 0)
b8748c12 292 return error;
293 } else {
294 ref = *base_ref;
295 *base_ref = NULL;
e28dd29b 296 }
297
cc146626 298 if (!git_reference_is_branch(ref)) {
299 error = GIT_EINVALIDSPEC;
300 goto cleanup;
301 }
302
a258d8e3 303 if ((error = git_branch_upstream(&tracking, ref)) < 0)
b8748c12 304 goto cleanup;
8480eef7 305
b8748c12 306 *base_ref = tracking;
307
e7279381 308cleanup:
b8748c12 309 git_reference_free(ref);
310 return error;
311}
312
c25aa7cd 313static 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 314{
315 bool is_numeric;
7e48635d 316 int parsed = 0, error = -1;
b8748c12 317 git_buf identifier = GIT_BUF_INIT;
318 git_time_t timestamp;
319
c25aa7cd 320 GIT_ASSERT(*out == NULL);
b8748c12 321
322 if (git_buf_put(&identifier, spec, identifier_len) < 0)
323 return -1;
324
325 is_numeric = !try_parse_numeric(&parsed, curly_braces_content);
326
327 if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) {
cc146626 328 error = GIT_EINVALIDSPEC;
b8748c12 329 goto cleanup;
330 }
331
332 if (is_numeric) {
333 if (parsed < 0)
cc146626 334 error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, git_buf_cstr(&identifier), -parsed);
b8748c12 335 else
336 error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), parsed);
337
338 goto cleanup;
339 }
340
341 if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) {
342 error = retrieve_remote_tracking_reference(ref, git_buf_cstr(&identifier), repo);
343
344 goto cleanup;
345 }
346
347 if (git__date_parse(&timestamp, curly_braces_content) < 0)
348 goto cleanup;
349
a8122b5d 350 error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), (size_t)timestamp);
b8748c12 351
352cleanup:
ac3d33df 353 git_buf_dispose(&identifier);
b8748c12 354 return error;
023c6f69
BS
355}
356
ac3d33df 357static git_object_t parse_obj_type(const char *str)
9d7bdf71 358{
b8748c12 359 if (!strcmp(str, "commit"))
ac3d33df 360 return GIT_OBJECT_COMMIT;
b8748c12 361
362 if (!strcmp(str, "tree"))
ac3d33df 363 return GIT_OBJECT_TREE;
a8122b5d 364
b8748c12 365 if (!strcmp(str, "blob"))
ac3d33df 366 return GIT_OBJECT_BLOB;
b8748c12 367
368 if (!strcmp(str, "tag"))
ac3d33df 369 return GIT_OBJECT_TAG;
b8748c12 370
ac3d33df 371 return GIT_OBJECT_INVALID;
9d7bdf71
BS
372}
373
b8748c12 374static int dereference_to_non_tag(git_object **out, git_object *obj)
023c6f69 375{
ac3d33df 376 if (git_object_type(obj) == GIT_OBJECT_TAG)
b8748c12 377 return git_tag_peel(out, (git_tag *)obj);
e28dd29b 378
575a54db 379 return git_object_dup(out, obj);
b8748c12 380}
e28dd29b 381
b8748c12 382static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n)
383{
384 git_object *temp_commit = NULL;
385 int error;
e28dd29b 386
ac3d33df 387 if ((error = git_object_peel(&temp_commit, obj, GIT_OBJECT_COMMIT)) < 0)
cc146626 388 return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
389 GIT_EINVALIDSPEC : error;
e28dd29b 390
e28dd29b 391 if (n == 0) {
b8748c12 392 *out = temp_commit;
e28dd29b 393 return 0;
394 }
395
b8748c12 396 error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1);
e28dd29b 397
b8748c12 398 git_object_free(temp_commit);
399 return error;
023c6f69 400}
ac250c56 401
b8748c12 402static int handle_linear_syntax(git_object **out, git_object *obj, int n)
38533d5a 403{
b8748c12 404 git_object *temp_commit = NULL;
405 int error;
e28dd29b 406
ac3d33df 407 if ((error = git_object_peel(&temp_commit, obj, GIT_OBJECT_COMMIT)) < 0)
cc146626 408 return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
409 GIT_EINVALIDSPEC : error;
e28dd29b 410
b8748c12 411 error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n);
e28dd29b 412
b8748c12 413 git_object_free(temp_commit);
414 return error;
38533d5a
BS
415}
416
b8748c12 417static int handle_colon_syntax(
418 git_object **out,
e28dd29b 419 git_object *obj,
420 const char *path)
244d2f6b 421{
b8748c12 422 git_object *tree;
bb89cf94 423 int error = -1;
424 git_tree_entry *entry = NULL;
244d2f6b 425
ac3d33df 426 if ((error = git_object_peel(&tree, obj, GIT_OBJECT_TREE)) < 0)
cc146626 427 return error == GIT_ENOTFOUND ? GIT_EINVALIDSPEC : error;
244d2f6b 428
b8748c12 429 if (*path == '\0') {
430 *out = tree;
431 return 0;
432 }
053b5096 433
bb89cf94 434 /*
435 * TODO: Handle the relative path syntax
436 * (:./relative/path and :../relative/path)
437 */
438 if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0)
439 goto cleanup;
440
b8748c12 441 error = git_tree_entry_to_object(out, git_object_owner(tree), entry);
053b5096 442
bb89cf94 443cleanup:
444 git_tree_entry_free(entry);
b8748c12 445 git_object_free(tree);
bb89cf94 446
447 return error;
244d2f6b
BS
448}
449
22a2d3d5 450static int walk_and_search(git_object **out, git_revwalk *walk, git_regexp *regex)
734efe4b 451{
b8748c12 452 int error;
453 git_oid oid;
454 git_object *obj;
8480eef7 455
b8748c12 456 while (!(error = git_revwalk_next(&oid, walk))) {
e28dd29b 457
ac3d33df 458 error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJECT_COMMIT);
10c06114 459 if ((error < 0) && (error != GIT_ENOTFOUND))
b8748c12 460 return -1;
461
22a2d3d5 462 if (!git_regexp_match(regex, git_commit_message((git_commit*)obj))) {
b8748c12 463 *out = obj;
464 return 0;
465 }
466
467 git_object_free(obj);
e28dd29b 468 }
469
f335ecd6 470 if (error < 0 && error == GIT_ITEROVER)
b8748c12 471 error = GIT_ENOTFOUND;
472
473 return error;
474}
475
476static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern)
477{
22a2d3d5 478 git_regexp preg;
b8748c12 479 git_revwalk *walk = NULL;
cc146626 480 int error;
b8748c12 481
cc146626 482 if ((error = build_regex(&preg, pattern)) < 0)
483 return error;
8480eef7 484
cc146626 485 if ((error = git_revwalk_new(&walk, repo)) < 0)
b8748c12 486 goto cleanup;
487
488 git_revwalk_sorting(walk, GIT_SORT_TIME);
489
490 if (spec_oid == NULL) {
c74077d1 491 if ((error = git_revwalk_push_glob(walk, "refs/*")) < 0)
b8748c12 492 goto cleanup;
cc146626 493 } else if ((error = git_revwalk_push(walk, spec_oid)) < 0)
b8748c12 494 goto cleanup;
495
496 error = walk_and_search(out, walk, &preg);
8480eef7 497
b8748c12 498cleanup:
22a2d3d5 499 git_regexp_dispose(&preg);
b8748c12 500 git_revwalk_free(walk);
501
502 return error;
503}
504
505static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content)
506{
ac3d33df 507 git_object_t expected_type;
b8748c12 508
509 if (*curly_braces_content == '\0')
510 return dereference_to_non_tag(out, obj);
511
512 if (*curly_braces_content == '/')
513 return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1);
514
515 expected_type = parse_obj_type(curly_braces_content);
516
ac3d33df 517 if (expected_type == GIT_OBJECT_INVALID)
cc146626 518 return GIT_EINVALIDSPEC;
b8748c12 519
e2c81fca 520 return git_object_peel(out, obj, expected_type);
b8748c12 521}
522
b8457baa 523static int extract_curly_braces_content(git_buf *buf, const char *spec, size_t *pos)
b8748c12 524{
525 git_buf_clear(buf);
526
c25aa7cd 527 GIT_ASSERT_ARG(spec[*pos] == '^' || spec[*pos] == '@');
b8748c12 528
529 (*pos)++;
530
531 if (spec[*pos] == '\0' || spec[*pos] != '{')
cc146626 532 return GIT_EINVALIDSPEC;
b8748c12 533
534 (*pos)++;
535
536 while (spec[*pos] != '}') {
537 if (spec[*pos] == '\0')
cc146626 538 return GIT_EINVALIDSPEC;
b8748c12 539
22a2d3d5
UG
540 if (git_buf_putc(buf, spec[(*pos)++]) < 0)
541 return -1;
b8748c12 542 }
543
544 (*pos)++;
545
546 return 0;
547}
548
b8457baa 549static int extract_path(git_buf *buf, const char *spec, size_t *pos)
b8748c12 550{
551 git_buf_clear(buf);
552
c25aa7cd 553 GIT_ASSERT_ARG(spec[*pos] == ':');
b8748c12 554
555 (*pos)++;
556
557 if (git_buf_puts(buf, spec + *pos) < 0)
558 return -1;
559
560 *pos += git_buf_len(buf);
561
562 return 0;
563}
564
b8457baa 565static int extract_how_many(int *n, const char *spec, size_t *pos)
b8748c12 566{
567 const char *end_ptr;
568 int parsed, accumulated;
569 char kind = spec[*pos];
570
c25aa7cd 571 GIT_ASSERT_ARG(spec[*pos] == '^' || spec[*pos] == '~');
b8748c12 572
573 accumulated = 0;
574
575 do {
576 do {
577 (*pos)++;
578 accumulated++;
579 } while (spec[(*pos)] == kind && kind == '~');
580
581 if (git__isdigit(spec[*pos])) {
6c7cee42 582 if (git__strntol32(&parsed, spec + *pos, strlen(spec + *pos), &end_ptr, 10) < 0)
cc146626 583 return GIT_EINVALIDSPEC;
b8748c12 584
585 accumulated += (parsed - 1);
586 *pos = end_ptr - spec;
e28dd29b 587 }
b8748c12 588
22a2d3d5 589 } while (spec[(*pos)] == kind && kind == '~');
b8748c12 590
591 *n = accumulated;
592
593 return 0;
594}
595
596static int object_from_reference(git_object **object, git_reference *reference)
597{
598 git_reference *resolved = NULL;
599 int error;
600
601 if (git_reference_resolve(&resolved, reference) < 0)
602 return -1;
603
ac3d33df 604 error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJECT_ANY);
b8748c12 605 git_reference_free(resolved);
606
607 return error;
608}
609
f672cd2a 610static 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 611{
612 int error;
613 git_buf identifier = GIT_BUF_INIT;
614
615 if (*object != NULL)
616 return 0;
617
f672cd2a 618 if (*reference != NULL)
619 return object_from_reference(object, *reference);
e28dd29b 620
b8748c12 621 if (!allow_empty_identifier && identifier_len == 0)
cc146626 622 return GIT_EINVALIDSPEC;
b8748c12 623
624 if (git_buf_put(&identifier, spec, identifier_len) < 0)
625 return -1;
626
f672cd2a 627 error = revparse_lookup_object(object, reference, repo, git_buf_cstr(&identifier));
ac3d33df 628 git_buf_dispose(&identifier);
b8748c12 629
630 return error;
631}
632
cc146626 633static int ensure_base_rev_is_not_known_yet(git_object *object)
b8748c12 634{
635 if (object == NULL)
636 return 0;
637
cc146626 638 return GIT_EINVALIDSPEC;
b8748c12 639}
640
b8457baa 641static bool any_left_hand_identifier(git_object *object, git_reference *reference, size_t identifier_len)
b8748c12 642{
643 if (object != NULL)
644 return true;
645
646 if (reference != NULL)
647 return true;
648
649 if (identifier_len > 0)
650 return true;
651
652 return false;
653}
654
cc146626 655static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference)
b8748c12 656{
cc146626 657 if (!ensure_base_rev_is_not_known_yet(object) && reference == NULL)
b8748c12 658 return 0;
659
cc146626 660 return GIT_EINVALIDSPEC;
734efe4b
BS
661}
662
22a2d3d5 663static int revparse(
e841c533 664 git_object **object_out,
665 git_reference **reference_out,
37f66e82 666 size_t *identifier_len_out,
e841c533 667 git_repository *repo,
668 const char *spec)
ac250c56 669{
b8457baa 670 size_t pos = 0, identifier_len = 0;
b8748c12 671 int error = -1, n;
672 git_buf buf = GIT_BUF_INIT;
673
674 git_reference *reference = NULL;
675 git_object *base_rev = NULL;
e28dd29b 676
80fd31fa 677 bool should_return_reference = true;
678
c25aa7cd
PP
679 GIT_ASSERT_ARG(object_out);
680 GIT_ASSERT_ARG(reference_out);
681 GIT_ASSERT_ARG(repo);
682 GIT_ASSERT_ARG(spec);
e28dd29b 683
e841c533 684 *object_out = NULL;
685 *reference_out = NULL;
e28dd29b 686
279b45b0 687 while (spec[pos]) {
b8748c12 688 switch (spec[pos]) {
689 case '^':
80fd31fa 690 should_return_reference = false;
691
f672cd2a 692 if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
b8748c12 693 goto cleanup;
e28dd29b 694
b8748c12 695 if (spec[pos+1] == '{') {
696 git_object *temp_object = NULL;
e28dd29b 697
b8748c12 698 if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
699 goto cleanup;
e28dd29b 700
b8748c12 701 if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
702 goto cleanup;
703
704 git_object_free(base_rev);
705 base_rev = temp_object;
e28dd29b 706 } else {
b8748c12 707 git_object *temp_object = NULL;
708
709 if ((error = extract_how_many(&n, spec, &pos)) < 0)
710 goto cleanup;
711
712 if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0)
713 goto cleanup;
714
715 git_object_free(base_rev);
716 base_rev = temp_object;
e28dd29b 717 }
e28dd29b 718 break;
719
b8748c12 720 case '~':
721 {
722 git_object *temp_object = NULL;
723
80fd31fa 724 should_return_reference = false;
725
b8748c12 726 if ((error = extract_how_many(&n, spec, &pos)) < 0)
727 goto cleanup;
728
f672cd2a 729 if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
b8748c12 730 goto cleanup;
731
732 if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0)
733 goto cleanup;
734
735 git_object_free(base_rev);
736 base_rev = temp_object;
e28dd29b 737 break;
b8748c12 738 }
e28dd29b 739
b8748c12 740 case ':':
741 {
742 git_object *temp_object = NULL;
743
80fd31fa 744 should_return_reference = false;
745
b8748c12 746 if ((error = extract_path(&buf, spec, &pos)) < 0)
747 goto cleanup;
748
749 if (any_left_hand_identifier(base_rev, reference, identifier_len)) {
f672cd2a 750 if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0)
b8748c12 751 goto cleanup;
752
753 if ((error = handle_colon_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
754 goto cleanup;
e28dd29b 755 } else {
b8748c12 756 if (*git_buf_cstr(&buf) == '/') {
757 if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_buf_cstr(&buf) + 1)) < 0)
758 goto cleanup;
759 } else {
760
761 /*
762 * TODO: support merge-stage path lookup (":2:Makefile")
763 * and plain index blob lookup (:i-am/a/blob)
764 */
ac3d33df 765 git_error_set(GIT_ERROR_INVALID, "unimplemented");
b8748c12 766 error = GIT_ERROR;
767 goto cleanup;
768 }
e28dd29b 769 }
b8748c12 770
771 git_object_free(base_rev);
772 base_rev = temp_object;
e28dd29b 773 break;
b8748c12 774 }
775
776 case '@':
5912d74c 777 if (spec[pos+1] == '{') {
778 git_object *temp_object = NULL;
e28dd29b 779
5912d74c 780 if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
781 goto cleanup;
b8748c12 782
cc146626 783 if ((error = ensure_base_rev_is_not_known_yet(base_rev)) < 0)
5912d74c 784 goto cleanup;
b8748c12 785
5912d74c 786 if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_buf_cstr(&buf))) < 0)
787 goto cleanup;
b8748c12 788
5912d74c 789 if (temp_object != NULL)
790 base_rev = temp_object;
791 break;
5912d74c 792 }
eae0bfdc 793 /* fall through */
e28dd29b 794
b8748c12 795 default:
cc146626 796 if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference)) < 0)
b8748c12 797 goto cleanup;
798
799 pos++;
800 identifier_len++;
e28dd29b 801 }
279b45b0 802 }
e28dd29b 803
f672cd2a 804 if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
b8748c12 805 goto cleanup;
806
80fd31fa 807 if (!should_return_reference) {
808 git_reference_free(reference);
809 reference = NULL;
810 }
811
e841c533 812 *object_out = base_rev;
813 *reference_out = reference;
814 *identifier_len_out = identifier_len;
b8748c12 815 error = 0;
e28dd29b 816
b8748c12 817cleanup:
cc146626 818 if (error) {
819 if (error == GIT_EINVALIDSPEC)
ac3d33df 820 git_error_set(GIT_ERROR_INVALID,
909d5494 821 "failed to parse revision specifier - Invalid pattern '%s'", spec);
cc146626 822
b8748c12 823 git_object_free(base_rev);
e841c533 824 git_reference_free(reference);
cc146626 825 }
e841c533 826
ac3d33df 827 git_buf_dispose(&buf);
b8748c12 828 return error;
ac250c56 829}
b208d900 830
e841c533 831int git_revparse_ext(
832 git_object **object_out,
833 git_reference **reference_out,
834 git_repository *repo,
835 const char *spec)
836{
37f66e82
RB
837 int error;
838 size_t identifier_len;
e841c533 839 git_object *obj = NULL;
840 git_reference *ref = NULL;
841
22a2d3d5 842 if ((error = revparse(&obj, &ref, &identifier_len, repo, spec)) < 0)
e841c533 843 goto cleanup;
844
845 *object_out = obj;
846 *reference_out = ref;
37f66e82 847 GIT_UNUSED(identifier_len);
e841c533 848
849 return 0;
850
851cleanup:
852 git_object_free(obj);
853 git_reference_free(ref);
854 return error;
855}
856
857int git_revparse_single(git_object **out, git_repository *repo, const char *spec)
858{
859 int error;
860 git_object *obj = NULL;
861 git_reference *ref = NULL;
862
863 *out = NULL;
864
865 if ((error = git_revparse_ext(&obj, &ref, repo, spec)) < 0)
866 goto cleanup;
867
868 git_reference_free(ref);
869
870 *out = obj;
871
872 return 0;
873
874cleanup:
875 git_object_free(obj);
876 git_reference_free(ref);
877 return error;
878}
8480eef7
BS
879
880int git_revparse(
cbda09d0 881 git_revspec *revspec,
36c2dfed
VM
882 git_repository *repo,
883 const char *spec)
b208d900 884{
8480eef7 885 const char *dotdot;
b208d900 886 int error = 0;
b208d900 887
c25aa7cd
PP
888 GIT_ASSERT_ARG(revspec);
889 GIT_ASSERT_ARG(repo);
890 GIT_ASSERT_ARG(spec);
36c2dfed 891
cbda09d0 892 memset(revspec, 0x0, sizeof(*revspec));
8480eef7
BS
893
894 if ((dotdot = strstr(spec, "..")) != NULL) {
895 char *lstr;
896 const char *rstr;
c25aa7cd 897 revspec->flags = GIT_REVSPEC_RANGE;
8480eef7 898
8b107dc5
WB
899 /*
900 * Following git.git, don't allow '..' because it makes command line
901 * arguments which can be either paths or revisions ambiguous when the
902 * path is almost certainly intended. The empty range '...' is still
903 * allowed.
904 */
905 if (!git__strcmp(spec, "..")) {
ac3d33df 906 git_error_set(GIT_ERROR_INVALID, "Invalid pattern '..'");
8b107dc5
WB
907 return GIT_EINVALIDSPEC;
908 }
909
36c2dfed 910 lstr = git__substrdup(spec, dotdot - spec);
8480eef7
BS
911 rstr = dotdot + 2;
912 if (dotdot[2] == '.') {
c25aa7cd 913 revspec->flags |= GIT_REVSPEC_MERGE_BASE;
8480eef7
BS
914 rstr++;
915 }
916
8b107dc5
WB
917 error = git_revparse_single(
918 &revspec->from,
919 repo,
920 *lstr == '\0' ? "HEAD" : lstr);
921
922 if (!error) {
923 error = git_revparse_single(
924 &revspec->to,
925 repo,
926 *rstr == '\0' ? "HEAD" : rstr);
927 }
8480eef7
BS
928
929 git__free((void*)lstr);
b208d900 930 } else {
c25aa7cd 931 revspec->flags = GIT_REVSPEC_SINGLE;
cbda09d0 932 error = git_revparse_single(&revspec->from, repo, spec);
b208d900
GP
933 }
934
b208d900
GP
935 return error;
936}