如何在 git 中修复 "warning: symbolic ref is dangling"

How to fix "warning: symbolic ref is dangling" in git

最近我开始注意到,在我的一个 git 存储库中,我 运行 的任何 git 命令都会收到以下警告:

warning: symbolic ref is dangling: refs/remotes/origin/HEAD

这是什么意思,我该如何解决?

如何修复

因为这个特殊的符号引用无论如何都没有用,我建议直接删除它:

git remote set-head origin --delete

如果您喜欢这个无用的符号引用,您可以让 Git 自动更新它:

git remote set-head origin --auto

这将使您的 Git 呼叫 origin 的另一个 Git,询问它当前的分支是 now,并适当更新您的符号参考 origin/HEAD

如果像这样更新它不能解决问题,请不要担心:它可能实际上并没有损坏,最终会 "self heal"。无论如何,在我看来,它无论如何都是无用的,所以即使坏了,这也没有什么坏处。

关于参考文献

让我们首先注意什么是 ref

  • ref 或引用是名称。名字有一些限制(见 the git check-ref-format documentation,但它主要只是一串字母和 digit 之类的,我们希望拼出一个对你有意义的名字。

    例如,分支名称是 ref。标签名也是如此。

  • 这些名字中的大多数实际上都是以 refs/ 开头的。唯一不总是被称为 refs(Git 对此并不完全一致):它们是特殊名称 HEADORIG_HEADCHERRY_PICK_HEAD,依此类推。 (它们要么都是 HEAD 本身,这是最特殊的,要么以 _HEAD 结尾。它们应该总是像这样全部大写。)

  • Refs 分为namespaces。您经常使用的两个是 branch 命名空间,它是每个拼写以 refs/heads/ 开头的引用,以及 tag 命名空间,这是名称以 refs/tags/ 开头的每个 ref。还有第三个你也经常使用:远程跟踪名称 是名称以 refs/remotes/ 开头的引用。在单词 remotes 及其尾部斜线之后,Git 添加相关遥控器的名称,例如 origin,以及另一个尾部斜线。

因此 ref 只是 Git 中任何分支或标记或远程跟踪名称或任何其他名称的一般形式。有一些不常用的和内部的,比如 refs/stash 用于 git stash 命令,refs/bisect/ 用于 git bisect,等等;通常你根本看不到这些。

您确实看到了分支、标签和远程跟踪名称,但是 Git 通常 删除 名称的 "namespace" 部分:

  • master 实际上是 refs/heads/master,但是 Git 去掉了 refs/heads/ 部分。
  • v1.2 实际上是 refs/tags/v1.2,但是 Git 去掉了 refs/tags/ 部分。
  • origin/master 实际上是 refs/remotes/origin/master,但是 Git 去掉了 refs/remotes/ 部分。

有时 Git 仅从远程跟踪名称中删除 refs/ 部分。完全不清楚为什么,但是 git branch -a 这样做了。这就是您在这里看到 remotes/origin/master 的原因。与 git branch -r 相比,您只能看到 origin/master。无论如何,这是表达每个名字的有效方式,因为解析你给的任何名字都有一个六步规则,如the gitrevisions documentation所示,三个其中一些步骤涉及尝试在您提供的任何名称前添加 refs/remotesrefs/headsrefs/tags。我们稍后会回到这个话题。

大多数 refs 只存储一件事:哈希 ID。对于分支名称和远程跟踪名称,该哈希 ID 必须是 commit 哈希 ID。对于标签,它通常是提交哈希 ID,或者 带注释的标签对象 的哈希 ID,然后存储提交哈希 ID。这个额外的步骤——遍历标签对象——允许你写一个带注释的标签,或者一个带有消息的标签。

但有些引用是 象征性的 引用。这意味着它们存储的不是哈希 ID,而是其他 ref 的 名称。最常见的——事实上,在我看来,唯一 有用的——是非常特殊的 HEAD ref.1

HEAD 通常包含分支的名称。也就是说,如果您查看实际文件 .git/HEAD,您会看到,例如:

$ cat .git/HEAD
ref: refs/heads/master

换句话说,名称 HEAD 仅包含 分支名称 master。这意味着 mastercurrent 分支。这就是 Git 实现附加 HEAD 的方式。因此,detached HEAD 只是 HEAD 持有提交哈希 ID 而不是分支名称的情况。这就是附加与分离 HEAD 的全部内容。 Git 只是问自己:HEAD 是否持有分支名称? 如果是,则该分支是 当前分支 并且存储在该分支名称中的提交哈希 ID 是 当前提交 。如果不是,HEAD 持有提交哈希 ID,该哈希 ID 是当前提交。


1HEAD 非常特别,因为除其他外,如果文件丢失,Git 将不再相信存储库 一个存储库!永远不要删除 .git/HEAD。如果在计算机崩溃后,您需要恢复 HEAD 文件丢失的存储库,您可以尝试创建一个包含 ref: ref/heads/master 的存储库,看看是否允许您继续。这种情况在崩溃后经常发生,因为 HEAD 是一个相对活跃的文件,计算机系统在崩溃后用来恢复的一种方法就是 删除 任何损坏的文件。


这一切意味着什么

一般来说,ref 必须 解析为某个有效的、现有的内部 Git 对象的提交或其他哈希 ID。2 例如,像 master 这样的分支名称只允许 存在 如果它包含一些现有的有效提交的哈希 ID。

如果一个 ref 持有其他 ref 的名称,则该其他 ref 需要存在,并解析为有效的哈希 ID。3 如果另一个 ref 存在,但是,我们可以说符号引用是悬空

这就是这个意思:

warning: symbolic ref is dangling: refs/remotes/origin/HEAD

您现有的 refs/remotes/origin/HEAD 是一个符号引用——所有远程跟踪 <em>remote</em>/HEAD 名称毕竟是符号引用— 但无论它包含什么名称,名称 都不存在。

注意:有一种情况你自己的HEAD是悬空符号引用。这通常发生在一个新的、完全空的存储库中。 HEAD 必须 存在,所以它确实存在。它应该包含分支的名称,因此它包含 ref: refs/heads/master。它是一个符号引用,表示您在分支 master 上。但这是一个全新的、完全空的存储库。其中没有有效的提交! 分支名称 master 不能存在,因为需要分支名称来保存有效的现有提交的哈希ID,并且有 none。所以 master 本身,分支名称,根本不存在。这使得 HEAD 成为悬空符号引用。

进行第一次提交 创建 名称 master,解决了这个问题。所以它自己解决了,这种情况没有错。因此,悬挂的符号引用是无害的。 HEAD 尤其如此(其中 Git 不会抱怨悬挂的符号引用),但 refs/remotes/origin/HEAD 也是如此:这些都是无害的。不过,与 HEAD 的特殊情况不同,它们 没有用


2这通常是一个提交,但标签可以指向 Git 的四种内部对象类型中的任何一种。当然,它们需要能够指向带注释的标签对象,这样您才能拥有带注释的标签。 Git 只是取消了这里的所有限制,这让它们也可以指向树或 blob 对象。

3请注意,在引用查找时解析的符号引用的引入也引入了循环的可能性。名称 A 可能引用名称 B,名称 B 引用 C,然后 C 可以返回到 A。你将如何解决这个问题?

练习:git如何解决这个问题?检测自环相对容易:如果名字tumbolia我的答案是通过查找名字"tumbolia"得到的,您可以立即看到,在查找 "tumbolia" 时,您将永远循环或递归地一遍又一遍地查找自己。但是长串的名字呢?

练习:您的操作系统可能提供符号linkssymlinks,它们是内容是其他文件名的文件。如果是这样,您的 OS 如何防止或检测符号 link 循环? (Git 用于通过 symlinks 实现符号引用。这在 Git 添加 Windows 支持时发生了变化,因为许多 Windows 系统不提供 symlinks.)


为什么这没用,除非你认为它有用

让我们回到 Git 用于解析您输入的名称的六步过程。例如,假设您 运行:

git rev-parse master

试试这个。尝试使用各种名称 运行ning git rev-parse,以及 master..develop 等范围,以及添加运算符的名称,例如 master~2。然后再去看the gitrevisions documentation,发现这个:

  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;

这五个规则中的三个可以只写 masterv1.2origin/master。规则 #5 处理 origin/master:因为 refs/heads/origin/master 存在,如果你 运行 git rev-parse origin/master 并且规则 1 到 4 不起作用,规则 5 起作用,这就是 git rev-parse 以有效的提交哈希 ID 停止。这就是 git rev-parse 打印的内容。

规则#4 处理分支名称。规则 #3 处理标签名称。请注意,规则 #3 意味着如果您创建一个名为 mastertagtag 将覆盖 branch,在大多数情况下。4 如果您遇到这种情况,您可以使用规则 #2,写成 heads/mastertags/master .

但这遗漏了最后一条规则。在这里:

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

这个特殊的规则意味着你可以 运行:

git rev-parse origin

Git 将尝试其他五​​个步骤,所有步骤都将失败,然后将尝试解析 origin,就像您编写 refs/remotes/origin/HEAD 一样。如果此名称存在 并且 可以解析为哈希 ID,这就是您将获得的哈希 ID。

所有这一切最终归结为您可以使用 remote 的名称作为 shorthand 作为 我的 Git 在该遥控器的 HEAD 下的符号引用中的远程跟踪名称。也就是说,如果您有名为 r1r2r3 的遥控器,您可以使用名称 r2 而不是写出 refs/remotes/r2/HEAD(不用需要六步过程)或 r2/HEAD(通过使用步骤 #5 起作用)。

您或任何人实际使用它的频率如何?这就是这些符号引用的用处。我认识的人从来没有用过它,据我所知,它没有用。


4假定或首先尝试将名称作为 分支 名称的命令将选择 分支意思相反。例如,git checkout master 将检查 分支 。但是 git rev-parse master 不假定分支名称,因此它解析 标记 ,在第 3 步找到它并且永远不会移动到第 4 步。

因此,使用相同的名称作为分支名称和标签名称是不明智的。 Git 的规则会选择其中之一,但它们可能不符合您的个人期望!