进行更改并将它们提交到分离的头部状态

making changes and committing them in in the detached head state

我使用 git checkout <commit_SHA> 访问了 git 树中的早期提交。 Git 向我展示了以下消息:

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 switching back to a branch.

这是否意味着当我 return 到最近的提交(使用 git checkout master)时,我在这里所做的任何更改(即使通过提交)都不会保留?

detached HEAD 状态中的更改将一直保留,直到 Git 垃圾回收 (GC) 将其删除。

Detached HEAD的文档很不错:

It is important to realize that at this point nothing refers to commit f. Eventually commit f (and by extension commit e) will be deleted by the routine Git garbage collection process, unless we create a reference before that happens.

这意味着,您可以 checkout / switch 到另一个引用的分支。 (例如 git switch main)或者您创建一个新分支以使用 git switch -c newbranchgit checkout -b newbranchname.

保留当前分离的工作树

当您创建一个新分支时,工作树不在 detached HEAD 状态并且有一个引用。 (新分支名称)

描述了如何禁用自动垃圾收集,如果您愿意的话。

Git 通过哈希 ID 查找提交。哈希 ID 是那些大而难看的字母和数字字符串,例如 e1cfff676549cdcd702cbac105468723ef2722f4。这些看起来是随机的,但不是。

如果您记下 每次提交的哈希 ID,您至少可以在一段时间内取回它们。但是,如果您在复制这些哈希 ID 时犯了错误或打字错误怎么办?最好让计算机保存它们。

这就是 分支名称 的作用。但事实上,它只保存了一个哈希ID。这就是您真正需要写下的全部内容。每次在“分离的 HEAD”状态下进行新提交时,您都必须记下刚刚进行的 new 提交的哈希 ID。您可以删除任何先前提交的哈希 ID(尽管您不必这样做)。

这就是它的工作原理。每次提交都会保存 两个 东西:

  • 每个提交存储每个文件的完整快照(Git 在您或任何人进行提交时知道)。这些文件以特殊的、压缩的 read-only、Git-only 格式存储,文件为 de-duplicated,因此如果新提交 re-uses 大多数文件来自旧提交,他们实际上并没有采取任何 space.

  • 并且,每个提交都会存储一些 元数据: 信息,例如您的姓名和电子邮件地址,以及一些 date-and-time-stamps。在此元数据中,Git 存储 上一个 提交的哈希 ID,该提交恰好在您刚刚进行的新提交之前。

所以,如果我们有一个连续的提交链,我们可以这样绘制它们:

... <-F <-G <-H

其中 H 代表这些提交的 last 的实际哈希 ID。 Git 可以使用哈希 ID 将提交 H 从其大的 database-of-all-commits,1 中拉回。这会得到 Git 保存的快照,加上元数据。元数据存储早期提交的原始哈希 ID G.

Git 可以使用它来将提交 G 从其数据库中拉回,这将获得一个不同的保存快照,以及 G 的元数据......其中包括哈希早期提交的 ID F。所以现在 Git 可以抓取 F,它有一个快照和元数据。这一直持续下去:Git 从 last 提交到第一个 backwards 工作。

但是你,或某人或某物,必须得到 Git 这个 last 哈希 ID。这就是分支名称有用的地方:根据定义,分支名称将 last 哈希 ID 存储在链中。如果你:

git checkout somebranch

(或使用 git switch 做同样的事情)你会得到一些我们可能会画成这样的东西:

...--F--G--H   <-- somebranch (HEAD)

特殊名称 HEAD 会记住 您告诉 Git 使用哪个名称名称 包含哈希 ID H。如果你现在进行新的提交,Git 将写出一个新的提交,它会得到一个新的 random-looking(但唯一且实际上根本不是随机的)哈希 ID,我们将其称为 I. Git 然后将 I 的哈希 ID 写入名称 somebranch:

...--F--G--H--I   <-- somebranch (HEAD)

这就是 Git 记住哪个提交是 最后一个 的方式。在分行名里!


1这个大数据库实际上保存了Git的所有内部对象。提交只是四种类型对象中的一种。 Git 存储库基本上是两个数据库:一个大数据库和一个较小的数据库(好吧,通常较小的数据库),它将名称(如分支名称)映射到哈希 ID。较小的数据库让您找到哈希 ID,而大数据库保存提交。


分离头模式

detached HEAD模式下,你告诉Git:不要存储一个name特殊名称 HEAD,改为存储原始哈希 ID。 例如,假设您决定查看历史提交 G:

...--F--G   <-- HEAD
         \
          H--I   <-- somebranch

您现在可以查看来自提交 G 的文件。如果你现在做一个 new 提交,Git 像往常一样存储新的提交:它有一些丑陋的大哈希 ID,它是唯一的,但我们称它为 J:

          J   <-- HEAD
         /
...--F--G
         \
          H--I   <-- somebranch

现在假设你再次git checkout somebranch,回到这个:

          J   ???
         /
...--F--G
         \
          H--I   <-- somebranch (HEAD)

name HEAD 现在包含 name somebranch,而不是提交的实际哈希 ID J。你将如何找到提交 J?

引用日志

如果您记下哈希 ID,这是找到它的一种方法。 Git 默认情况下会挂起提交 J 至少 30 天,您可以查找哈希 ID 并再次输入。那是......充其量是痛苦的。

Git 还 为您保存哈希 ID 在 Git 调用的 reflog 中。 reflogs 使用起来也有点痛苦。 运行 git reflog 随时,Git 将向您显示 HEAD reflog 中的内容。哈希 ID 是 HEAD 指向的每个提交的真实名称,2 无论是直接(分离的 HEAD)还是在在过去 30 天或更长时间内直接(通过分支机构名称)。但通常有数百个,maze of twisty little hash IDs, all alike中找到一个有用的并不好玩。


2这些是为了显示而缩写的。它们也有带编号的名称,例如 HEAD@{3}HEAD@{14}。每次 Git 添加一个 reflog 条目时,数字 增加 ,而哈希 ID(缩写或完整)始终保持不变。


那么你应该做什么?

如果您不关心稍后再次查找您的提交,只需继续在 detached-HEAD 模式下工作。如果您确实 想稍后找到它们,请创建一个新的分支名称。分支名称是 super-cheap:它们只是持有那些丑陋的大哈希 ID 之一。

使用 git branch newname 创建新分支名称 newname 无论您现在身在何处。然后使用git checkoutgit switch切换到它,使HEAD附加到那个名字。或者,结合这两个步骤:git checkout -b newnamegit switch -c newname 表示 创建名称,然后检查/切换到它 ,一次完成。