将 Git 回退到某个特定的提交,而不改变历史记录并创建新的提交,类似于 Git revert。

12
有没有一种方法可以回滚到某个提交,而不改变远程历史记录,基本上像git revert一样撤消该提交之后的所有更改,并在新提交中创建与提交A相同的代码,以便将该提交推送到远程分支。
例如-我有3个提交 提交A-> B-> C,我的头目前在C处 现在我想创建另一个提交D,它将具有与A相同的代码,以便我可以将该提交推送到远程分支而不更改历史记录。

3
git revert [commit]撤销提交,然后将其推送到新的提交中有什么问题吗?这样做不会将本地分支上的提交还原回A,然后您可以将此新的A推送到D上。 - L_Church
你想让提交 D 拥有与 A 相同的更改,还是要让 D 撤销 A 引入的更改? - Tim Biegeleisen
我希望D的代码与A相同。我不想撤销A引入的更改。 - Manthan Jamdagni
3
@L_Church git revert A会撤销A引入的更改,但B和C引入的更改不受影响。我想完全回到A的代码,即撤消A之后所做的所有更改。 - Manthan Jamdagni
2个回答

24

小心使用“还原”这个词

当人们在Git中说“我想还原”时,有时他们的意思是git revert所做的事情,这更像是一个撤销操作,而有时他们的意思是你实际所做的事情,即从早期版本恢复源代码库

举个例子,假设我们有一个提交只包含一个文件README,并且进行了三次提交:

A <-B <-C   <-- master (HEAD)

版本A的README文件说:“我是一个README文件”,只有一行。

版本B的README文件与之前相同,也说“我是一个README文件”,但新增了第二行:“这个文件共有五行。”

版本C的README文件被更正了,它的第二行说:“这个文件共有两行。”

Git的git revert命令可以撤销一个变更。现在,运行git revert <hash-of-B>会尝试移除添加的一行。由于该行不再匹配,因此操作将失败(我们可以运行git revert --abort来放弃操作)。类似地,运行git revert <hash-of-C>将尝试撤销更正。这将成功,实际上回滚到了版本B

这个问题Undo a particular commit in Git that's been pushed to remote repos,全都是关于撤销的,虽然有时会导致恢复之前某个版本,但并不完全相同。根据你的问题,你想要的是:“让我生成一个新的提交D,其源代码与提交A相同”。你想要恢复到版本A

Git没有可以“恢复到”某个版本的用户命令,但很容易实现

这个问题How to revert Git repository to a previous commit?,充满了使用git reset --hard命令来执行该操作的答案,但这样做会删除历史记录。然而,被接受的答案中包含一个关键点,具体来说是:

git checkout 0d1d7fc32 .
该命令告诉Git从给定的提交0d1d7fc32中提取所有文件,并且只提取当前目录(.)中的文件。如果你的当前目录是工作树的顶部,那么它将递归包含子目录中的所有文件。但是它有一个问题,就是它确实提取了所有文件,但是它不会移除(从索引和工作树)任何您不需要的文件。为了说明这一点,让我们回到我们的三个提交存储库并添加第四个提交:
$ echo new file > newfile
$ git add newfile
$ git commit -m 'add new file'

现在我们有四个提交:

A <-B <-C <-D   <-- master (HEAD)

如果提交D包含正确的两行README并且包含新文件newfile

那么我们可以执行:

$ git checkout <hash-of-A> -- .

我们将使用提交A中的版本覆盖README的索引和工作树版本。我们回到了单行的README,但是在我们的索引和工作树中仍然有文件newfile

为了解决这个问题,我们不应该只从提交中检出所有文件,而是应该先删除索引中存在的所有文件:

$ git rm -r -- .

然后从提交记录 A 安全地重新填充索引和工作树:

$ git checkout <hash> -- .

(我试图在这里自动使用--,以防所需的路径名称类似于选项或分支名称等;即使我只想检出名为-f的文件或目录,它也可以工作。)

完成这两个步骤后,就可以安全地git commit提交结果了。

次要: 一种快捷方式

由于Git实际上只是从索引中进行提交,因此您只需将所需的提交复制到索引中即可。 git read-tree命令可以实现此功能。 您可以同时更新工作树,因此:

$ git read-tree -u <hash>

使用替换而非删除和检查的方法就足够了。(你仍然需要像往常一样提交一个新的 commit。)


6
非常感谢您的清晰解释,git read-tree -u --reset <hash> 命令成功解决了我的问题。 - Manthan Jamdagni

4

开始之前,请记得 git stash 或备份所有未提交的文件,因为这些命令执行后它们将会丢失。

  1. git reset --hard <提交A>
  2. git reset --soft <提交C>
  3. git add . && git commit -m "提交D"

现在处于D状态,之前的提交A、B和C都已保留。


1
我不知道为什么会被踩,可能是因为看起来有点棘手。但这是唯一简单的方法来实现它。在再次踩之前,请自己进行一些实验以查看它是否有效。 - dzcpy
我尝试按照被接受的回答中的说明操作,但是我要么做错了什么,要么发生了某些事情 - 命令“git read-tree”没有实现我想要的任何内容。你的解决方案确实有效,但这是一个糟糕的黑客方法。(这应该可以在不移动分支指针的情况下完成。) - Mike Rosoft
1
我的情况有了完美的解决方案:一位用户搞砸了他们的分支,想要恢复到之前的状态,但他们不想进行硬重置,并且想保留整个 Git 历史记录,包括这些混乱的提交。谢谢!如果您在每个步骤中添加了一些解释,那么这个答案将会更有价值。 - StoneThrow
对于那些不怕使用 git reset --hard 命令的用户来说,我认为这是最好的方法。 - Devin Rhode

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