我有一个很长的混乱的git提交历史。我想删除头部和head@{119}之间的所有提交。我已经尝试过变基,但由于很多提交都是对相同几行的微小更改,因此我可能会进行 200 次更正。如果压缩它们是正确的途径,那么我如何让 git 自动尊重最新的更改?它要求我解决冲突,但难道不应该很明显我最近的提交是我想保留的更改吗?我怎样才能让 git 做到这一点?如果挤压不是正确的途径,我可以删除 head 和 head@{119} 之间的提交(不包括 head 现在所在的提交)。
我对 Git 不是很好,我在 Google 和 SO 上研究了很多,但我没有足够的经验来弄清楚如何将这些答案应用于我的问题。有几个类似的问题,但如果有人能更清楚地了解这里的应用程序,将不胜感激。谢谢。
请参阅Ry-的答案,了解执行此操作的实用方法。
请注意,您真正要做的是在进行与上一次提交一样好的新提交后丢弃所有提交,包括最后一个提交 - 除了它比上一个更好,因为它的父级是倒数第 119 个。 也就是说,我们告诉 Git 停止使用一些现有的提交,并开始使用一些新的和改进的提交。 在这种情况下,只有一个新的和改进的提交,它替换了许多旧的和糟糕的提交。
从图形上讲,我们可以将每个提交绘制为图形中的一个节点(因为它是)。 每个 Git 提交的实际名称是一些丑陋的大哈希 ID。 我们可以使用单个大写字母来代替这些丑陋的大哈希 ID,尽管我们在达到 120 次提交之前很久就会用完,这当然是 Git 使用大丑陋哈希 ID 而不是简单的单字母 ID 的原因之一。😀 但原则上,这幅画抓住了重要的现实。 像master
这样的分支名称包含链中最后一个提交的哈希 ID:
... <-H <--master
我们说master
指向最后一次提交(在这种情况下是H
,这意味着这个存储库中可能只有八个提交)。 提交H
本身包含其直接前身G
的哈希 ID:
... <-G <-H <--master
所以H
指向G
,但G
也指向另一个较早的提交:
... <-F <-G <-H <--master
这种情况一直持续到我们到达第一次提交(此时我懒洋洋地停止将中间箭头绘制为箭头):
A--B--C--D--E--F--G--H <-- master
每次提交都包含一些数据:所有源文件的完整快照。 而且,每个提交还包含一些元数据:谁做了,什么时候,为什么(日志消息)等等。父提交哈希 ID是元数据的一部分。
现在,假设我们制作一个完全相同的H
副本,只是它不是将G
命名为其父级,而是将提交C
命名为其父级? 也就是说,我们制作这个:
A--B--C--D--E--F--G--H <-- master
H'
H'
是H
的副本 - 第九次提交。 我们需要一个名称来跟踪最后一次提交...但是让我们使用master
,通过强制 Git 使名称master
指向H'
而不是H
:
A--B--C--D--E--F--G--H [abandoned]
H' <-- master
Git 查找提交的方式是采用分支名称,如master
,并使用其箭头(其(单个)存储的提交哈希 ID)来查找最后一个提交。 从该提交开始,Git 跟随内部存储的箭头向后到提交的父级,依此类推。
通过将master
从提交H
中剥离出来并使其指向提交H'
,我们使其无法找到提交H
。 当您从master
开始并列出提交时,您会看到H'
,然后是C
,然后是B
,然后是A
。 如果你不注意提交哈希 ID——H'
的和H
的不一样——你可能会认为你仍然看到H
,然后C
,好像H
仍然存在。 但它不存在——无论如何,不在这个列表中。
提交H
仍在您的仓库中,如果您知道其哈希 ID,您仍然可以看到它以及它之前的所有提交。 如果您知道在宽限期内查找旧的废弃提交在真正被丢弃之前查找它们的秘密1方法,您也可以使用它来查找提交H
。 但是,如果您从您的名字master
开始并查看提交,您将看不到原始H
。
请注意,此存储库的任何其他克隆可能仍会保留原始提交。要让他们像您一样阅读,您必须:
- 向他们交付新的和改进的提交
H'
(这部分很简单:任何git push
或git fetch
都可以做到这一点);和 - 强迫他们移动他们的
master
,和/或任何其他他们找到提交H
的名称,以便他们找到H'
。
第 2 步更难,也可能更难:如果他们不想改变,他们就不会改变。 如果你有正确的权限,你可以使用git push --force
或git push --force-with-lease
来让他们这样做。 如果没有,你不能。
一旦您获得此存储库的所有克隆以丢弃原始文件,2 现在每个人都有这组提交。 历史现在看起来像:
A--B--C--H' <-- master
因为 Git 存储库中的历史记录是存储库中的一组提交。 您可以通过让 Git 从某个特定的提交(例如分支名称master
指向的提交)开始并反向工作来查看它。
1实际上不是秘密。
2与您自己的克隆一样,它们可能会保留原始提交一段时间,特别是如果他们有自己的 reflog。--bare
服务器端存储库(例如 GitHub 或其他托管服务的存储库)通常没有reflogs,因此隐藏起来的陈旧提交往往会更快地从这些存储库中消失。 不过,它仍然不是即时的。 例如,GitHub的人说这可能需要一两天的时间。
将分支指向应保持原样的最后一个提交,在分阶段更改时保留差异:
git reset --soft HEAD~119
将差异提交为新提交:
git commit
如果最近的提交也是您要用于此目的的提交消息:
git commit --reuse-message=HEAD@{1}