]>
Commit | Line | Data |
---|---|---|
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 | 17 | static 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 | 27 | static 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 | 37 | static 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 | 44 | static 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 | 62 | static 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(®ex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0) |
74 | return -1; | |
3e82d6c6 | 75 | |
22a2d3d5 UG |
76 | error = git_regexp_match(®ex, spec); |
77 | git_regexp_dispose(®ex); | |
b8748c12 | 78 | |
79 | if (error) | |
3e82d6c6 | 80 | return GIT_ENOTFOUND; |
81 | ||
0e8e5a61 | 82 | return maybe_abbrev(out, repo, substr+2); |
3e82d6c6 | 83 | } |
84 | ||
f672cd2a | 85 | static 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 | 123 | static 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 | 139 | 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) |
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 | ||
198 | cleanup: | |
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 | 206 | static 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 | ||
246 | notfound: | |
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 | 256 | static 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 | |
280 | cleanup: | |
281 | git_reference_free(ref); | |
282 | return error; | |
283 | } | |
284 | ||
285 | static 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 | 308 | cleanup: |
b8748c12 | 309 | git_reference_free(ref); |
310 | return error; | |
311 | } | |
312 | ||
c25aa7cd | 313 | 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) |
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(×tamp, 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 | |
352 | cleanup: | |
ac3d33df | 353 | git_buf_dispose(&identifier); |
b8748c12 | 354 | return error; |
023c6f69 BS |
355 | } |
356 | ||
ac3d33df | 357 | static 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 | 374 | static 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 | 382 | static 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 | 402 | static 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 | 417 | static 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 | 443 | cleanup: |
444 | git_tree_entry_free(entry); | |
b8748c12 | 445 | git_object_free(tree); |
bb89cf94 | 446 | |
447 | return error; | |
244d2f6b BS |
448 | } |
449 | ||
22a2d3d5 | 450 | static 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 | ||
476 | static 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 | 498 | cleanup: |
22a2d3d5 | 499 | git_regexp_dispose(&preg); |
b8748c12 | 500 | git_revwalk_free(walk); |
501 | ||
502 | return error; | |
503 | } | |
504 | ||
505 | static 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 | 523 | static 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 | 549 | static 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 | 565 | static 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 | ||
596 | static 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 | 610 | 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) |
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 | 633 | static 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 | 641 | static 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 | 655 | static 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 | 663 | static 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 | 817 | cleanup: |
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 | 831 | int 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 | ||
851 | cleanup: | |
852 | git_object_free(obj); | |
853 | git_reference_free(ref); | |
854 | return error; | |
855 | } | |
856 | ||
857 | int 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 | ||
874 | cleanup: | |
875 | git_object_free(obj); | |
876 | git_reference_free(ref); | |
877 | return error; | |
878 | } | |
8480eef7 BS |
879 | |
880 | int 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 | } |