revision: allow --end-of-options to end option parsing

There's currently no robust way to tell Git that a particular option is meant to be a revision, and not an option. So if you have a branch "refs/heads/--foo", you cannot just say: git rev-list --foo You can say: git rev-list refs/heads/--foo But that breaks down if you don't know the refname, and in particular if you're a script passing along a value from elsewhere. In most programs, you can use "--" to end option parsing, like this: some-prog -- "$revision" But that doesn't work for the revision parser, because "--" is already meaningful there: it separates revisions from pathspecs. So we need some other marker to separate options from revisions. This patch introduces "--end-of-options", which serves that purpose: git rev-list --oneline --end-of-options "$revision" will work regardless of what's in "$revision" (well, if you say "--" it may fail, but it won't do something dangerous, like triggering an unexpected option). The name is verbose, but that's probably a good thing; this is meant to be used for scripted invocations where readability is more important than terseness. One alternative would be to introduce an explicit option to mark a revision, like: git rev-list --oneline --revision="$revision" That's slightly _more_ informative than this commit (because it makes even something silly like "--" unambiguous). But the pattern of using a separator like "--" is well established in git and in other commands, and it makes some scripting tasks simpler like: git rev-list --end-of-options "$@" There's no documentation in this patch, because it will make sense to describe the feature once it is available everywhere (and support will be added in further patches). Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Aug 6, 2019 at 10:39 UTC 19e8789b236dfe33667747d5523d6689bb59b5ef
2 files changed +15 -1
revision.c
+7 -1
@@ -2523,6 +2523,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
2523 int i, flags, left, seen_dashdash, got_rev_arg = 0, revarg_opt;
2524 struct argv_array prune_data = ARGV_ARRAY_INIT;
2525 const char *submodule = NULL;
2526 + int seen_end_of_options = 0;
2527
2528 if (opt)
2529 submodule = opt->submodule;
@@ -2552,7 +2553,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
2553 revarg_opt |= REVARG_CANNOT_BE_FILENAME;
2554 for (left = i = 1; i < argc; i++) {
2555 const char *arg = argv[i];
2555 - if (*arg == '-') {
2556 + if (!seen_end_of_options && *arg == '-') {
2557 int opts;
2558
2559 opts = handle_revision_pseudo_opt(submodule,
@@ -2574,6 +2575,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
2575 continue;
2576 }
2577
2578 + if (!strcmp(arg, "--end-of-options")) {
2579 + seen_end_of_options = 1;
2580 + continue;
2581 + }
2582 +
2583 opts = handle_revision_opt(revs, argc - i, argv + i,
2584 &left, argv, opt);
2585 if (opts > 0) {
t/t6000-rev-list-misc.sh
+8
@@ -140,4 +140,12 @@ test_expect_success '--header shows a NUL after each commit' '
140 test_cmp expect actual
141 '
142
143 +test_expect_success 'rev-list --end-of-options' '
144 + git update-ref refs/heads/--output=yikes HEAD &&
145 + git rev-list --end-of-options --output=yikes >actual &&
146 + test_path_is_missing yikes &&
147 + git rev-list HEAD >expect &&
148 + test_cmp expect actual
149 +'
150 +
151 test_done