【问题标题】:How to retrieve a single file from a specific revision in Git?如何从 Git 中的特定修订中检索单个文件?
【发布时间】:2010-10-11 05:54:29
【问题描述】:

我有一个 Git 存储库,我想看看一些文件几个月前的样子。我在那个日期找到了修订版;它是27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8。我需要查看一个文件的外观,并将其另存为(“新”)文件。

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

git-show 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8 my_file.txt

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

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

【问题讨论】:

  • 这里的关键:git show(无济于事)使用不同的语法和冒号。 git show 2c7cf:my_file.txt
  • 为了进一步澄清,上述命令要求 git 显示两个单独的对象,一个修订版和一个文件。下面接受的答案在两个项目之间使用冒号,是要求特定版本的特定文件。
  • 在 *nix 上你不需要 PAGER,只需使用 > 进行 shell 输出重定向
  • Checat 有一个重要的评论,对于那些想要将内容导出到某个文件的人。你需要这样的东西: git show {sha}:my_file.txt > old_my_file.txt

标签: git single-file


【解决方案1】:

使用git show

为了完成你自己的答案,语法确实是

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

该命令采用通常的修订样式,这意味着您可以使用以下任何一种:

  1. 分支名称(suggested by ash
  2. HEAD + x 个^ 字符
  3. 给定修订版的 SHA1 哈希
  4. 给定 SHA1 哈希的前几个(可能是 5 个)字符

提示请务必记住,在使用“git show”时,始终指定从存储库根目录开始的路径,而不是您当前的目录位置。 p>

(虽然Mike Morearty 提到,至少在 git 1.7.5.4 中,您可以通过将“./”放在路径开头来指定相对路径。例如:

git show HEAD^^:./test.py

)

使用git restore

使用 Git 2.23+(2019 年 8 月),您还可以使用 git restore 替换 the confusing git checkout command

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

这将仅在工作树上恢复 “源”(-s) 提交 SHA1 或分支 somebranch 中存在的文件。
要恢复索引:

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

-SW--staged --worktree的缩写)


the comments starwarswii 所述

它允许您将内容通过管道传输到文件中,如果您只想快速比较来自提交的文件,这非常有用。

例如你可以这样做:

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

然后比较它们。


使用低级 git 管道命令

在 git1.5.x 之前,这是通过一些管道完成的:

git ls-tree &lt;rev&gt;
在提交中显示一个或多个“blob”对象的列表

git cat-file blob &lt;file-SHA1&gt;
cat 文件,因为它已在特定版本中提交(类似于 svn 猫)。 使用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-object,并简单地转储它反对stdout


注意:从 Git 2.11(2016 年第四季度)开始,您可以将内容过滤器应用于 git cat-file 输出。

commit 3214594, commit 7bcf341(2016 年 9 月 9 日), commit 7bcf341(2016 年 9 月 9 日)和 commit b9e62f6, commit 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 年第四季度)中更正

参见Jeff King (peff)commit cc0ea7c(2017 年 9 月 21 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit bfbc2fc,2017 年 9 月 28 日)

【讨论】:

  • @Oscar 因为git show 基本上将内容转储到stdout(标准输出),您可以简单地将输出重定向到您想要的任何文件(tldp.org/LDP/abs/html/io-redirection.html)。
  • git checkout [branch | revision] filepath 是正确的命令
  • @Gaui 但git checkout覆盖您的文件,而不是git show,后者允许您以不同的名称保存它,以便您可以查看both(当前版本和旧版本)。从问题中不清楚 OP 是否想用旧版本替换其当前版本。
  • 我想指出,^^^ 也可以更一般地写作~~~,或者更好的是~3。使用波浪线还有一个好处是不会触发某些 shell(例如 zsh)的文件名匹配。
  • 我没有足够老的 git 来检查:pre-1.5.x git rev-parse 处理 rev:path 语法吗? (在较新的 git 中,您可以 git cat-file -p $REV:path。但是,git show 也适用于目录路径,因此它不仅更短,而且通常更接近人们想要的。)
【解决方案2】:

如果您希望将当前分支中的文件内容替换/覆盖为上一次提交或其他分支中的文件内容,您可以使用以下命令执行此操作:

git checkout 08618129e66127921fbfcbc205a06153c92622fe path/to/file.txt

git checkout mybranchname path/to/file.txt

然后,您必须提交这些更改才能使它们在当前分支中生效。

【讨论】:

  • 最简单的解决方案,这就是 git-checkout 的设计目的 - 指定路径名意味着只签出匹配的文件。来自 git-checkout 手册页:git checkout master~2 Makefile
  • 那么,如何回到运行这条命令之前的状态呢?
  • @Flint 如果您来自 HEAD 状态,它将像 git checkout HEAD -- [完整路径] 一样简单。
  • 请注意,这会覆盖该路径中的现有文件,而 git show SHA1:PATH 解决方案仅打印到标准输出。
  • 不错!通过查看git help checkout,我无法弄清楚这一点。我必须在某个日期签出一个子目录,使用这种方法,我可以让这个语法工作:git checkout @{YYYY-MM-DD} sub-dir
【解决方案3】:

您需要提供文件的完整路径:

git show 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8:full/repo/path/to/my_file.txt

