无需refspec参数即可运行git push、pull和fetch命令

5
来自Loeliger的《使用Git进行版本控制》第二版,
如果在 git push 命令中没有明确指定refspec,那会怎样?Git如何知道要做什么或将数据发送到哪里?
首先,在命令中未给出显式的远程名称时,Git会假定您想要使用 origin。 没有指定refspecgit push 将向远程推送与您的存储库和上游存储库之间的所有共同分支相关的提交。任何本地分支如果还未存在于上游存储库中,则不会被发送到上游;必须已经存在并且匹配名称的分支才能被发送。
  • “与您的存储库和上游存储库之间的所有共同分支”是否与本地存储库中的所有远程跟踪分支以及远程存储库中相应的跟踪分支相同?
  • 该引用是针对git push的。它(特别是没有refspec的部分)是否适用于git fetchgit pull

没时间多说了,但关于“假设...origin”:我认为这在某个时候是正确的,但那是相当久以前的事了。现在的Git默认使用branch.<branch>.remote,除非(自1.8.3版本以来)你设置了branch.<branch>.pushremote和/或remote.pushdefault - torek
1个回答

6
很抱歉,这个答案又有点长了,因为问题所做的假设——至少今天——是错误的。请跳到粗体部分获取实际答案,但前面的所有内容也很重要。
你引用的书本文已经过时,现在是完全错误的(这是软件不断发展的危险,书本会过时)。如果没有明确指定远程命令,Git 会默认使用 origin。我快速浏览了一下 Git 的 git 历史记录,并没有找到行为更改的确切时间,但在 Git 1.8.2.1 中,文档已经发生了变化,现在的文档如下所示:当命令行没有指定推送位置时,会查阅当前分支的 branch.*.remote 配置来确定推送位置。如果配置缺失,则默认为 origin。
(这种行为也与git fetch相匹配——毫不奇怪,因为它们使用同一段源代码来获得该结果。)这未提及可选的branch.$branch.pushremote设置,该设置会覆盖branch.$branch.remote。在所有这些情况下,$branch代表您当前的分支,如git symbolic-ref --short HEAD的输出所示。(如果您处于“分离头”模式,以至于git symbolic-ref失败,则git push通常会给出“fatal: You are not currently on a branch”错误,我认为自2.0版本起默认值已更改。)
这仍然没有涵盖全部内容;git-config文档添加了另一个怪异之处:

remote.pushDefault
默认情况下要推送到的远程仓库。覆盖所有分支的 branch.<name>.remote 设置,并被特定分支的 branch.<name>.pushRemote 设置所覆盖。

(幸运的是,就今天而言,我认为这已经涵盖了所有内容。也就是说,git 首先会查找当前分支的 pushremote 设置。如果没有该设置,则接下来它将查找 remote.pushdefault。如果也没有设置,它会回退到当前分支的 remote 设置。旧版本 git 的行为可能会有所不同。注意,配置项不区分大小写:pushremotepushRemote 是相同的配置名称[这让我想知道分支名称部分是否也不区分大小写;我以后会测试一下]。)

关于 refspecs 的下一部分更加错误,这是因为 git 随着时间的推移获得了几个变化,其中一些甚至在 git 的内置文档中也没有很好地记录。你引用的内容如下:

没有指定 refspec,git push 命令会将你的提交推送到本地库和上游库之间共同存在的所有分支。根据新文档,当命令行没有指定用 <refspec> 参数或 --all--mirror--tags 选项推送什么时,它会通过查找 remote.*.push 配置来找到默认的 <refspec>,如果未找到,则遵循 push.default 配置来决定要推送什么(有关 push.default 的含义,请参见 git-config)。git 版本 1.8 左右添加了 push.default 设置,但其默认值在 git 2.0 中更改了。如果您尚未配置 push.default,则会收到此警告:
warning: push.default is unset; ...

自从配置项可用以来。查阅git-config文档,您现在会发现这个:

push.default
定义了如果没有显式给出refspec时git push应该执行的操作。不同的值适合特定的工作流程...

有五个选项,我将简单列出它们(有关详细信息,请参见链接的文档):nothingcurrentsimpleupstreammatching。您引用的书中的文本描述了matching,这是git版本2.0之前的默认行为。新的默认值是simple

git push上,如果您配置了“匹配”行为

“您的存储库和上游存储库之间共同的所有分支”是否与本地存储库中的所有远程跟踪分支和远程存储库中的相应跟踪分支相同?

不是。

我建议您尝试运行git ls-remote origin(或将origin替换为任何其他远程名称):
$ git ls-remote origin
28274d02c489f4c7e68153056e9061a46f62d7a0        HEAD
1ff88560c8d22bcdb528a6629239d638f927cb96        refs/heads/maint
28274d02c489f4c7e68153056e9061a46f62d7a0        refs/heads/master
0ac5344e619fec2068de9ab2afdb99d1af8854be        refs/heads/next
5467631257c047b16d95929559dd1887d27b0ddc        refs/heads/pu
68a0f56b615b61afdbd86be01a3ca63dca70edc0        refs/heads/todo
d5aef6e4d58cfe1549adef5b436f3ace984e8c86        refs/tags/gitgui-0.10.0
3d654be48f65545c4d3e35f5d3bbed5489820930        refs/tags/gitgui-0.10.0^{}
[snip -- lots more omitted]

然后尝试使用git for-each-ref refs/heads

$ git for-each-ref refs/heads

