对新分支上的旧提交进行硬重置也会重置 master 吗?

Will doing a hard reset to an old commit on new branch will reset the master as well?

我想看看上一次提交时的设计是什么样的,所以我想签出到一个新分支,然后 revert/hard 重置为那个旧提交。

我对提交还很陌生,不想通过随机试验破坏任何东西,所以在新分支上硬重置到旧提交将硬重置主分支以及之前的提交。

我想知道上述问题的答案以及正确的做法(对于 git 初学者)。

谢谢 dk

so I want to checkout to a new branch and then revert/hard reset to that older commit.

实际上,我认为你想以相反的顺序执行此操作,即:

  • 通过 git checkout <SHA-1 hash of earlier commit>
  • 检查早期的提交
  • 通过 git checkout -b new_branch
  • 从那里签出一个新分支

当您签出较早提交的分支时,您将处于分离的 HEAD 状态。从那里创建一个新分支应该会给你留下你想要的东西。

在深入探讨以下内容之前(您应该阅读和了解,因为您最终会需要它),我想提一下,您可以使用 git checkout -b <commit-id> 以更简单的方式做到这一点。这具有检查现有(较旧)提交并创建指向该提交的 new 分支名称的效果。这就像执行下面的 git branch 然后 git checkout 然后 git reset,全部一步完成。

使用git reset

git reset 的作用有点难以解释,但很容易说明,特别是如果我们有颜色...而我们没有。所以,让我们分多个部分来做。

绘制DAG

首先,我们在存储库中有一组实际的提交。这些提交形成了一个图表——特别是 Directed Acyclic Graph 或 DAG,尽管现在我们只需要名字,"DAG" 是一个 nice short name。这里要知道的主要事情是每个提交 "points to" 它的 parent 提交,并且这些提交是非常可靠和永久的。无论我们做什么,这些提交都会保留在图中,至少会保留一段时间。稍后我们将讨论异常("some time" 部分)。

如果我们将较早的提交向左绘制,而较晚的(较新的)向右提交,则它看起来像这样:

       o <- o
      /
o <- o
      \
       o <- o

(斜杠 / 代表 down-and-left 箭头,并非所有字体都可用;反斜杠 \ 代表 up-and-left 箭头).每个 o 节点代表一个提交,箭头代表 parent 指针。这个特定的图表有六个提交,没有合并。

这些提交中的每一个都有一个唯一的 SHA-1 哈希名称,例如 a123456...。这些名称永远不会改变:每个名称都特定于它的一个特定提交。您可以随时使用这些名称,但当然它们很难输入。 (有时我用鼠标copy-and-paste这些一长串的数字。)

标记 DAG

因为我们知道早期的提交是向左的,后来的提交是向右的,并且提交指向它们的 parents,所以我通常会把它画得更紧凑。此外,我们通常想知道这些提交在哪些分支上,所以让我们添加一些分支名称:

      master
        |
        v

     o--o
    /
o--o
    \
     o--o

        ^
        |
     develop

这是相同的六个提交,但现在我们知道它们在哪个分支上:前两个提交——在左边,在垂直中间——在两个 分支,然后有两个提交仅在 master(顶部)和两个仅在 develop(底部)上的提交。请注意,分支名称指向此处的 tip-most commit

移动分支名称

关于分支标签接下来要了解的是它们 移动 。提交是可靠且永久的,但标签就像小鸟,从一个提交飞到另一个提交。让我们通过向 develop 添加一个提交来使图表更大一点,看看标签会发生什么:

      master
        |
        v

     o--o
    /
o--o
    \
     o--o--o

           ^
           |
        develop

我们开始了:它感动了!它仍然指向 tip 提交,但是那个 tip 提交是我们刚刚添加的那个。

添加新标签(新分支)

现在让我们给这个图添加一个 new 标签——一个新的分支。我也会将新标签指向 develop 的尖端。为了腾出空间,我会将现有标签绘制到右侧,而不是上方和下方,但请记住,这些是可移动的标签。

     o--o      <-- master
    /
o--o
    \
     o--o--o   <-- develop

           ^
           |
          new

