"remotes/origin/dev" 和 "origin/dev" 在你的本地仓库中有区别吗?

Is there a difference between "remotes/origin/dev" and "origin/dev" in your local repo?

在您的本地存储库中 "remotes/origin/dev" 和 "origin/dev" 有区别吗?

我似乎目睹了一些差异,但还没有向自己证明。

通常,remotes/origin/devorigin/dev 是表达同一事物的两种方式。但这并不一定如此!该问题类似于同时创建名为 Xbranch 和名为 Xtag 时发生的情况。在这种情况下,简单地写X是有歧义的,在不同的情况下,你可能要写不同的东西。

整个故事很长,但有必要了解正在发生或可能发生的事情。

Git 引用有全名

所有这些名称——像 master 这样的分支名称,像 v1.2 这样的标签名称,以及像 origin/master 这样的远程跟踪名称——都存在于所谓的计算机科学 /信息学,一个单独的名称spacenamespace。如果有歧义的问题,我们所做的与我们在派对上所做的一样,所有的男人都叫 Bruce,所有的女人都叫 Sheila:我们使用不同的 and/or 更长的名字。在编程语言中,我们倾向于称这些为 限定名称,它们看起来像 std::map。在 Git 中,我们只是称它们为 reference 名称,它们以 refs/ 开头,然后继续识别哪个 name space 我们的意思是。

Git现有的标准化参考名称-space是按字母顺序排列的,在我写这篇文章的时候:1

  • refs/heads/: 分支名称
  • refs/namespaces/:为递归保留的 space(git upload-packgit receive-pack 的特殊 hack,真的——这些不适合普通使用)
  • refs/notes/:被git notes
  • 使用
  • refs/remotes/: 远程跟踪名称
  • refs/replace/:被git replace
  • 使用
  • refs/stash(没有尾部斜杠,其中不能有名称):由 git stash
  • 使用
  • refs/tags/: 标签名称

这意味着如果您同时创建了 refs/heads/X,它是一个名为 X 的分支,以及 refs/tags/X,它是一个名为 X 的标签,你可以明确地 拼出 refs/heads/X 表示 branch Xrefs/tags/X 表示 tag X。标准规则有一些恼人的例外情况,但首先,让我们看看这些规则。


1名字 space 随着时间的推移而成长。


常规规则

一般来说,当Git要显示你一个参考时,它往往会根据一些简单的规则来缩短它。而且,如果你 使用 一个 不合格的 引用——我的意思是一个不以 refs/ 开头的名字——Git 有一个六步过程来弄清楚你的意思。 the gitrevisions documentation:

中描述了这个六步过程

When ambiguous, a <refname> is disambiguated by taking the first match in the following rules:

  1. If $GIT_DIR/<refname> exists, that is what you mean (this is usually useful only for HEAD, FETCH_HEAD, ORIG_HEAD, MERGE_HEAD and CHERRY_PICK_HEAD);

  2. otherwise, refs/<refname> if it exists;

  3. otherwise, refs/tags/<refname> if it exists;

  4. otherwise, refs/heads/<refname> if it exists;

  5. otherwise, refs/remotes/<refname> if it exists;

  6. otherwise, refs/remotes/<refname>/HEAD if it exists.

所以如果你写 master,并且 refs/heads/master 存在,你通常会得到名为 master 分支 (来自第 4 步)。因此,如果 Git 要打印 refs/heads/master,它可能只打印 master。类似地,如果你写 origin/dev,并且 refs/remotes/origin/dev 存在,你通常会得到它(从第 5 步)——所以如果 Git 要打印 refs/remotes/origin/dev,它可能只是打印 origin/dev.

有很多例外

如果你 运行 git branch -r, Git 脱掉 refs/remotes/:

$ git branch -r
  origin/HEAD -> origin/master
  origin/maint
  origin/master
  origin/next
  origin/pu
  origin/todo

这告诉我们 refs/remotes/origin/HEAD 存在于 refs/remotes/ 中,依此类推。这些将在上面的第 5 步中匹配。

但是,如果您 运行 git branch -a,Git 仅从远程跟踪名称中剥离 refs/

$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/maint
  remotes/origin/master
  remotes/origin/next
  remotes/origin/pu
  remotes/origin/todo

当前分支 master,实际上是 refs/heads/master,已经删除了两个组件:refs/heads/。但是远程跟踪名称,以前有两个组件被剥离,现在只有一个被剥离:例如 remotes/origin/master。这些仍然有效,事实上,它们会在第 2 步中更早地匹配。但为什么它们不一致?唯一的答案似乎是:它是传统的。

现在假设您不小心创建了一个名为 master 标签 ,即完全限定名称 refs/tags/master。根据六个步骤的列表,如果你写名字master,Git应该首先找到tag,因为那是第3步。让我们看看它是否做。首先,让我们看看哈希 ID master 名称是什么,然后选择一个不同的(较早提交的)哈希 ID:

