我可以在GIT中进行部分还原吗?

211
在多文件提交中,是否可以只还原单个文件或某个文件的特定更改?
完整故事: 我提交了一堆文件。几次提交后,有人(JACK!!!)将一个文件复制到他的存储库,并提交了几个文件,覆盖了我所做的一些更改。我想撤销那个被破坏的文件,或者更好地说,去撤销该文件中的两个更改。由于该文件已被拉取和推送,这将是一个单独的撤销提交。

Brian让我找到了正确的方向。最终我使用了git add --patch命令,然后在patch中使用编辑功能得到了我想要的结果。 - Clutch
2
我来晚了,但我认为我找到了“正确”的答案 - ntc2
@ntc2 奇怪的是,被采纳的答案对我来说比你链接的那个更好 - 它让我需要解决合并冲突,而不仅仅是无法应用补丁。 - Iiridayn
@liridayn 你读完我的整个回答了吗?在“变体”部分,我声称添加 --3way 会导致合并冲突而不是无法应用补丁。 - ntc2
还在Git邮件列表上进行了讨论。 - VonC
4个回答

276

通过添加--no-commit选项,您可以在不创建新提交的情况下还原提交。这将使所有被还原的文件留在暂存区。

接下来,我会执行混合重置(重置的默认值)以取消暂存文件,并添加我真正想要的更改。然后,提交(如果您想要多个提交,则可以添加和提交更多文件),最后,检出当前目录以清除由revert导致的任何未提交和未暂存的修改。以下是一个示例工作流程:

git revert <sha-of-bad-commit> --no-commit
git reset   # This gets them out of the staging area

# ...edit bad file to look like it should, if necessary

git add <bad-file>
git commit
git checkout . # This wipes all the undesired reverts still hanging around in the working copy

52
软重置后,您还可以执行 git add -p 来启动交互式会话,以允许您选择性地添加文件的块,而不是整个文件。 (还有 git reset -p 用于有选择地取消暂存更改。这很好知道,但可能不是您在此情况下想要的。) - Stéphan Kochen
6
这正是我所需要的:还原为工作副本,交互式选择片段,提交。你为此应该得到一个大大的、很咸的男人之吻 :* - das_weezul
2
受此解决方案启发的另一个解决方案是执行 revertresetstash -p(储藏“我不想实际执行的取消操作”)、添加其余内容、commit - Cyril CHAPON
git resetgit reset --mixed HEAD 的简写。因此,这个答案显示的 git reset 实际上是一个 --mixed 重置,而不是所述的--soft 重置,因为根据 git help reset,混合重置是默认的。也就是说,混合重置既重置了当前分支指向的提交(在本例中表示为 HEAD 所代表的提交),同时还将文件从索引/暂存区中删除(即使用 HEAD 现在指向的任何快照更新暂存区,Pro Git,第217页)。我确认 git reset --soft 不会取消暂存的文件。如果有人能够确认,我将编辑帖子。 - Life5ign
@StéphanKochen 实际上是混合重置,请参见我的其他评论。 - Life5ign

95

您可以使用 checkout 命令交互式地应用文件的旧版本。

例如,如果您知道将添加回来的代码被删除的 COMMIT,则可以运行以下命令:

git checkout -p COMMIT^ -- FILE_TO_REVERT

Git会提示您添加当前文件版本中缺少的补丁。您可以使用e命令在应用更改之前创建更改的补丁。


1
非常有用,如果您只想还原文件的一部分!基本上,要从头获取块,请使用git checkout -p path/to/file - falstaff
如果提交不是最新的或几乎是最新的,这并不是真正有用的。它不会提供特定于撤销该提交的内容的反转,而是提供自该提交的父提交以来所有更改的反转。 - Tgr

44

您可以使用 git checkout 手动检出想要还原的文件的旧版本。例如,如果您想将 my-important-file 还原为版本 abc123 中的版本,则可以执行以下操作:

git checkout abc123 -- my-important-file

现在,您已经回到了my-important-file的旧内容,如果需要,甚至可以编辑它们,然后像往常一样提交以撤销他所做的更改。如果只想还原他部分提交的更改,请使用git add -p从要提交的补丁中选择一些碎片。


20
这不是“还原(revert)”,而是恢复文件的旧版本。正确的“还原(revert)”操作将删除文件中的错误提交修改,即使自从该错误提交以来该文件已经多次被修改,你也不想失去那些修改。 - Totor
2
Totor,看我的答案。你可以使用“-p”保存修改。实际上,这个答案非常接近这个。 - cmcginty
1
或者你可以将其合并到 git checkout -p 中。 - Léo Lam

35

我在Git邮件列表上找到了一种方法:

git show <commit> -- <path> | git apply --reverse

来源:http://git.661346.n2.nabble.com/Revert-a-single-commit-in-a-single-file-td6064050.html#a6064406

变化

如果补丁无法应用,该命令会失败(不会造成任何更改),但使用--3way选项,则会出现冲突,您可以手动解决这些冲突(在这种情况下,Casey的答案可能更实用):

git show <commit> -- <path> | git apply --reverse --3way

您还可以使用此方法来部分还原多个提交,例如:

git log -S<string> --patch | git apply --reverse

为了还原任何提交中符合 <string> 更改的文件。这正是我在使用场景中所需的(几个独立的提交引入了类似的更改到不同的文件,以及对其他文件进行了不相关的更改,我不想还原)。

如果您在~/.gitconfig中设置了diff.noprefix=true,则需要将-p0添加到git apply命令中,例如:

git show <commit> -- <path> | git apply -p0 --reverse

这个答案比被采纳的那个好多了:“最好的办法是进去并还原该文件中的两个更改”。 - rewritten
我想要撤销提交的一部分,所以我进行了以下操作:**(1)** git show ... > foo.txt (2) 编辑 foo.txt 以删除我不想撤销的差异 (3) git apply --reverse foo.txt 来撤销仍在 foo.txt 中列出的差异。 - cxw
在git show部分添加--binary --full-index也可能是一个有用的变化。 - Laurynas Biveinis
即使您在<path>中传递了一个模式,这也可以正常工作。太棒了! - lnogueir

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