在Git中,“--”(破折号)的含义是什么?

97
阅读 Git 命令的 man 手册时,您经常会看到一个可选的 --(破折号)。根据我的经验,-- 不是必需的,也不会有任何影响。那么你什么时候需要它?考虑到它出现在许多命令中,它通常表示什么?

12
不是重复问题。这个问题要求对所有 git 命令中的双破折号有一个概念性的理解。而链接的问题只涉及到 git checkout 命令。 - Eric
2个回答

97
在git中,双破折号"--"对不同的命令有不同的含义,但通常它用于将选项与参数分隔开来。
在git中,--的含义取决于你使用它的子命令。它通常用于将子命令的参数(例如git checkout中的分支名称)与修订版本或文件名分隔开来。有时它是可选的,仅用于防止将不寻常的文件名解释为程序选项。
例如:
  • git checkout。要检出一个“提交”(在手册中称为“树状物”,因为您实际上可以指定一系列对象类型),您可以使用

    git checkout <commit>

    要将检出精确到一个或两个文件,请使用--将“树状物”参数与要检出的“文件名”分隔开。

  • git commit。要提交“索引”中的内容(即,通过git add暂存的内容),只需执行git commit命令。

    git commit [-m message]

    要忽略通过git add添加的内容,并提交特定文件中的更改,请使用git commit -- <filename>

  • git add。要提交以---开头的文件名,您必须告诉git add停止读取参数,并开始读取文件名;--就是这样做的。

    git add -- -sample.txt

  • git log。要查看仅限于影响某个文件的提交历史,请使用

    git log -- <filename>

你需要查看man手册,以了解你使用的任何git命令的具体含义。

1
很有趣。我从 https://git-scm.com/docs/git-diff 到这里来,但所有列出的示例中都没有 git-diff :) 不过我明白了,谢谢。 - trss

9
这个问题要求对所有git命令中的双破折号有概念性的理解。
双破折号表示选项结束,但在Git中被认为“不够用”。
随着Git 2.24(2019年第三季度)的推出,命令行解析器学会了“--end-of-options”符号:
脚本编写者的标准约定是在命令行上首先硬编码一组选项,并强制命令将最终用户输入视为非选项,使用“--”作为分隔符,但对于使用“--”作为修订和路径规范之间分隔符的命令来说,这种方法不起作用。

查看提交 67feca3, 提交 51b4594, 提交 19e8789 (2019年8月6日)由Jeff King (peff)完成。
(由Junio C Hamano -- gitster合并于提交 4a12f89, 2019年9月9日)

修订版: 允许 --end-of-options 结束选项解析

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

解析选项: 允许使用--end-of-options作为"--"的同义词

最近的修订选项解析器学会了使用--end-of-options,但这对于所有调用者来说还不够。
其中一些调用者,例如git-log,使用parse_options()挑选出一些选项,然后将其余部分传递给setup_revisions()
对于这些情况,我们需要停止parse_options()在看到--end-of-options时找到更多选项,并保留该选项在argv中,以便setup_revisions()也可以看到它。

让我们像处理"--"一样处理它。我们甚至可以利用PARSE_OPT_KEEP_DASHDASH的处理方式,因为任何想要保留一个的调用者也会想要保留另一个。

示例:

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

随着Git 2.30(2021年第一季度)的到来, "git rev-parse"(man) 学会了 "--end-of-options",以帮助脚本安全地获取参数,这些参数应该是一个修订版本,例如 "git rev-parse --verify -q --end-of-options $rev(man)"。

查看 提交3a1f91c, 提交9033add, 提交e05e2ae (2020年11月10日) 由 Jeff King (peff).
(合并自Junio C Hamano -- gitster --提交0dd171f, 2020年11月21日)

rev-parse: 处理 --end-of-options

签名: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现在在其手册页中包含了:

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

使用Git 2.31(2021年第一季度), "git mktag"(man) 在写入标签对象之前会使用自己的规则验证其输入 - 它已更新为与git fsck共享逻辑。这意味着它也支持--end-of-options
请查看以下提交记录:commit 06ce791(2021年1月6日),commit 2aa9425commit 3f390a3commit 9a1a3a4commit acfc013commit 1f3299fcommit acf9de4commit 40ef015commit dfe3948commit 0c43911commit 692654dcommit 30f882ccommit ca9a1edcommit 47c95e7commit 3b9e4ddcommit 5c2303ecommit 317c176commit 0d35ccbcommit b5ca549commit aba5377commit 18430ed(2021年1月5日),以及commit 9ce0fc3commit f59b61d(2020年12月23日)由Ævar Arnfjörð Bjarmason (avar)提交。(由Junio C Hamano -- gitster --commit c7d6d41中合并,2021年1月25日)

mktag: 转换为解析选项

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

将“mktag”命令转换为使用parse-options.h而不是自己的特定参数处理方式。这在实践中并不重要,因为它不支持任何选项,但可以消除代码库中的另一个特例,并使将来向其中添加选项更加容易。对于希望以一致的方式执行git命令并始终使用--end-of-options的程序,情况稍微有所改善。例如,“gitaly”就是这样做的,并且有一个内置黑名单,不支持--end-of-options。这是它和其他类似程序需要支持的一个特例。

1
+1:感谢这个关于Git选项解析的大全,它远远超出了我们所需要知道的范围。对我来说,所有这些都可以归结为:“最好不要将--foo用作分支、标签或文件名”。但是能够编写能够安全处理这样的仓库的强健脚本还是很不错的。 - ojdo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接