修复 GIT 存储库中的历史记录

Fixing history on GIT repository

我的 GIT 存储库中存在以下情况。有人忘记在进行更改之前拉入 master,然后提交给本地 master。在那之后,出于某种原因,他将 origin/master 合并到他的本地 master 中,然后将其推送。结果是 origin/master 有点 "switched places" 和他当地的主人。我说的有道理吗? 这是一个例子:

推送之前

x----x-----x----x----x----x----x-----x----x (MASTER)

推送之后

 ---------------------------------------------x---x (MASTER)
|                                                 |
x----x-----x----x----x----x----x-----x----x-------

这有点搞砸了存储库,因为现在所有的历史似乎都在一个分支上。

在那之后,有一些新的提交被推送到新的 master,然后出于一个现在不重要的原因,我们决定不想要那些,所以我们设法放弃了我们没有的提交想要,同时将 de MASTER 恢复到原来的位置。像这样:

之前

 ---------------------------------------------x---x---x---x---x (MASTER)
|                                                 |
x----x-----x----x----x----x----x-----x----x-------

之后
                                             (2)
 ---------------------------------------------x---x---x---x---x-- 
|                                               |                |
x----x-----x----x----x----x----x-----x----x-----x----------------x (MASTER)
                                         (1)                    (3)

如您所见,现在由忘记 tu pull 的人提交的提交已合并到最初的主提交中。这是这样实现的:

git checkout <HASH OF COMMIT MARKED AS (1) >
git checkout -b refactor_master
git merge --no-ff <HASH OF COMMIT MARKED AS (2) >
git push origin refactor_master
git merge --strategy=ours mastergit checkout master
git merge refactor_master
git push origin master

这有效地使那些提交合并的更改从主控中消失了,并且也将主控变成了它以前的样子。但是,我现在有一个不应该存在的"branch"。事实上,标记为 (3) 的最后一次提交并未进行任何更改。这只是"switches"的高手。有没有办法让这些提交消失?

git 分支仅仅是指向单个提交的标签。 提交不知道当前指向哪个分支 is/are对它;它也不知道以前哪些分支指向它的历史。因此,唯一真正重要的(并且是非常重要的改变)是提交历史本身。

清除此问题的最简单方法可能如下:找到您认为代表代码库合理状态的最新提交,以及 运行 以下命令(假设该提交的哈希是 123abc):

git checkout -B master 123abc
git push -f origin master

这将使 master 指向 123abc 在本地(在执行这些命令的 运行 的机器上)和服务器上。当其他开发人员 运行 git fetch 时,他们的 origin/master 将移动到 123abc,他们可以查看并移动他们自己的 master 和 [=18] =](不过,我不完全确定此命令的语法,而且我手头没有 git 存储库。)

警告:除非您有一个分支指向比 123abc 更新的提交,否则这些提交似乎会消失。如果你想稍后查看它们的内容以便清理它并重新提交它,你应该首先为这些提交创建分支,例如git branch tempbranch 567def.

确实有道理:他的所作所为违反了"main line of development is first-parent"规则。

请注意,git 本身没有任何内容可以强制执行此规则。这是不可能的,原因很简单:谁定义了 "main line" 是哪一行?该问题的唯一可能答案是 "you",其中 "you" 表示 "whoever runs git to manipulate the commit-graph"。所以这并不是真正的 git 规则,而是 "people who use git" 规则。

每当你运行git merge(或者本例"he"运行s),你选择你当前的分支作为开发的主线,随便您正在合并为正在合并的备用行。因此,如果您这样做:

$ git checkout master
$ make-some-change; git add ...; git commit -m message

$ git fetch origin # and let's assume this brings in a new commit
$ git merge origin/master

你是在告诉git把你的master作为主线,合并上游的改动作为支线。

请注意,最后两个命令——git fetch 后跟 git merge——是 git pull 默认执行的操作。反过来,这意味着 "main line is first-parent" 经常被违反并且不能依赖,除非你非常严格/小心。


Is there any way to make those [merge] commits disappear?

是的,但只能通过编写新的提交行 ("rewriting history")。

让我看看你的最终图表(不用担心你是如何到达那里的)并对绘图进行一些小的修改以获得更紧凑的表示:

  ------------------------A---M1--B--C--D
 /                           /           \
o--o--o--o--o--o--o--o--o---x-------------M2   <-- master

此时提交 BD 是 "on the wrong line" 因为合并提交 M2 的第一个父级是 x,它的第二个父级是D。同时提交 AM1 的第一个父级,xM1 的第二个父级。

如果你真的很关心第一父规则,你可以从提交 x:

中创建一个 new 行提交
  ------------------------A---M1--B--C--D
 /                           /           \
o--o--o--o--o--o--o--o--o---x-------------M2   <-- master
                             \
                              A'--B'--C'--D'   <-- new-master

这里 A' 的第一个也是唯一的父级是提交 x,它是 master 的尖端提交,当时事情是第一次 "went wrong"。然后B''s first and only parent is A',依此类推

如果在获得此图表后,从白板上擦除 AM2 并使 master 指向提交 D',您将拥有这个:

o--o--o--o--o--o--o--o--o---x
                             \
                              A'--B'--C'--D'   <-- master

现在您可以 "straighten out" 从 xA' 的 link,它看起来像一个很好的线性历史。

这里是棘手的部分:这只是您想要的 graph。对于图中的每个提交,git 保留一个 :当您 git checkout 该提交时要放入您的工作目录的一组文件。从 A'D' 每次提交所需的 tree 可能与 AD 上的原始树不完全相同.

可以肯定的是,B'C'D' 所需的树与 BC,和 D。但是,您想要用于新提交 A' 的树可能是当前正在合并的树 M1。此 可能 与提交 A 下的相同,但也可能不同。这实际上取决于 AM1 相比如何。

有很多相对棘手的方法可以在无需大量手动工作的情况下构建新提交,但很难用文字描述。此外,这种 "history rewrite"——当你强行让旧的 master 标签指向 new-master 的提交 D' 时发生的部分——给所有开发人员带来痛苦,他们正在提交将 M2 作为其父提交的提交。他们必须将这些提交复制到以新 D' 作为父项的新提交。

这种痛苦是否值得,取决于你和他们。