In Git, what does `--` (dash dash) mean?
Asked Answered
R

2

104

When reading the man pages for Git commands, you will often see an optional -- (dash dash). In my experience, the -- is not necessary and makes no difference. When do you need it? What does it mean in general, given that it appears in so many commands?

Rutland answered 30/3, 2014 at 21:21 Comment(1)
Not a duplicate. This question asks for a conceptual understanding of the double-dash across all git commands. The linked question asks only about git checkout.Vasilikivasilis
R
102

The double dash -- in git means different things to different commands, but in general it separates options from parameters.

In git specifically, the meaning of -- depends on which subcommand you are using it with. It usually separates subcommand arguments (like the branch name in git checkout) from revisions or filenames. Sometimes it is completely optional and used only to prevent an unusual filename from being interpreted as program options.

For Example

  • git checkout. To check out a "commit" (referred to as "tree-ish" in the manual, because you can actually specify a range of object types) you use

    git checkout <commit>

    To refine the checkout to just a file or two, use -- to separate the "tree-ish" parameters from the "filenames" you wish to check out.

  • git commit. To commit whatever is in the "index" (ie, what you have staged via git add, simply issue the git commit command.

    git commit [-m message]

    To ignore whatever you have added via git add and commit the changes in a specific file, use git commit -- <filename>

  • git add. To commit a file whose name begins with a - or a --, you must tell git add to stop reading parameters, and start reading filenames; -- does that.

    git add -- -sample.txt

  • git log. To see the commit history restricted to only commits affecting a file use

    git log -- <filename>

You need to check the man pages for any git command you use if you need to understand its specific meaning.

Rains answered 30/3, 2014 at 22:4 Comment(1)
Funny. I got here from git-scm.com/docs/git-diff and among all the examples listed git-diff is missing :) Got the point though, thanks.Droplight
C
9

This question asks for a conceptual understanding of the double-dash across all git commands.

The double-dash, which signals the end of options, has been recognized as "not enough" for Git.

With Git 2.24 (Q3 2019), the command line parser learned the "--end-of-options" notation:

The standard convention for scripters to have hardcoded set of options first on the command line, and force the command to treat end-user input as non-options, has been to use "--" as the delimiter, but that would not work for commands that use "--" as a delimiter between revs and pathspec.

See commit 67feca3, commit 51b4594, commit 19e8789 (06 Aug 2019) by Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit 4a12f89, 09 Sep 2019)

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 "$@"

parse-options: allow --end-of-options as a synonym for "--"

The revision option parser recently learned about --end-of-options, but that's not quite enough for all callers.
Some of them, like git-log, pick out some options using parse_options(), and then feed the remainder to setup_revisions().
For those cases we need to stop parse_options() from finding more options when it sees --end-of-options, and to retain that option in argv so that setup_revisions() can see it as well.

Let's handle this the same as we do "--". We can even piggy-back on the handling of PARSE_OPT_KEEP_DASHDASH, because any caller that wants to retain one will want to retain the other.

Example:

git update-ref refs/heads/--source HEAD &&\
git log --end-of-options --source

With Git 2.30 (Q1 2021), "git rev-parse"(man) learned the "--end-of-options" to help scripts to safely take a parameter that is supposed to be a revision, e.g. "git rev-parse --verify -q --end-of-options $rev(man)".

See commit 3a1f91c, commit 9033add, commit e05e2ae (10 Nov 2020) by Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit 0dd171f, 21 Nov 2020)

rev-parse: handle --end-of-options

Signed-off-by: Jeff King

We taught rev-list a new way to separate options from revisions in 19e8789b23 ("revision: allow --end-of-options to end option parsing", 2019-08-06, Git v2.24.0-rc0 -- merge listed in batch #2), 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 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, as it would otherwise see just "--foo".

git rev-parse now includes in its man page:

Note that if you are verifying a name from an untrusted source, it is wise to use --end-of-options so that the name argument is not mistaken for another option.

$ git rev-parse --verify --end-of-options $REV^{commit}
$ git rev-parse --default master --verify --end-of-options $REV

With Git 2.31 (Q1 2021), "git mktag"(man) validates its input using its own rules before writing a tag object---it has been updated to share the logic with git fsck".

That means it also supports --end-of-options.

See commit 06ce791 (06 Jan 2021), commit 2aa9425, commit 3f390a3, commit 9a1a3a4, commit acfc013, commit 1f3299f, commit acf9de4, commit 40ef015, commit dfe3948, commit 0c43911, commit 692654d, commit 30f882c, commit ca9a1ed, commit 47c95e7, commit 3b9e4dd, commit 5c2303e, commit 317c176, commit 0d35ccb, commit b5ca549, commit aba5377, commit 18430ed (05 Jan 2021), and commit 9ce0fc3, commit f59b61d (23 Dec 2020) by Ævar Arnfjörð Bjarmason (avar).
(Merged by Junio C Hamano -- gitster -- in commit c7d6d41, 25 Jan 2021)

mktag: convert to parse-options

Signed-off-by: Ævar Arnfjörð Bjarmason

Convert the "mktag" command to use parse-options.h instead of its own ad-hoc argc handling.
This doesn't matter much in practice since it doesn't support any options, but removes another special-case in our codebase, and makes it easier to add options to it in the future.

It does marginally improve the situation for programs that want to execute git commands in a consistent manner and e.g. always use --end-of-options.
E.g.
"gitaly" does that, and has a blacklist of built-ins that don't support --end-of-options.
This is one less special case for it and other similar programs to support.


Before Git 2.44 (Q1 2024), "git $cmd --end-of-options --rev -- --path for some $cmd failed to interpret --rev as a rev, and "--path" as a path.
This was fixed for many programs like "reset" and "checkout".

See commit 9385174 (06 Dec 2023) by Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit 9eec6a1, 20 Dec 2023)