使用git reset

现在,假设我已经完成 git checkout new,所以我在分支 new 上,让我们看看 git reset 对标签做了什么。具体来说,让我们获取旧提交并获取其编号,例如 fdeee3df9f54372c31506eb24f2b7f2339ba21ec(此特定编号是 Git 版本 2.8.1):

$ git branch new
$ git checkout new
Switched to branch 'new'
$ git reset --hard fdeee3df9f54372c31506eb24f2b7f2339ba21ec
HEAD is now at d95553a Git 2.8.1

Git 的存储库图表本身太大而无法绘制,但假设我是在一个较小的存储库中完成的,比如现在只有 7 次提交的存储库(当然哈希不再是 fdeee3d...)。那么我现在可能有这个:

     o--o      <-- master
    /
o--o
    \
     o--o--o   <-- develop

     ^
     |
    new

(假设我给了 Git 正确的散列)。如果我给 Git 存储库中第一个提交的哈希值,我得到这个:

     o--o      <-- master
    /
o--o
    \
^    o--o--o   <-- develop
|
new

git reset移动分支标签

这里的重点是git reset所做的是移动分支标签

由于我在分支 new 上,git reset 移动的标签是 new

小心一点 git reset

如果我现在 git checkout develop 并且 git reset 指向后退一步呢?也就是说,假设我让图表看起来像这样:

     o--o      <-- master
    /
o--o
    \
^    o--o--o
|       ^
new  develop

请注意不再有任何箭头指向分支 develop 的 tip-most 提交?

发生这种情况时,该提交现在是 "abandoned" 或 "unprotected"(更准确的术语是 未引用)。1 未受保护的提交有资格进行 垃圾收集 git gc 命令(根据需要自动为您调用,因此您通常不会r 必须 运行 它) 会找到这些废弃的剩菜并回收它们以取回您的磁盘 space。

最终,然后,在 git reset 移除 develop 上的额外提交后,Git 将真正删除它,我们将拥有:

     o--o      <-- master
    /
o--o
    \
^    o--o
|       ^
new  develop

也就是说,我们将回到只有六个提交。

当然,分支名称保护其分支的尖端提交,但它也保护尖端处 而非 的所有提交,因为在 Git ,我们始终可以跟随箭头,并将提交点返回(在这些图中向左)到他们的 parent 提交。我们可以通过以名称开始,跟随箭头指向一个提交,然后跟随提交的箭头指向其他提交来到达的任何提交——所有这些提交都是 reachable,因此受到保护并保留在永远的DAG。

因此,您必须至少小心一点 git reset,以确保您有 一些 标签——通常是一些分支名称——仍然指向你想保留的承诺。创建一个 new 分支名称,然后移动它,保证你没问题。

小心git reset --hard

虽然 all git resets 移动分支标签,但 --hard 重置也做了其他事情:它删除了你的 index/staging-area 和在您的 work-tree 中,通过将它们重置为您要 git reset 移动分支标签的 相同 提交。通常情况下,就像您的情况一样,这正是您想要的。

事实上,有时您不想移动分支标签,但确实想要重置您的索引和work-tree。在这种情况下,您可以 运行 git reset --hard 而不命名特定的提交。 Git 将 "move" 分支标签从它的旧提交到 ... 现在的任何地方。也就是说,它实际上停留在原处。然后 Git 将继续重置索引和 work-tree,即清除您所做的任何更改。如果您在代码上工作了一段时间并决定重新开始,这就是您想要的:"set everything back to the way it is in the most recent commit." 使用 git reset --hard 即可。它仍然移动分支标签,但是将它从 所在的位置移动到 它所在的位置,意味着你只能 看到 "reset the index and work-tree" 效果。


1幸运的是,Git 通常将每个正常提交 semi-protected 保留至少 30 天,使用 Git 调用的 [=130] =]reflogs。每个分支都有自己的 reflog,HEAD 也有一个大的 reflog,默认情况下这些会记住 30 到 90 天的提交 ID。只要 reflog 记住——引用——提交 ID,提交就不会被垃圾收集。