恢复多个提交并合并回特定提交而无需强制推送

Reverting multiple commits and merges back to a specific commit without force pushing

问题:

我知道我可以 git revert -n "my specific commit"..HEAD 问题是它在第一次合并时停止。通过 -m 1 会解决这个问题,但它会在非合并时停止。

git 重置 --hard 和强制推送是 NOT 一个选项。我无法强制推送此 git 主机。

情况:(我为什么需要这个)

我知道这里有很多关于这个的问题,但我还没有找到一个有这个确切问题的人,并且没有强制推动的合理解决方案。

您可以使用 git checkout -p <sha1> 将工作树恢复为给定的 sha1,而无需更改当前分支所在的位置。

git checkout -p "my specific commit"

对每个问题回答 "a" - 我没有找到如何强制它进入非交互式变体,尽管你可以使用 Linux 命令 yes - 它只是输入 "y"永远:

yes | git checkout -p "my specific commit"

然后提交并推送

git commit -m'Reverting to my specific commit'
git push

结果是一个单一的提交,一下子撤销了糟糕的历史记录。

编辑:警告:如果文件从 my specific commit 删除到当前 HEAD,此命令将不会恢复它们。因此,@torek 和@Marcus 提出的解决方案比这个更可取。

TL;DR

使用 git read-tree -u <hash>,然后进行新的提交。

请记住,Git 存储库本质上是提交的集合。每个提交都包含所有文件的完整快照——好吧,是该提交中的所有文件,但这样表达听起来像是同义反复——加上一些 元数据: 名称和电子邮件地址提交人的姓名、提交时间的时间戳、解释为什么他们提交的日志消息,等等。 (元数据的关键部分之一是此提交的 parent 提交的哈希 ID,但这一次我们根本不必关心这部分!)

任何给定提交的真实名称是其哈希 ID。您可以通过以下方式暂时将此提交粘贴到您的工作树中:

git checkout <hash-id>

但当然这只会让你得到一个 "detached HEAD",当你通过 git checkout master 重新附加你的 HEAD 时,你又回到了损坏的源代码快照。

因此,每次提交都代表所有文件的快照。你有一个快照,在某个时候,你喜欢,你想回到它——但实际上并没有直接 git checkout <hash>。这意味着您想要的是进行 new 提交,其快照与现有提交相同。有一些很好的提交和一些现有的哈希 ID,并且 git checkout <hash> 是......好的,但是你想要一个 new 提交和 new 哈希 ID,在当前分支的(新)尖端与好的哈希 ID 相匹配。这个新提交应该将当前提交作为其父提交,就像 any 新提交将当前提交作为其父提交一样。

中概述的方法将起作用:它将选定提交中的内容与当前工作树中的内容进行比较,并询问您是否要调整工作树以匹配选定的提交,对于每个文件中的每个 diff-hunk。回答 "a" 表示 "take all for this file",但这让您需要输入很多答案,每个不同的文件一个。

使用不同的 git checkout 模式更简单但有缺陷:

git checkout <good-commit-hash> -- .

此模式告诉 git checkout不要将 切换到 另一个提交。不要以任何方式改变 HEAD 本身!但是 do 搜索 Git 数据库以查找与良好提交中的路径说明符 . 匹配的文件。对于每个匹配的文件,将其从该提交中取出,将其复制到我当前的索引,然后将其复制到我当前的工作树。

如果您在工作树的顶层执行此操作,则良好提交中的 所有 文件将匹配路径说明符 .。因此,如果您在提交 <hash> 中有文件 READMEmain.py,它们将替换索引中的 READMEmain.py,以及 READMEmain.py 在你的工作树中。您将准备好提交。

这里的缺陷是:如果你现在有一个 third 文件,比如说 bug.txt,那么 isn 't in the good commit you're trying to switch back to? 要解决这个问题,你必须明确地 删除 当前文件中的任何文件索引和工作树 not 在好的提交中。

您可以手动删除任何此类文件。或者可能没有任何此类文件,在这种情况下,问题只是理论上的。但是有一个保证的治疗方法,如果没有问题就是无害的。您可以从以下开始:

git rm -r .

删除所有内容。这将删除 bug.txt and main.py and README。随后的 git checkout <good-commit-hash> -- . 放回 好的 main.pyREADME,并且 bug.txt 仍然被删除,因此您可以 git commit结果。

不过,有一个较低级别的 Git 命令可以为您执行此操作,那就是 git read-tree。此命令并非真正适合日常使用,1 但这也不是日常问题。在这种特殊情况下,使用:

git read-tree -u <good-commit-hash>

告诉管道命令:清除我当前的索引。从给定的哈希 ID 中获取文件并将它们放入我的索引中。无论你在哪里完全删除了一个文件,也从我的工作树中删除 if 。无论你在哪里替换了一个文件,也要在我的工作树中替换它。 就文件及其内容而言,这与你使用 git rm -r .; git checkout <good-commit-hash> -- . 获得的结果完全相同,只是它更多高效。

在任何一种情况下,您现在都可以使用当前索引内容进行新提交,该索引内容现在与当前工作树内容匹配(未跟踪文件除外,它们像往常一样保持未跟踪状态)。


1git read-tree 命令实际上是一个 管道命令,用于制作新的花哨的前端命令东西,嗯,花哨的。例如,某天某人可能会写一个 git revert-to 命令,它只包含一些状态检查,然后是 git read-tree -u <hash>git commit.

git reset --hard good_commit_hash
git reset --soft current_commit_hash
git commit

会做你想做的。

(如果上游(远程)分支是您要提交的地方,您可以简单地使用“@{u}”作为 "current_commit_hash"。)

简而言之:如果您想在您的存储库中进行任何类型的清理,请使用 git 重置。有点像瑞士军刀。