我有一个很好的工作来清理一个完善的git存储库。在过去,有人将整个linux内核src合并到存储库中(所有650k提交)。我知道来自合并以及来自父级的提交ID。当然,将linux与masterbranch合并之间的时间有所变化,所以目前树看起来与此类似
-x-x-x-x-LinuxMerge-x-x-x-x-x-x-x-x-x-today
我想要的是还原LinuxMerge提交包括。这个历史。这可能和怎么样?
答案 0 :(得分:1)
你可以通过变基来撤消这个。
如果你从这开始......
-x-x-x-x-LinuxMerge-x-x-x-x-x-x-x-x-x-today
...那么你可能正在讨论这个问题,而不是:
-x-x-x-x-x-x-x-x-x-x-x-x-x-today
/
-linus-/
让我们标记更多提交内容:
-x-x-x-prev-merg-post-x-x-x-x-x-x-x-today
/
-linus-/
因此,您希望将prev
和post
粘合在一起,然后扔掉merg
。对此的命令是:
git rebase merg today --onto prev
(请注意,在命令中,我们提到merg
,而不是post
;这是git
中声明提交范围的典型问题" + - 1"问题)。
此rebase命令将添加一个新的提交行,并将today
分支更改为指向新尾部:
post'-y-y-y-y-y-y-y-today'
/
-x-x-x-prev-merg-post-x-x-x-x-x-x-x-today
/
-linus-/
如果你忽略旧的东西,这会变得平坦:
-x-x-x-prev-post'-y-y-y-y-y-y-y-today'
rebase还会将today
分支更改为指向此ASCII图片中标记为today'
的提交。
请注意,post'
和y
提交(以及today'
)所有的哈希值都与原始哈希值不同,它们不是"相同的"提交。
如果没有其他标记或分支指向导致linus
的历史记录,那么这些提交和相关对象最终将被git垃圾集合清除(您可以使用git gc
强制进行清除肯定)。
答案 1 :(得分:1)
我认为这个问题引起了一些混淆,因为你把它说成是想要"还原"错误 - 并且"还原"意味着git特有的东西。我知道你的意思并不是git对这个词意味着什么,因为git' s意思是一个像" revert history"不是一件事。
因为您想完全撤消更改,所以历史记录重写是第一步。 AnoE的答案显示了一种方法,假设只有一个ref可以从中获得坏合并,并且没有合并提交"在"之间。那个参考和坏的合并。
如果有多个参考,您需要做更多的事情。例如,如果你有
x -- x -- x --- ML -- x -- A -- x <--(master)
/ \
(linux history) o <--(branch_one)
完成rebase会给你
x' -- A' -- x' <--(master)
/
x -- x -- x --- ML -- x -- A -- o <--(branch_one)
/
(linux history)
然后您需要移植o
提交,例如
git rebase --onto A' A branch_one
(用提交ID或命名相应提交的其他表达式替换A
和A'
。
如果有合并需要重写,那么你就有了更大的问题。 rebase
命令将默认尝试写入线性历史记录。您可以告诉它您希望使用--preserve-merges
选项保留合并拓扑,但它可能无法正常工作。如果合并提交存在冲突,则您必须重新解决它。更糟糕的是,如果合并提交没有有冲突,但最初没有使用默认合并结果完成,那么rebase
将不会重新创建合并(或其任何子项)正确。
因此,唯一安全的rebase方法是分段,在你遇到它们时手动重现合并。
另一种选择可能是使用git filter-branch
而不是rebase
;但这也很棘手。如果您可以编写删除合并引入的任何内容,那么它是唯一可行的。例如,如果linux历史记录与您自己的工作路径不同,那么您可以通过rm
某些路径清理内容的给定实例,那么您可以使用filter-branch
。
(因为这是一个可能或可能不适合您的选项,现在我不会详细说明详细步骤。filter-branch
文档可以填写空白。基本上你是&#39 ; d使用parent-filter
绕过合并提交(通过将以下提交重新提交到第一个父提交),加上index-filter
或tree-filter
以从后续删除linux文件提交。)
无论如何,一旦您清理了历史记录,您仍然在您的repo数据库中拥有所有历史记录。至少你需要确保没有引用该历史记录。然后它最终会被gc
清除(或者你可以强迫它更早发生)。
大多数情况下,这意味着您必须找到任何可以达到Linux历史记录的引用。由于重写移动了#34;你的&#34; refs,这可能包含任何带有linux历史记录的refs(分支或标签)。所以你只想删除它们。
还有可以(间接)到达linux历史记录的reflog,并且gc无法删除以这种方式可以访问的历史记录。老实说,最简单的事情就是重新克隆回购(因为新的克隆应只获取当前的refs及其历史记录)并用结果替换原点。
如果您想修复现有的仓库,而不是出于任何原因重新克隆,下一步就是清除reflog(我通常只是rm -r .git/logs
),然后运行激进的gc
(请参阅gc
文档)
答案 2 :(得分:0)
你有几个选择。
如果您能够重写 master
分支的历史记录而没有后果,那么实现目标的最快捷方式就是删除合并与git rebase --onto
完全一致:
git checkout master
git rebase --onto <SHA-1-of-the-linux-merge>^ <SHA-1-of-the-linux-merge>
这意味着:&#34; rebase master
位于合并提交的第一个父级之上,从合并提交开始&#34;。这将有效地删除合并提交,并在其第一个父级之上应用所有后续提交。您可以详细了解git rebase --onto
的工作原理here。
如果您想避免重写历史记录,您可以随时恢复&#34; LinuxMerge&#34;使用git-revert
提交:
git revert --mainline-parent 1 --no-commit <SHA-1-of-the-linux-merge>
--mainline-parent
选项告诉Git要将恢复为的合并提交的哪个父级。在这种情况下,您希望恢复到 first 父级,即将Linux内核合并到到的提交。
通常您无法恢复合并,因为您不知道合并的哪一侧应被视为主线。此选项指定主线的父编号(从1开始),并允许恢复相对于指定父级的更改。
请注意,以这种方式还原合并将导致以后将同一分支合并到排除最初由恢复的合并引入的提交:
恢复合并提交声明您永远不会希望合并带来的树更改。因此,以后的合并只会带来由不是先前还原的合并的祖先的提交引入的树更改。这可能是也可能不是你想要的。
然而,在这种情况下,听起来你很快就会再次合并Linux内核。
对于--no-commit
选项,它允许您执行干运行,以查看在没有实际创建提交的情况下是否在工作目录中发生任何冲突。