撤销Git中一个文件的工作副本修改

2039
在最后一次提交之后,我修改了我的工作副本中的许多文件,但我想撤消其中一个文件的更改,即将其重置为与最近的提交相同的状态。
然而,我只想撤消那个文件的工作副本更改,不涉及其他任何内容。
我该如何做到这一点?
15个回答

2660

你可以使用

git checkout -- file

你可以不使用--(如nimrodm建议的),但如果文件名看起来像是分支或标签(或其他修订标识符),则可能会混淆,因此最好使用--

您还可以查看特定版本的文件:

git checkout v1.2.3 -- file         # tag v1.2.3
git checkout stable -- file         # stable branch
git checkout origin/master -- file  # upstream master
git checkout HEAD -- file           # the version from the most recent commit
git checkout HEAD^ -- file          # the version before the most recent commit

44
HEAD 和 HEAD^ 有什么区别? - hasen
74
HEAD 是当前分支上最近的提交,HEAD^ 是当前分支上它之前的提交。对于你描述的情况,你可以使用 git checkout HEAD -- 文件名 来操作。 - Paul
19
简而言之,"git checkout sha-reference -- filename" 中的 sha-reference 是指任何形式表示的提交 sha(分支、标签、父级等),该命令可以用来检出特定提交版本的文件。 - lprsd
36
注意:如果文件已经暂存,您需要首先将其重置。git reset HEAD <filename> ; git checkout -- <filename> - Olie
14
@gwho 是的,你可以使用 HEAD^^ 来获取最近两次提交,或者使用 HEAD^^^ 来获取最近三次提交。你也可以使用 HEAD~2 或者 HEAD~3,如果你想要回退更多次提交的话,这样会更加方便。而 HEAD^2 表示 "这个提交的第二个父提交";因为合并提交的缘故,一个提交可能有多个前置提交。所以在 HEAD^ 中使用数字来选择它们中的哪一个父提交,在 HEAD~ 中则始终选择第一个父提交,并进行相应的回退。有关更多详细信息,请参见 git help rev-parse - Brian Campbell
显示剩余10条评论

212

只需要使用

git checkout filename

这将使用当前分支中的最新版本替换文件名。

警告:您的更改将被丢弃,不会保留备份。


29
这段话的意思是:"@duckx这是为了区分分支名称和文件名。如果你输入 git checkout x,而 x 同时是一个分支名称和文件名,我不确定默认行为是什么,但我认为 git 会假定你想要切换到分支 x。当你使用 -- 时,你表明接下来的内容是文件名。" - hasen
3
感谢您澄清这一点。当他们向您展示示例时,每个人都认为您知道“--”的含义。而且这不是您可以轻易在谷歌上搜索到的东西。 - Patoshi パトシ
3
看起来答案已经被编辑以将其中的 -- 删除。虽然仍然正确,但正如 @hasen 指出的那样,如果文件名和分支名称之间存在歧义,你可能会在这里得到非常不希望的行为! - BrainSlugs83
4
我喜欢它现在的样子,没有“—”符号,简单易懂。当你用文件名来命名分支时,一定存在着糟糕的思维... - Marco Faustinelli

155
git checkout <commit> <filename>

今天我使用了这个方法,因为我意识到在升级到 Drupal 6.10 时,我的网站图标(favicon)已经被覆盖了几次提交。所以我需要找回它。以下是我所做的:

git checkout 088ecd favicon.ico

1
如何在不必翻阅大量“git log --stat”输出的情况下获取提交(已删除文件的提交)? - Alex
4
在命令行中浏览Git日志并找到正确的文件可能有点困难。使用GUI应用程序,例如http://www.sourcetreeapp.com/要容易得多。 - neoneye
6
git log --oneline <文件名> 可以提供更紧凑的日志,并且仅包括对特定文件所做的更改。 - rjmunro
1
你可以使用“git reflog <文件名>”作为替代方法。 - ygesher
这是一个通用的答案。如果不想要的内容已经提交,那么大多数其他答案都不起作用,因为它们的目标是从最后一次提交中恢复文件内容。 - Dmitry Melnikov

100
如果您的文件已经处于暂存状态(在编辑文件后执行git add等操作时会发生这种情况),则需要取消暂存以撤消更改。
使用 git reset HEAD <file> 命令。
git reset HEAD <file>
那么。
git checkout <file>

如果还没有暂存,就使用以下命令:

git checkout <file>

3
这比那个被采纳的回答更有帮助哈哈。很容易忘记哪些更改已经被暂存了,哪些没有,所以重置是有帮助的。虽然我之前也尝试过 "git reset --hard",但它没有像 "git reset HEAD" 那样起作用。我想知道为什么? - Arman Bimatov

36