parse-options: decouple "--end-of-options" and "--"

Signed-off-by: Jeff King

When we added generic end-of-options support in 51b4594 ("parse-options: allow --end-of-options as a synonym for --", 2019-08-06, Git v2.24.0-rc0 -- merge listed in batch #2), we made them true synonyms.
They both stop option parsing, and they are both returned in the resulting argv if the KEEP_DASHDASH flag is used.

The hope was that this would work for all callers:

  • most generic callers would not pass KEEP_DASHDASH, and so would just do the right thing (stop parsing there) without needing to know anything more.
  • callers with KEEP_DASHDASH were generally going to rely on setup_revisions(), which knew to handle --end-of-options specially

But that turned out miss quite a few cases that pass KEEP_DASHDASH but do their own manual parsing.
For example, "git reset"(man), "git checkout"(man), and so on want pass KEEP_DASHDASH so they can support:

git reset $revs -- $paths

but of course aren't going to actually do a traversal, so they don't call setup_revisions().
And those cases currently get confused by --end-of-options being left in place, like:

$ git reset --end-of-options HEAD
fatal: option '--end-of-options' must come before non-option arguments

We could teach each of these callers to handle the leftover option explicitly.
But let's try to be a bit more clever and see if we can solve it centrally in parse-options.c.

The bogus assumption here is that KEEP_DASHDASH tells us the caller wants to see --end-of-options in the result.
But really, the callers which need to know that --end-of-options was reached are those that may potentially parse more options from argv.
In other words, those that pass the KEEP_UNKNOWN_OPT flag.

If such a caller is aware of --end-of-options (e.g., because they call setup_revisions() with the result), then this will continue to do the right thing, treating anything after --end-of-options as a non-option.

And if the caller is not aware of --end-of-options, they are better off keeping it intact, because either:

  1. They are just passing the options along to somebody else anyway, in which case that somebody would need to know about the --end-of-options marker.
  2. They are going to parse the remainder themselves, at which point choking on --end-of-options is much better than having it silently removed.
    The point is to avoid option injection from untrusted command line arguments, and bailing is better than quietly treating the untrusted argument as an option.

Note that this does shut the door for callers which want to know if we hit end-of-options, but don't otherwise need to keep unknown opts.
The obvious thing here is feeding it to the DWIM verify_filename() machinery.
And indeed, this is a problem even for commands which do understand --end-of-options already.
For example, without this patch, you get:

$ git log --end-of-options --foo
fatal: option '--foo' must come before non-option arguments

because we refuse to accept "--foo" as a filename (because it starts with a dash) even though we could know that we saw end-of-options.
The verify_filename() function simply doesn't accept this extra information.

So that is the status quo, and this patch doubles down further on that.
Commands like "git reset" have the same problem, but they won't even know that parse-options saw --end-of-options! So even if we fixed verify_filename(), they wouldn't have anything to pass to it.

But in practice I don't think this is a big deal.
If you are being careful enough to use --end-of-options, then you should also be using "--" to disambiguate and avoid the DWIM behavior in the first place.
In other words, doing:

git log --end-of-options --this-is-a-rev -- --this-is-a-path

works correctly, and will continue to do so.
And likewise, with this patch now:

git reset --end-of-options --this-is-a-rev -- --this-is-a-path

will work, as well.

Cecilia answered 10/9, 2019 at 21:19 Comment(1)
+1: Thanks for this far-more-than-we-thought-we-needed-to-know compendium about Git options parsing. For me, all this it boils down to: "better not use --foo as a branch, tag or filename". But it's nice to be able to write robust scripts that could deal with such repos safely.Leckie

© 2022 - 2024 — McMap. All rights reserved.