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

4
我有一个GIT存储库的以下情况。有人在进行更改之前忘记在主分支上拉取,然后在本地主分支上提交了更改。之后,由于某些原因,他将origin/master合并到了本地主分支,然后推送了该更改。结果是,origin/master和他的本地主分支有点像“交换了位置”。我的意思清楚吗? 以下是一个示例:
推送之前:
x----x-----x----x----x----x----x-----x----x (MASTER)

推送之后

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

那有点弄乱了代码库,因为所有的历史记录似乎都在一个分支上。之后,有一些新的提交被推到了新的主分支上,然后因为某个不重要的原因,我们决定不想要那些提交,所以我们成功地删除了我们不想要的提交,并同时将主分支还原到了它原来的位置。就像这样:BEFORE(之前)。
 ---------------------------------------------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)

正如您所看到的,由于那个忘记拉取的人提交的内容现在已经合并到了最初的主分支中。这是通过以下方式实现的:
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

这实际上使得那些提交所包含的更改从主分支中消失,同时将主分支恢复为之前的状态。然而,现在我有了一个本不应存在的“分支”。事实上,最后一个标记为(3)的提交并未做出任何更改。它只是“切换”了主分支。有没有办法让这些提交消失?

我不确定我完全理解你的意思,但我想说的是:Git没有“哪个分支”提交来自的概念。在你的第二张图中,它可以被渲染为大部分x在第一行和2个x在第二行;这样表示相同的提交图。 - Nayuki
为什么不将主分支回滚到发生问题之前的最后一次提交呢? - Matthew Herbst
“所有的历史记录现在似乎都在一个分支上”是什么意思?还有其他地方吗?主分支怎么会错了呢?分支有物理位置吗? - Sven Marnach
“所有的历史记录现在似乎都在一个分支上”……这是定义。在 git 中,所有提交记录都会出现在至少一个分支上 - 那些没有被垃圾回收的分支。 - twalberg
2个回答

2

有道理:他所做的是违反了“主开发线是第一父提交”的规则。

请注意,git本身没有办法执行此规则。这是不可能的,因为一个简单的原因:谁定义哪条线是“主开发线”?唯一可能的答案是“你”,其中“你”表示“运行git来操作提交图的人”。因此,这不是真正的git规则,而是“使用git的人”的规则。

每当您运行git merge(或在这种情况下,“他”运行它)时,您选择当前分支作为主开发线,并将要合并的任何内容作为要合并的备用线。因此,如果您这样做:

$ 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将你的主分支作为主线,并将上游更改合并为分支线。
请注意,最后两个命令——git fetch跟随git merge——是git pull默认执行的操作。这意味着“主线是第一父节点”通常会被违反,除非你非常严格/小心,否则不能依赖它。

有没有办法让这些[合并]提交消失?

是的,但只能通过编写新的提交历史来实现(“重写历史”)。

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

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

提交记录 BD 在这一点上“在错误的行”,因为合并提交 M2 的第一个父提交是 x,而其第二个父提交是 D。与此同时,提交记录 AM1 的第一个父提交,而 xM1 的第二个父提交。

如果您真的非常关心第一个父提交规则,您可以从提交记录 x 创建一个的提交记录行:

  ------------------------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 的 tip 提交。然后 B' 的第一个也是唯一的父提交是 A',以此类推。

如果你有了这个图形之后,从白板上清除提交 AM2 并将 master 指向提交 D',你会得到这样的结果:

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

现在,您可以“纠正”从xA'的链接,并且它看起来像一个漂亮的线性历史记录。

然而,这只是你想要的图形。 对于图中的每个提交,git都会保留一个:一组文件,当您git checkout该提交时,将其放入您的工作目录中。 对于每个提交A'D',您想要的可能与AD上的原始树并不完全相同。

可以确定的是,您想要的B'C'D'的树将与您分别拥有的BCD的树相同。 然而,您想要的新提交A'的树可能是当前处于合并状态M1下的树。 这可能与提交A下的树相同,但也可能不同。 这实际上取决于AM1的比较情况。

有许多相对棘手的方法可以构建新的提交,而不需要太多手动工作,但是很难用文本来描述。 此外,这种“历史重写” - 当您强制使旧的master标签指向new-master的提交D'时发生的部分 - 对所有正在使用M2作为其父提交的开发人员施加了痛苦。 他们必须将这些提交复制到具有新D'作为其父提交的新提交中。

是否值得承受这种痛苦取决于您和他们。


我该如何“擦除”提交以使线条变直?我不明白那部分。 - manugarciac
从技术上讲,你不能这样做(好吧,除非使用一些更棘手的低级git操作),但你也不必这样做。分支标签和其他引用(标签、存储等)使提交可见并可达。一旦更改了任何标签(如master)使旧提交可见,它们就会消失,或者说可以消失,除了你可以在一个月内进行的恢复操作。所以你只需要强制分支master指向新的目标提交(使用git branchgit reset),然后就完成了。 - torek
是的,除非您保留一个名称(例如 oldmaster)以使它们可见。或者,如果您有一个标签来保持它们可见等。记住这一点的方法是引用使提交可见/可查找。 - torek
我现在有主分支和固定的"new-master"。我正在尝试进行切换,就像这样:git branch -m master old-master; git branch -m new-master master; git push -f origin master;这是正确的方法吗?因为我得到了"remote: GitLab: You don't have permission"的错误提示,而且我是在我自己命名空间下创建的全新项目中进行操作。 - manugarciac
没关系,主程序已经被保护了。我解除了保护以进行操作,然后重新保护了它。 - manugarciac
显示剩余2条评论

0

Git分支只是指向单个提交的标签。提交不知道当前指向它的分支(们);也不知道哪些分支曾经指向过它的历史。因此,真正重要的事情(并且难以改变)就是提交历史本身。

最简单的澄清方法可能是以下步骤:找到您认为代表代码库合理状态的最新提交,并运行以下命令(假设该提交的哈希值为123abc):

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

这将使本地(运行这些命令的人的计算机)和服务器上的master都指向123abc。当其他开发人员运行git fetch时,他们的origin/master将移动到123abc,然后他们可以使用git checkout -B master origin/master检出并将自己的master移动到那里(我不确定这个命令的语法,而且我手头没有git存储库)。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接