Git:签出与恢复单个文件

  • 本文关键字:恢复 单个 文件 Git git
  • 更新时间 :
  • 英文 :


我将在前面提到我已经使用Git多年了,但我的知识仅限于非常基本的工作流程。意识到这一点,我已经变得好多了"高级";Git的功能,但有一个问题我不太清楚:

git checkout [file]究竟做什么?

我的理解是,它检索特定文件的最新修订/提交,丢弃任何未标记的工作副本。

所以,我想知道这是否与git restore [file]有任何不同,以及我何时可能想要使用这两者中的任何一个。

遗憾的是,我一直没能在其他地方找到一个非常明确的答案。

在axic和Jay的回答之间,你有一个相当完整的画面,但我想以不同的方式介绍整个事情。这里有两种感兴趣的git checkout形式:

  1. git checkout --path:path参数被视为文件或目录——如果它是目录,则表示该目录中的所有文件,递归地——Git将在索引中找到的文件的当前副本复制到工作树中。

    我们稍后将详细介绍该指数。

  2. git checkout HEAD --path:path参数的处理方式相同,但Git将HEAD提交中找到的文件的当前副本复制到索引和工作树

    您可以在这里使用HEAD以外的名称:任何提交说明符都有效,Git将提交文件副本的复制到索引和工作树中。

对于git restore,您有更大的灵活性。你可以告诉Git复制一个文件:

  • 从索引或任何提交(使用--source选项显式选择源,或者让命令自己确定一个源)
  • 到索引,或到工作树,或两者兼有(使用--staged选项复制到索引,和/或使用--worktree选项复制到工作树)

请注意,如果使用git checkout并选择提交,文件将同时到达两个位置:索引和工作树。复制";从索引到索引"0";并没有真正做任何事情(可能会让你抱怨,因为这似乎是一件奇怪的事情)。

关于索引和工作树

你的工作树非常简单明了。Git将每个提交存储为一个完整的快照:您提交的每个文件的完整副本。但是,存储在Git提交中的文件是以一种特殊的、只读的、压缩的、重复数据消除的、仅限Git的格式存储的。这样可以防止.git目录(存储库本身)过大过快。大多数提交实际上只是重复使用大多数以前的文件,这意味着重复数据消除效果非常好。对于那些工作不太好的时候,压缩通常会处理剩下的部分。1另一方面,因为这里的文件是只读的,不是普通文件,所以实际上不能使用来完成工作。Git必须将这些文件复制出来,然后再转换回你和你的计算机可以使用的普通文件。

你的文件的有用的日常工作副本出现在Git称之为工作树的工作树用它们!它确实创建了它们,从提交中提取了压缩的仅Git存储的文件,但它不会从中提交

Git的索引也被称为暂存区,有时(现在很少)被称为缓存。它保存了当前提交的每个文件的副本2,现在可以进入您将要进行的下一次提交了3当您运行git commit时,Git只会打包当时索引中的任何内容。这就是为什么您必须不断地git add一个文件。如果你更改了一个文件,你就没有更改Git的文件副本:你只更改了工作树副本。您使用git add告诉Git:将我更新的工作树文件复制回您的索引中,替换那里的旧副本

因此,该索引始终包含您提出的下一次提交。使用git add,或者如果需要删除文件,则使用git rm来更新或删除文件的这些索引副本。它们从您签出的提交的副本开始:当前或HEAD提交。因此,如果没有使用git add,则索引和HEAD提交副本通常匹配4当它们匹配时,索引副本的存在往往是不可见的:例如,git status没有提到索引副本。

不过,如果您很好奇,可以尝试运行git ls-files --stage(为大型存储库中的大量输出做好准备!--此命令不会通过寻呼机运行其输出)。这将显示索引中的每个文件。


1压缩、消除重复的格式不适用于一些非常大的二进制文件格式。在这种情况下,Git可能会变得臃肿。这就是像Git LFS这样的东西的用武之地

2从技术上讲,索引并不包含文件的实际副本,而是一个引用。只有当您开始使用可以直接检查索引内容的低级别命令git update-indexgit ls-files --stage时,差异才会显示出来。不过,对于日常使用,您可以将索引视为保存文件的副本。索引副本采用冻结和重复数据消除的格式,即预冻结和预重复数据消除,尽管可以覆盖,这使得提交速度很快。

3当您必须处理合并冲突时,索引还将扮演一个扩展的角色。这个答案不包括这个案例。

4这里的一词通常用于处理许多角情况,包括git reset --softgit commit的特殊变体,它们不必使用正则索引。

git checkout做两件事:切换分支和将文件恢复到特定状态。

这两种行为是在Git2.23中的两个新命令中提取的:git switchgit restore

git checkout仍然同时做这两件事,而且可能会在很多年后继续做,但建议使用新命令,因为它们更清晰。

git checkout file将指定的文件或文件夹恢复为索引=其暂存状态(如果没有为提交暂存的更改,则恢复为当前HEAD)。

假设当前提交(HEAD)的文件内容是a

您过去曾使用git add进行从内容a到内容b的更改。

那么您当前的git diff --staged显示

diff --git a/file b/file
--- a/file
+++ b/file
@@ -1 +1 @@
-a
+b

此外,还对文件进行了进一步的更改,但尚未添加

运行git checkout -- file会将文件恢复为内容b并丢弃其当前内容。这意味着已暂存的更改(在git diff --staged -- file中可见)将保留,而未暂存的更改将丢失(在git diff -- file中可见)。

如果您想实际将文件更改回HEAD(内容a),则必须指定

git checkout HEAD -- file

然后,未暂存和暂存的更改都将被撤消。

(也可以先用git reset -- file取消所有更改,然后用git checkout -- filegit reset --hard同时执行这两项操作)


关于与git restore的差异-由于该命令被标记为THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.,因此在这里很难做出任何有意义的预测

然而,根据当前文档,它显然默认情况下也不会从HEAD恢复,而是从索引恢复(因此它将保留阶段性更改,就像git checkout -- file一样)

但我确实注意到了一些有趣的区别——如果文件在源中被删除(或从未存在),git checkout -- file将拒绝执行任何操作。因此,如果git rm是一个文件,那么就向它写入内容——使用git checkout -- file无法再次删除它。CCD_ 55显然可以用于";恢复";文件到其不存在的状态,即删除它。

最新更新