【讨论】:

  • 不必是完整路径。 git根目录的路径(git show --name-only进来的也够了
  • Erm,来自存储库根目录的完整路径。更好地看看我给出的例子。 “full”之前没有前导斜杠。
  • 仅供参考,如果您在子目录中,您也可以成功使用 ./filename.ext。
  • 我认为关键是,如果你在full/repo/path/to 并且你尝试:git show 27cf8e84:my_file.txt,你将得到如下消息:fatal: Path 'full/repo/ path/to/my_file.txt' 存在,但不存在 'my_file.txt'。 你的意思是 '27cf8e84:full/repo/path/to/my_file.txt' aka '27cf8e84:./my_file .txt'? 就像,Git 可以直接提供帮助,但在这里选择了迂腐。
【解决方案4】:

最简单的方法是写:

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

这会将修订版 27cf8e 中的 my_file.txt 保存为名为 my_file.txt.OLD

的新文件

已使用 Git 2.4.5 进行测试。

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

示例:

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

【讨论】:

  • 附加信息:您可以获得 HASH,例如使用 git 日志
  • @xotix 谢谢。我使用 git log file/path/name.ext 获得了特定文件的所有 HASH 历史记录
【解决方案5】:

在 Windows 中,使用 Git Bash:

  • 在您的工作区中,将 dir 更改为您的文件所在的文件夹
  • git show cab485c83b53d56846eb883babaaf4dff2f2cc46:./your_file.ext &gt; old.ext

【讨论】:

    【解决方案6】:
    git checkout {SHA1} -- filename
    

    此命令从特定提交中获取复制的文件。

    【讨论】:

    • 这怎么排名不高?
    • +1,老实说,我不知道 - 最佳答案列出了 5 种不同的方法,比简单的 checkout 命令复杂得多...
    【解决方案7】:

    并且很好地将其转储到文件中(至少在 Windows 上)-Git Bash:

    $ echo "`git show 60d8bdfc:src/services/LocationMonitor.java`" >> LM_60d8bdfc.java
    

    需要" 引号,以便保留换行符。

    【讨论】:

    • 不错的一个。 +1。我上面提到的git show 语法的很好的补充。
    • 我真的不明白你为什么要使用 echo,不管有没有引号。而且我不明白你为什么想要输出重定向的附加形式。直接写不是更好吗: git show 60d8bdfc:src/services/LocationMonitor.java > LM_60d8bdfc.java 如果出于某种原因你真的想强制dos风格的行结尾,你可以通过unix2dos管道它。但是我从来没有发现在 Windows 上保留 dos 行尾最有用,因为我在 Windows 上使用的除记事本之外的任何文本工具都可以很好地处理 unix 样式的行。
    • git show 60d8bdfc:src/services/LocationMonitor.java >> LM_60d8bdfc.java 为我工作。
    • @Mike:你在 Windows 上吗?
    • 不要使用双引号,因为如果您的文件字符看起来像一个 shell 变量,即 $LANG,它将被替换。 @LưuVĩnhPhúc 是节省者。也不要使用 >> 如果文件存在,它将附加文件并可能导致错误
    【解决方案8】:

    除了其他答案列出的所有选项之外,您还可以将 git reset 与感兴趣的 Git 对象(哈希、分支、HEAD~x、标签...)和文件路径一起使用:

    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 showgit show 27cf8e8:file.txt 当然更直接。

    虽然我将留下这个答案,因为直接运行 git show 可以让您立即获得旧版本,但如果您想用它做点什么,从那里这样做并不像它那样方便是如果您在索引中重置该版本。

    【讨论】:

    • 如果文件在另一个分支上怎么办?
    • 所以我想在特定的其他分支上检索特定文件的特定版本。这可能吗?
    • 是的。具体版本和具体分支:你只需要先找到对应的hash,比如git log --graph --oneline --all。具体文件:给出文件路径。
    • 请记住,这会将文件恢复到与 索引中的哈希值对应的版本。不在工作树中。如果要在工作树中恢复该版本,则需要运行 git commitgit restore &lt;file&gt;,正如我在答案中提到的那样。
    【解决方案9】:

    这将帮助您在不指定路径的情况下在提交之间获取所有已删除的文件,这在删除大量文件时很有用。

    git diff --name-only --diff-filter=D $commit~1 $commit | xargs git checkout $commit~1
    

    【讨论】:

      【解决方案10】:

      正如其他人已经指出的那样,使用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 解析符号链接的结果。

      【讨论】:

        【解决方案11】:

        通过检出先前的提交和复制文件从先前的提交中获取文件。

        • 注意你在哪个分支:git branch
        • 签出你想要的上一个提交:git checkout 27cf8e84bb88e24ae4b4b3df2b77aab91a3735d8
        • 将您想要的文件复制到一个临时位置
        • 检查你开始的分支:git checkout theBranchYouNoted
        • 复制到您放置在临时位置的文件中
        • 将您的更改提交到 git:git commit -m "added file ?? from previous commit"

        【讨论】:

        • 我不知道为什么这个答案会得到如此多的反对。它当然不漂亮,而且显然不是最好的方法。但它确实到达了那里。
        猜你喜欢
        • 1970-01-01
        • 2012-07-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-03
        • 1970-01-01
        相关资源
        最近更新 更多