我已经通过 git bash 完成了以下操作:

(使用 "git checkout -- <file>..." 可以放弃工作目录中的更改)

  1. Git 状态。 [所以我们看到有一个文件被修改过。]
  2. git checkout -- index.html [我已经在 index.html 文件中进行了更改:
  3. git status [现在这些更改已被删除]

输入图像描述


28

在 Git 2.23 中引入了 restore 命令来实现这一点,我想这是为了让回答这类问题更加简单明了。

git restore [--] <pathspec>...

一如既往地,当文件名以破折号开头时可能需要使用--。(与分支名称的混淆在这里不可能出现,因为restore的范围不包括分支,不像全能的checkout

完整来说,restore也可以使用--staged还原已暂存的文件,并且可以使用--source=<tree>从不同于HEAD的提交中还原。


2
对于一些人来说可能很明显,但对我来说并不是:要恢复所有内容:git restore . - vicegax
1
这是现代时代的正确答案。 - eric

26

如果你只想撤销之前提交的对某个文件的更改,可以尝试这个命令:

git checkout branchname^ filename

这将检出文件,就像它在最后一次提交之前的样子。如果你想再回退几个提交,使用 branchname~n 标记。


1
这不会从提交中删除更改,它只会将差异应用于HEAD上的版本。 - FernandoEscher
2
当时,原帖作者只是想撤销他的工作副本修改(我认为),而不是撤销上次提交的更改。原帖作者的问题有点不清楚,所以我能理解造成的混淆。 - user456814
2
也许不是原帖作者的用法,但我正在寻找如何使用主分支的副本覆盖我的分支 - 当替换“branchname ^”时,这种方法非常有效。 - Alex

12

这个答案是为了解决需要撤销同一或多个文件夹(或目录)中多个特定文件的本地更改而提供的命令。此答案特别针对一个用户有多个文件,但用户不想撤消所有本地更改的情况:

如果您有一个或多个文件,您可以将相同的命令 (git checkout -- file) 应用于每个文件,通过列出它们的位置并以空格分隔,如下所示:

git checkout -- name1/name2/fileOne.ext nameA/subFolder/fileTwo.ext

注意name1/name2/fileOne.ext和nameA/subFolder/fileTwo.ext之间的空格。

对于同一文件夹中的多个文件:

如果你需要放弃某个目录下所有文件的修改,可以使用如下的git checkout命令:

git checkout -- name1/name2/*

上述内容中的星号可以撤销name1/name2路径下所有文件的更改。

同样地,以下内容可以撤销多个文件夹中所有文件的更改:

git checkout -- name1/name2/* nameA/subFolder/*

请注意上述名称中的name1/name2/* nameA/subFolder/*之间的空格。

注:name1、name2、nameA、subFolder - 所有这些示例文件夹名称表示可能包含有关文件的文件夹或包。


9
我往往会对此感到困惑,因此这里提供一个测试案例作为提醒;假设我们有这样一个用于测试 gitbash 脚本:
set -x
rm -rf test
mkdir test
cd test
git init
git config user.name test
git config user.email test@test.com
echo 1 > a.txt
echo 1 > b.txt
git add *
git commit -m "initial commit"
echo 2 >> b.txt
git add b.txt
git commit -m "second commit"
echo 3 >> b.txt

此时,更改并未在缓存中被暂存,因此git status的状态为:
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   b.txt

no changes added to commit (use "git add" and/or "git commit -a")

如果从这个点开始,我们执行 git checkout 命令,结果是这样的:
$ git checkout HEAD -- b.txt
$ git status
On branch master
nothing to commit, working directory clean

如果我们执行git reset,则结果如下:
$ git reset HEAD -- b.txt
Unstaged changes after reset:
M   b.txt
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   b.txt

no changes added to commit (use "git add" and/or "git commit -a")

所以,在这种情况下 - 如果更改没有被暂存,git reset 没有任何影响,而 git checkout 会覆盖更改。
现在,假设上述脚本中的最后一次更改已经被暂存/缓存,也就是说我们在结尾处执行了 git add b.txt
此时,git status 的输出为:
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   b.txt

如果从这个点开始,我们执行git checkout命令,结果是这样的:
$ git checkout HEAD -- b.txt
$ git status
On branch master
nothing to commit, working directory clean

如果我们使用git reset,结果如下:
$ git reset HEAD -- b.txt
Unstaged changes after reset:
M   b.txt
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   b.txt

no changes added to commit (use "git add" and/or "git commit -a")

在这种情况下,如果更改是分阶段的,git reset将使分段更改变为未分段更改,而git checkout会完全覆盖更改。

5
我使用SHA id恢复我的文件。我所做的是git checkout <sha hash id> <file name>

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