$ git rev-parse master
b7bd9486b055c3f967a870311e704e3bb0654e4f
$ git rev-parse master~3
18f2717578853edfdaed5fb7361b5f992a68a79e

现在让我们用散列 ID 18f2717578853edfdaed5fb7361b5f992a68a79e 创建 标签 master,这样第 3 步就会找到这个 18f27... 东西而不是步骤4 找到 b7bd9... 东西:

$ git tag master 18f2717578853edfdaed5fb7361b5f992a68a79e
$ git rev-parse master
warning: refname 'master' is ambiguous.
18f2717578853edfdaed5fb7361b5f992a68a79e

啊哈:我们收到 警告 ,并且 Git 实际上找到了标签而不是分支。所以如果我们 运行 git checkout master,我们会检查 标签 ,对吧? 错了!

$ git checkout master
warning: refname 'master' is ambiguous.
Already on 'master'
Your branch is up-to-date with 'origin/master'.
$ git rev-parse HEAD
b7bd9486b055c3f967a870311e704e3bb0654e4f

git checkout 命令首先尝试将该名称作为 分支 名称,并找到提交 b7bd9486b055c3f967a870311e704e3bb0654e4f!它仍然给了我们警告,但它使用了分支名称。如果我们想要标签名称,我们必须将其完整拼写出来,或者使用 tags/master 通过步骤 2 获得它。我自己更喜欢完整拼写:

$ git checkout refs/tags/master
Note: checking out 'refs/tags/master'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 18f2717578... Merge branch 'ms/core-icase-doc'

最好删除多余的 master 并恢复理智:

$ git tag -d master
Deleted tag 'master' (was 18f2717578)
$ git checkout master
Previous HEAD position was 18f2717578... Merge branch 'ms/core-icase-doc'
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.

Git 充满了像这样奇怪的极端情况,找出每个命令的真正行为的唯一方法是试验(或避免模棱两可的情况)。

这对原始问题意味着什么

可以创建一个名为 origin/dev 分支 ——因此其全名是 refs/heads/origin/dev——或者一个 远程跟踪名称例如,其全名是refs/remotes/remotes/origin/dev。如果我们从这个姓氏中去掉两个名称部分,我们会看到 remotes/origin/dev,它看起来像我们从 git branch -a 中得到的那种名字,当它只去掉 一个 部分时.如果您使用各种着色选项,远程跟踪名称默认为红色,分支名称默认为绿色或黑色,因此其中一些会很突出。但绝对有可能让自己遇到一些糟糕的情况。

要查看所有参考文献及其全名,请使用 git for-each-ref。请注意,在具有许多标签或分支的存储库中,这会产生大量输出,因此我从 Git 存储库中删除了 Git:

的输出
b7bd9486b055c3f967a870311e704e3bb0654e4f commit refs/heads/master
b7bd9486b055c3f967a870311e704e3bb0654e4f commit refs/remotes/origin/HEAD
53f9a3e157dbbc901a02ac2c73346d375e24978c commit refs/remotes/origin/maint
b7bd9486b055c3f967a870311e704e3bb0654e4f commit refs/remotes/origin/master
5c9ce644c390ec4ef3ba4adc94e7f4af17ade36b commit refs/remotes/origin/next
1aaaa8cf15ba4eb62d485c5c8b64d6a75b9e7c3f commit refs/remotes/origin/pu
f59de5ad04b18866024fb298ddb276cb51d91673 commit refs/remotes/origin/todo
d5aef6e4d58cfe1549adef5b436f3ace984e8c86 tag    refs/tags/gitgui-0.10.0
33682a5e98adfd8ba4ce0e21363c443bd273eb77 tag    refs/tags/gitgui-0.10.1
ca9b793bda20c7d011c96895e9407fac2df9648b tag    refs/tags/gitgui-0.10.2
[mass snippage]
f883596e997fe5bcbc5e89bee01b869721326109 tag    refs/tags/v2.9.3
8d091e9ed473c372a5b89d1258d1c3ad01daa04c tag    refs/tags/v2.9.4
dcba104ffdcf2f27bc5058d8321e7a6c2fe8f27e tag    refs/tags/v2.9.5

这里的名字(在第三列)是完全限定的,所以你可以看看是否有什么奇怪的事情发生。您还可以仅检查名称的特定部分 -space,并使用 --format 指令来限制输出:

$ git for-each-ref --format='%(refname)' refs/remotes/origin
refs/remotes/origin/HEAD
refs/remotes/origin/maint
refs/remotes/origin/master
refs/remotes/origin/next
refs/remotes/origin/pu
refs/remotes/origin/todo

如果您认为自己处境不佳——尤其是 Git 警告您名字不明确时——您可以使用 git for-each-ref 分析您的实际情况,并据此制定您的计划恢复。