如何从Git的特定版本中检索单个文件?

1060

我有一个 Git 存储库,想要查看几个月前某些文件的历史版本。我找到了那个日期的修订版本; 它是 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8。我需要查看一个文件的旧版本,并将其保存为一个(“新”)文件。

我使用 gitk 工具成功地查看了该文件,但它没有保存选项。我试过使用命令行工具,最接近的尝试是:

git-show 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8 my_file.txt

然而,这个命令会显示差异,而不是文件内容。我知道之后可以使用类似于PAGER=cat的东西并将输出重定向到一个文件,但我不知道如何获取实际的文件内容。

基本上,我正在寻找类似于svn cat的东西。


105
重点在于:git show(不太有用地)使用不同的语法来处理冒号。git show 2c7cf:my_file.txt - Steve Bennett
5
为了进一步澄清,上述命令是要求 Git 显示两个独立的对象,一个修订版本和一个文件。下面被接受的答案,使用冒号将两个项分开,是在请求特定修订版本中的特定文件。 - jhclark
2
在*nix上,您不需要PAGER,只需使用>进行shell输出重定向。 - Konstantin Pelepelin
1
可能是重复的问题:有没有一个快速的Git命令可以查看文件的旧版本? - Ciro Santilli OurBigBook.com
Checat有一个重要的评论,针对那些想将内容导出到某个文件的人。你需要像这样做:git show {sha}:my_file.txt > old_my_file.txt - ormurin
显示剩余3条评论
12个回答

995

使用git show

为了完成你自己的回答,语法确实是这样的:

git show object
git show $REV:$FILE
git show somebranch:from/the/root/myfile.txt
git show HEAD^^^:test/test.py

