Skip to content

Commit 3a1f91c

Browse files
peffgitster
authored andcommitted
rev-parse: handle --end-of-options
We taught rev-list a new way to separate options from revisions in 19e8789 (revision: allow --end-of-options to end option parsing, 2019-08-06), but rev-parse uses its own parser. It should know about --end-of-options not only for consistency, but because it may be presented with similarly ambiguous cases. E.g., if a caller does: git rev-parse "$rev" -- "$path" to parse an untrusted input, then it will get confused if $rev contains an option-like string like "--local-env-vars". Or even "--not-real", which we'd keep as an option to pass along to rev-list. Or even more importantly: git rev-parse --verify "$rev" can be confused by options, even though its purpose is safely parsing untrusted input. On the plus side, it will always fail the --verify part, as it will not have parsed a revision, so the caller will generally "fail closed" rather than continue to use the untrusted string. But it will still trigger whatever option was in "$rev"; this should be mostly harmless, since rev-parse options are all read-only, but I didn't carefully audit all paths. This patch lets callers write: git rev-parse --end-of-options "$rev" -- "$path" and: git rev-parse --verify --end-of-options "$rev" which will both treat "$rev" always as a revision parameter. The latter is a bit clunky. It would be nicer if we had defined "--verify" to require that its next argument be the revision. But we have not historically done so, and: git rev-parse --verify -q "$rev" does currently work. I added a test here to confirm that we didn't break that. A few implementation notes: - We don't document --end-of-options explicitly in commands, but rather in gitcli(7). So I didn't give it its own section in git-rev-parse(1). But I did call it out specifically in the --verify section, and include it in the examples, which should show best practices. - We don't have to re-indent the main option-parsing block, because we can combine our "did we see end of options" check with "does it start with a dash". The exception is the pre-setup options, which need their own block. - We do however have to pull the "--" parsing out of the "does it start with dash" block, because we want to parse it even if we've seen --end-of-options. - We'll leave "--end-of-options" in the output. This is probably not technically necessary, as a careful caller will do: git rev-parse --end-of-options $revs -- $paths and anything in $revs will be resolved to an object id. However, it does help a slightly less careful caller like: git rev-parse --end-of-options $revs_or_paths where a path "--foo" will remain in the output as long as it also exists on disk. In that case, it's helpful to retain --end-of-options to get passed along to rev-list, s it would otherwise see just "--foo". Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 9033add commit 3a1f91c

File tree

4 files changed

+68
-25
lines changed

4 files changed

+68
-25
lines changed

Documentation/git-rev-parse.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ names an existing object that is a commit-ish (i.e. a commit, or an
109109
annotated tag that points at a commit). To make sure that `$VAR`
110110
names an existing object of any type, `git rev-parse "$VAR^{object}"`
111111
can be used.
112+
+
113+
Note that if you are verifying a name from an untrusted source, it is
114+
wise to use `--end-of-options` so that the name argument is not mistaken
115+
for another option.
112116

113117
-q::
114118
--quiet::
@@ -446,15 +450,15 @@ $ git rev-parse --verify HEAD
446450
* Print the commit object name from the revision in the $REV shell variable:
447451
+
448452
------------
449-
$ git rev-parse --verify $REV^{commit}
453+
$ git rev-parse --verify --end-of-options $REV^{commit}
450454
------------
451455
+
452456
This will error out if $REV is empty or not a valid revision.
453457

454458
* Similar to above:
455459
+
456460
------------
457-
$ git rev-parse --default master --verify $REV
461+
$ git rev-parse --default master --verify --end-of-options $REV
458462
------------
459463
+
460464
but if $REV is empty, the commit object name from master will be printed.

builtin/rev-parse.c

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
595595
struct object_context unused;
596596
struct strbuf buf = STRBUF_INIT;
597597
const int hexsz = the_hash_algo->hexsz;
598+
int seen_end_of_options = 0;
598599

599600
if (argc > 1 && !strcmp("--parseopt", argv[1]))
600601
return cmd_parseopt(argc - 1, argv + 1, prefix);
@@ -628,21 +629,23 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
628629
continue;
629630
}
630631