你会看到类似的输出,但SHA-1不同,并且单词 commit 夹在中间。
重点是在refs/heads中列出的每个“head” - 在来自 ls-remote for-each-ref 的两个输出中都是如此 - 代表了git push在进行“matching”时考虑的(本地)分支。匹配的头将添加到推送列表中,给出一组refspecs(以内部形式表示,但可以轻松表达为name:name或者,如果使用了--force标记,则+name:name)。
即使这些本地分支没有配置upstream也会发生这种情况。因此,如果他们(远程)有一个 refs/heads/bob 供Bob Roberts使用,而您有一个 refs/heads/bob 用于了解您的管道卡扣或钓鱼浮标等信息,git将尝试匹配它们,即使您的 bob 没有上游流。

关于pushfetch

这段引用是针对git push的。它(特别是没有refspec的情况下)是否适用于git fetchgit pull

不适用。

git fetch 的行为更容易解释,因为它不会像 git pull 那样对你进行所有奇怪的操作。 git pull 命令应该是方便的,但是 git pull 试图将一个基本上是针对整个存储库的“获取”操作与一个基本上是针对分支限制的“合并”操作融合在一起。 这两者不能很好地协作1。(最好的情况下,我认为 git pull 可以在某些时候被修改为执行一个更简单的、针对整个存储库的“获取所有内容,然后根据跟踪配置和参数成对地检出和合并一些分支”的顺序。 我认为这将使其表现出大多数人所期望的方式。 它也将在很大程度上不兼容,并且仅对最简单的情况(默认情况):获取所有内容,然后仅合并当前分支。 但这只是猜测。)
如果省略所有refspecs,git fetch将读取您的remote.$remote.fetch配置行。(请注意,“省略所有refspecs”子句意味着您还必须避免使用旧的“命名文件在$GIT_DIR/branches”方法,例如。)通常情况下,名为origin的远程仓库的默认配置行(单数形式)如下所示(查看本地存储库配置文件即可看到)。
[remote "origin"]
    +refs/heads/*:refs/remotes/origin/*

这是“远程跟踪分支”产生的实际机制。 fetch 过程始于同样的 git ls-remote origin 步骤,获取来自远程仓库的每个引用(分支、标签等)。fetch 代码接着将其与 [remote "origin"] 下的每个 fetch = 行进行匹配。左侧 refspec 上的星号有明显的含义;右侧的星号则被替换为左侧星号所匹配的内容。(在当前版本的 git 中,* 必须匹配“整个部分”,宽泛地定义为“斜杠之间的内容”,但在即将推出的 git 版本中,此限制将得到放宽——尽管我不确定能带来什么有用的结果。)
你可以拥有任意数量的 fetch = 行。每个原始引用都会通过每行进行运行,以获取新的(替换后的)引用:如果左侧匹配,则右侧提供替换内容。(如果右侧缺失或为空,则由于历史原因和保持 git pull 的工作,行为会变得有些奇怪,部分原因是自 git 1.8 左右的期望行为发生了改变。)
所有这些替换的结果形成了重命名后的引用,并确定哪些引用被复制; 但是每个原始引用最多只能有一个结果:例如,您不能将"their" refs/heads/foo映射到"your" refs/remotes/origin/bar和your refs/remotes/origin/zoink。要使存储库充当镜像,请使用+refs/*:refs/*,以不重命名的方式带入所有引用(但这将在每次获取时覆盖您的本地分支,因此只有在--bare的情况下才有意义)。(基于启动协商期间其他方发送的内容删除“死”引用——通过设置--prune标志单独完成。)

1它并非一开始就是这样。事实上,最初的pull脚本早于远程和远程跟踪分支的出现。提交7ef76925d9c19ef74874e1735e2436e56d0c4897pull拆分为pullfetch,然后两者随着时间的推移朝不同的方向发展。一旦“远程”和“远程跟踪分支”出现,它们已经发展到混合它们只会带来麻烦的地步,现在更加糟糕了。 :-)


pushremote 部分让我想起了 git 2.5 引入的 @{push} 简写:https://dev59.com/RnI-5IYBdhLWcg3wF0Uc#30720302 - VonC
(2)对于 git fetch 命令,要获取的默认分支是那些既出现在远程仓库的 refs/heads/ 中,又出现在本地仓库的 refs/remotes/origin/ 中的分支? - Tim
(3)再次强调,fetch命令实际上遵循配置行的规则;而push命令在5个设置方面变得复杂了。这种疯狂背后有一定的道理:fetch通常被配置为安全地从远程复制内容:无论远程跟踪分支(refs/remotes/origin/...)发生了什么变化,都不会丢失你自己的工作,也不会将你自己的混乱和/或敏感数据交给其他人。然而,push操作通常并非如此:你直接推送到其他人也在使用的地方(例如refs/heads/master),因此新版本(2.0+)的默认设置更加保守,只允许输出特定内容。 - torek
(4)是的。所有与“fetch =”行和推送设置相关的疯狂愚弄最终都归结为(内部化的)refspecs。不幸的是,“git fetch”在跨越git 1.8.2版本边界时存在奇怪的问题,即使这个简单的答案也略微有些错误! :-)此外,在没有“--tags”或“--no-tags”的情况下获取标签的默认行为在refspecs方面是无法解释的(叹息)。 - torek
1
啊,对于 git fetch 来说,它只是“使用当前分支的远程仓库,如果没有设置则使用 origin”。 - torek
显示剩余10条评论

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