该命令采用通常的修订样式,这意味着您可以使用以下任何一种方式:
  1. 分支名称(如 建议 by ash
  2. HEAD + x 个 ^ 字符
  3. 给定修订的SHA1哈希值
  4. 给定SHA1哈希值的前几个字符(可能是5个字符)
提示: 在使用 "git show"时,重要的是要记住,始终从存储库的根目录指定路径,而不是当前目录位置。
(尽管 Mike Morearty 提到,至少在 git 1.7.5.4 中,您可以通过在路径开头放置 "./" 来指定相对路径。例如:
git show HEAD^^:./test.py

)

使用 git restore

从Git 2.23+(2019年8月)开始,您也可以使用 git restore 命令,它取代了 令人困惑的 git checkout 命令

git restore -s <SHA1>     -- afile
git restore -s somebranch -- afile

这将仅在工作树中还原与"source" (-s)提交SHA1或分支somebranch中存在的文件。
要还原索引:

git restore -s <SHA1> -SW -- afile

(-SW:缩写形式,表示--staged --worktree)


正如starwarswii评论中所指出的

It lets you pipe the contents into a file, which is great if you want to just quickly compare files from a commit.

E.g. you can do:

git show 1234:path/to/file.txt > new.txt 
git show 1234~:path/to/file.txt > old.txt

then compare them.


使用低级别的git工具命令

在git1.5.x之前,可以使用以下一些低级别的工具完成:

git ls-tree <rev>
显示提交中一个或多个“blob”对象的列表

git cat-file blob <file-SHA1>
以指定版本已提交的方式查看文件(类似于svn cat)。 使用git ls-tree来检索给定文件SHA1的值

git cat-file -p $(git-ls-tree $REV $file | cut -d " " -f 3 | cut -f 1)::

git-ls-tree 列出了修订版 $REV$file 的对象 ID,这将从输出中剪切出来并用作 git-cat-file 的参数。实际上,git-cat-file 应该被称为 git-cat-object,它只是将该对象转储到 stdout


注意:自Git 2.11(2016年第4季度)以来,您可以对git cat-file输出应用内容过滤器。

查看commit 3214594commit 7bcf341(2016年9月9日)、commit 7bcf341(2016年9月9日)和commit b9e62f6commit 16dcc29(2016年8月24日),由Johannes Schindelin (dscho)提交。
(由Junio C Hamano -- gitster --合并于commit 7889ed2,2016年9月21日)

git config diff.txt.textconv "tr A-Za-z N-ZA-Mn-za-m <"
git cat-file --textconv --batch

注意: "git cat-file --textconv" 最近(2017年)开始出现段错误,这在Git 2.15(2017年第四季度)中已得到纠正。
请参见commit cc0ea7c(由Jeff King (peff)于2017年9月21日提交)。 (由Junio C Hamano -- gitster --commit bfbc2fc中合并,2017年9月28日)

8
@Oscar,因为git show本质上是将内容转储到标准输出(stdout),所以您可以将该输出重定向到任何您想要的文件中 (http://tldp.org/LDP/abs/html/io-redirection.html)。 - VonC
10
git checkout [branch | revision] filepath 是正确的命令。它的作用是切换到指定的分支或版本,并将指定文件路径的文件恢复到该状态。 - Gaui
22
@Gaui,但是git checkout将会用另一个版本覆盖你的文件,而不是像git show一样允许你将其保存为不同的名称,以便你可以获取并查看两个版本(当前版本和旧版本)。从问题中不清楚OP是否想要用旧版替换当前版本。 - VonC
13
我想指出 ^^^ 也可以更一般地写作 ~~~ 或者更好的是 ~3。使用波浪号的另一个优点是不会触发某些shell(例如zsh)的文件名匹配。 - Eric O. Lebigot
2
我没有足够旧的git来检查:pre-1.5.x的git rev-parse是否处理rev:path语法?(在更近期的git中,您可以使用git cat-file -p $REV:path。但是,git show也适用于目录路径,因此它不仅更短,而且通常更接近于人们想要的内容。) - torek
显示剩余19条评论

555

如果您想要使用之前提交或不同分支的文件内容替换/覆盖当前分支中文件的内容,可以使用以下命令:

git checkout 08618129e66127921fbfcbc205a06153c92622fe path/to/file.txt
或者
git checkout mybranchname path/to/file.txt

您随后必须提交这些更改,以便它们在当前分支中生效。


5
最简单的解决方案是使用git-checkout,这就是它的设计目的——指定文件路径意味着只有匹配的文件会被检出。来自git-checkout手册页面的示例:git checkout master~2 Makefile。 - RichVel
1
那么,你如何回到运行此命令之前的先前状态? - Flint
@Flint,如果你是从HEAD状态来的,那么只需要简单地执行git checkout HEAD -- [完整路径]即可。 - Tiago Espinha
81
请注意,这将覆盖该路径下现有的文件,而git show SHA1:PATH的解决方案只会输出到标准输出。 - Flimm
很好!如果不是看了 git help checkout,我是想不到这个方法的。我需要按照某个日期检出子目录,使用这种方法,我成功让这个语法工作了:git checkout @{YYYY-MM-DD} sub-dir - haridsv
我犯了一个愚蠢的错误,但如果你使用Windows(Sourcetree/Mingw),请注意路径分隔符是“/”,而不是“\”。 - BurnsBA

169
你需要提供文件的完整路径:
git show 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8:full/repo/path/to/my_file.txt

10
不一定要完整路径,从git根目录开始的路径(在git show --name-only中出现的路径也足够了)。 - Mohsen
9
从代码库根目录开始的完整路径。请仔细查看我所举的例子。在“full”之前没有前导斜杠。 - Milan Babuškov
8
如果您在子目录中,也可以成功使用 ./filename.ext。 - Traveler
我认为重点是,如果你在 full/repo/path/to 目录下尝试执行 git show 27cf8e84:my_file.txt 命令,你会得到如下信息: fatal: Path 'full/repo/path/to/my_file.txt' exists, but not 'my_file.txt'. Did you mean '27cf8e84:full/repo/path/to/my_file.txt' aka '27cf8e84:./my_file.txt'? 这就像是 Git 可以直接帮助你,但选择在这里表现得过于严谨。 - Ed Randall

136

最简单的方法是写:

git show HASH:file/path/name.ext > some_new_name.ext

其中:

  • HASH是Git版本的SHA-1哈希值
  • file/path/name.ext是您正在查找的文件的名称
  • some_new_name.ext是旧文件应保存的路径和名称

示例

git show 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8:my_file.txt > my_file.txt.OLD

这将保存带有名称my_file.txt和修订版本为27cf8e的文件作为一个新文件,并将其命名为my_file.txt.OLD

它已经在Git 2.4.5中进行了测试。

如果您想要检索删除的文件,可以使用HASH~1(指定HASH之前的一次提交)。

示例:

git show 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8~1:deleted_file.txt > deleted_file.txt

2
附加信息:你可以通过git log获取HASH。 - xotix
@xotix谢谢。我使用git log file/path/name.ext获取了特定文件的所有哈希历史记录。 - Sriram Kannan
这是正确的语法。重要时刻:1. : 使Git输出在修订时的文件(而同样位置的空格则显示差异)。2. file/path/name.ext 路径必须相对于存储库根目录。 - Melebius

17
git checkout {SHA1} -- filename

这个命令可以从特定的提交中获取复制的文件。


1
这个为什么排名不高呢? - jouell
+1,老实说我一点也不清楚——顶部答案列出了5种不同的、远比简单的“checkout”命令更复杂的方法... - Tomáš M.

13

在Windows中,使用Git Bash:

  • 在你的工作区中,将目录更改为包含文件的文件夹
  • git show cab485c83b53d56846eb883babaaf4dff2f2cc46:./your_file.ext > old.ext

9
除了其他答案中列出的所有选项外,您还可以使用与所需Git对象(哈希、分支、HEAD~x、标签等)和文件路径相应的git reset命令。
git reset <hash> /path/to/file

在你的例子中:
git reset 27cf8e8 my_file.txt

这样做的作用是将my_file.txt恢复到索引中提交27cf8e8时的版本,同时保留工作目录中它的当前版本(不会受到影响)。
接下来,操作非常简单:
- 您可以使用git diff --cached my_file.txt命令比较两个版本的文件。 - 如果您决定不需要旧版本文件,则可以使用git restore --staged file.txt命令(或Git v2.23之前的版本使用git reset file.txt命令)删除旧版本文件。 - 您可以使用git commit -m "Restore version of file.txt from 27cf8e8"git restore file.txt命令(或Git v2.23之前的版本使用git checkout -- file.txt命令)恢复旧版本文件。 - 您甚至可以交互式地选择要在第一步中重置的某些巨块,如果运行git add -p file.txt命令(然后进行git commitgit restore file.txt)。
最后,您可以根据需要在第一步交互式地选择挑选并重置巨块。
git reset -p 27cf8e8 my_file.txt

git reset 带有路径参数,能够让你更灵活地检索特定版本的文件,以便与当前已检出的版本进行比较,并且如果选择这样做,可以将其完全还原或仅还原部分块到该版本。


编辑: 我刚意识到我没有回答你的问题,因为你想要的不是差异或检索旧版本的某个或所有部分的简便方法,而只是要 cat 该版本。

当然,在重置文件之后,仍然可以使用以下命令进行操作:

git show :file.txt

输出到标准输出或

git show :file.txt > file_at_27cf8e8.txt

但如果您只需要这个,那么像其他人建议的直接使用git show 27cf8e8:file.txt命令运行git show显然更加直接。

我留下这个答案是因为直接运行git show可以立即获得旧版本,但如果您想对其进行操作,从那里进行操作并不像将该版本重置到索引中那样方便。


如果文件在另一个分支上怎么办? - Chuck
我想从另一个特定的分支检索特定文件的特定版本。这是可能的吗? - Chuck
1
是的。具体版本和特定分支:您只需要使用例如 git log --graph --oneline --all 找到相应的哈希值即可。具体文件:提供文件路径。 - prosoitos
1
请记住,这将还原文件为与索引中哈希对应的版本。不是工作树中的版本。如果你想在工作树中恢复那个版本,那么你需要运行 git commitgit restore <file>,正如我在答案中提到的那样。 - prosoitos

8

如果你想将其写入文件中(至少在Windows上),可以通过Git Bash执行以下命令:

$ echo "`git show 60d8bdfc:src/services/LocationMonitor.java`" >> LM_60d8bdfc.java
< p > "引号是必需的,这样可以保留换行符。


不错。+1。很好的补充了我之前提到的git show语法。 - VonC
26
我真的不明白为什么你会使用echo,无论是带引号还是不带引号。我也不明白为什么你想要输出重定向的追加形式。直接写以下命令不是更好吗:git show 60d8bdfc:src/services/LocationMonitor.java > LM_60d8bdfc.java。如果出于某些原因你确实想要强制使用dos风格的换行符,可以通过unix2dos进行管道连接。但在Windows上,除了记事本以外的任何文本工具都可以很好地处理Unix风格的行尾符,所以我从来没有觉得保留dos风格的换行符有任何用处。 - sootsnoot
4
"git show 60d8bdfc:src/services/LocationMonitor.java >> LM_60d8bdfc.java" 的翻译是:"这条指令对我有用,它将显示提交ID为60d8bdfc所对应的 'src/services/LocationMonitor.java' 文件,并将其附加(append)到名为 'LM_60d8bdfc.java' 的文件中。" - Mike6679
在Ubuntu bash中,git show 60d8bdfc:./src/services/LocationMonitor.java > LM_60d8bdfc.java 对我有效。注意:后面的./ - phuclv
2
不要使用双引号,因为如果您的文件字符看起来像shell变量,即$ LANG,它将被替换。@LưuVĩnhPhúc是救星。也不要使用>>,如果文件存在,它将附加到文件并可能导致错误。 - theguy
显示剩余5条评论

5
这将帮助您获取提交之间所有已删除的文件,而无需指定路径,如果有许多已删除文件,则非常有用。
git diff --name-only --diff-filter=D $commit~1 $commit | xargs git checkout $commit~1

2

使用git show $REV:$FILE,正如其他人已经指出的那样,可能是正确的答案。我发表另一个答案是因为当我尝试这种方法时,有时会从git中得到以下错误:

fatal: path 'link/foo' exists on disk, but not in 'HEAD'

当文件路径的某些部分是符号链接时,就会出现问题。在这种情况下,git show $REV:$FILE 方法将无法工作。重现步骤:
$ git init .
$ mkdir test
$ echo hello > test/foo
$ ln -s test link
$ git add .
$ git commit -m "initial commit"
$ cat link/foo
hello
$ git show HEAD:link/foo
fatal: path 'link/foo' exists on disk, but not in 'HEAD'

问题在于,像realpath这样的工具在这里并没有帮助,因为符号链接可能不再存在于当前提交中。我不知道一个好的通用解决方案。在我的情况下,我知道符号链接只能存在于路径的第一个组件中,所以我通过两次使用git show $REV:$FILE方法来解决了问题。这是有效的,因为当git show $REV:$FILE用于符号链接时,它的目标会被打印出来:
$ git show HEAD:link
test

与文件不同,使用目录命令将输出一个标题,然后是目录内容:
$ git show HEAD:test
tree HEAD:test

foo

在我的情况下,我只是检查了第一次调用 git show $REV:$FILE 的输出,如果它只有一行,那么我就用结果替换了路径的第一个组件,以通过 git 解析符号链接。


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