631-
if (!strcmp(arg, "--local-env-vars")) {
632-
int i;
633-
for (i = 0; local_repo_env[i]; i++)
634-
printf("%s\n", local_repo_env[i]);
635-
continue;
636-
}
637-
if (!strcmp(arg, "--resolve-git-dir")) {
638-
const char *gitdir = argv[++i];
639-
if (!gitdir)
640-
die("--resolve-git-dir requires an argument");
641-
gitdir = resolve_gitdir(gitdir);
642-
if (!gitdir)
643-
die("not a gitdir '%s'", argv[i]);
644-
puts(gitdir);
645-
continue;
632+
if (!seen_end_of_options) {
633+
if (!strcmp(arg, "--local-env-vars")) {
634+
int i;
635+
for (i = 0; local_repo_env[i]; i++)
636+
printf("%s\n", local_repo_env[i]);
637+
continue;
638+
}
639+
if (!strcmp(arg, "--resolve-git-dir")) {
640+
const char *gitdir = argv[++i];
641+
if (!gitdir)
642+
die("--resolve-git-dir requires an argument");
643+
gitdir = resolve_gitdir(gitdir);
644+
if (!gitdir)
645+
die("not a gitdir '%s'", argv[i]);
646+
puts(gitdir);
647+
continue;
648+
}
646649
}
647650

648651
/* The rest of the options require a git repository. */
@@ -652,14 +655,15 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
652655
did_repo_setup = 1;
653656
}
654657

655-
if (*arg == '-') {
656-
if (!strcmp(arg, "--")) {
657-
as_is = 2;
658-
/* Pass on the "--" if we show anything but files.. */
659-
if (filter & (DO_FLAGS | DO_REVS))
660-
show_file(arg, 0);
661-
continue;
662-
}
658+
if (!strcmp(arg, "--")) {
659+
as_is = 2;
660+
/* Pass on the "--" if we show anything but files.. */
661+
if (filter & (DO_FLAGS | DO_REVS))
662+
show_file(arg, 0);
663+
continue;
664+
}
665+
666+
if (!seen_end_of_options && *arg == '-') {
663667
if (!strcmp(arg, "--git-path")) {
664668
if (!argv[i + 1])
665669
die("--git-path requires an argument");
@@ -937,6 +941,12 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
937941
puts(the_hash_algo->name);
938942
continue;
939943
}
944+
if (!strcmp(arg, "--end-of-options")) {
945+
seen_end_of_options = 1;
946+
if (filter & (DO_FLAGS | DO_REVS))
947+
show_file(arg, 0);
948+
continue;
949+
}
940950
if (show_flag(arg) && verify)
941951
die_no_single_rev(quiet);
942952
continue;

t/t1503-rev-parse-verify.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,17 @@ test_expect_success SYMLINKS 'ref resolution not confused by broken symlinks' '
144144
test_must_fail git rev-parse --verify broken
145145
'
146146

147+
test_expect_success 'options can appear after --verify' '
148+
git rev-parse --verify HEAD >expect &&
149+
git rev-parse --verify -q HEAD >actual &&
150+
test_cmp expect actual
151+
'
152+
153+
test_expect_success 'verify respects --end-of-options' '
154+
git update-ref refs/heads/-tricky HEAD &&
155+
git rev-parse --verify HEAD >expect &&
156+
git rev-parse --verify --end-of-options -tricky >actual &&
157+
test_cmp expect actual
158+
'
159+
147160
test_done

t/t1506-rev-parse-diagnosis.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,20 @@ test_expect_success 'arg after dashdash not interpreted as option' '
263263
test_cmp expect actual
264264
'
265265

266+
test_expect_success 'arg after end-of-options not interpreted as option' '
267+
test_must_fail git rev-parse --end-of-options --not-real -- 2>err &&
268+
test_i18ngrep bad.revision.*--not-real err
269+
'
270+
271+
test_expect_success 'end-of-options still allows --' '
272+
cat >expect <<-EOF &&
273+
--end-of-options
274+
$(git rev-parse --verify HEAD)
275+
--
276+
path
277+
EOF
278+
git rev-parse --end-of-options HEAD -- path >actual &&
279+
test_cmp expect actual
280+
'
281+
266282
test_done

0 commit comments

Comments
 (0)