git checkout -- <em>path</em> 复制从索引/暂存区,到工作树
如果您命名的 path 是一个目录,Git 会将它知道的所有文件(在索引/暂存区域中找到)复制到工作树中,从该目录开始并包括任何子目录。
不过,要使用此答案,您需要了解每个文件的三个活动副本。
每个文件的(最多)三个副本
请记住,在 Git 中,每个文件在任何时候都最多有三个 版本处于活动状态。例如,假设在您的存储库中,您已提交名为README.txt 和a.ext 的文件。 README.txt 有三份,a.ext 有三份,可供您随时使用。三份副本中的两份采用特殊的仅 Git 格式。
如果我们使用git show 用来访问Git-only 格式文件的语法,我们可以这样描述这三个副本:
HEAD index work-tree
--------------- ----------- ----------
HEAD:README.txt :README.txt README.txt
HEAD:a.ext :a.ext a.ext
如果你现在创建一个新的未跟踪文件b.dat,你有这个:
HEAD index work-tree
--------------- ----------- ----------
HEAD:README.txt :README.txt README.txt
HEAD:a.ext :a.ext a.ext
b.dat
索引/暂存区域中还没有b.dat 的副本。 有另外两个文件的两个逻辑上独立的副本,即使它们在 HEAD 和索引中都是相同的。 (当它们相同时,Git 会自动共享底层副本,因此不需要额外的空间。)
工作树副本是普通文件
存储在工作树中的任何文件的任何副本都只是一个普通文件。你可以用它做任何你的电脑允许你做的事情。 Git 不在乎你对这些文件做了什么。如果你问 Git 有什么不同,Git 会告诉你工作树副本与索引副本不同。
git add <em>path</em> 复制从工作树,到暂存区
假设此时您使用您喜欢的任何编辑器修改README.txt,该编辑器编辑工作树副本(它根本无法使用或触摸索引副本,除非它非常了解 Git)。工作树副本现在不同于索引副本。索引副本采用特殊的 Git-only 格式,准备好进入下一次提交。
您现在需要运行git add README.txt,将更新的工作树文件复制到索引中。完成后,旧版本的README.txt 仍然是HEAD 提交,也是 是特殊的 Git-only 格式,但现在 HEAD:README.txt 与 :README.txt 不同,而:README.txt 与README.txt 相同。
每个文件的HEAD提交副本是只读的;索引副本不是
任何提交中的任何内容都无法更改。因此,您提交的README.txt 的副本和您提交的a.ext 的副本将安全地永久保存在您的存储库中1。索引/暂存区域中的副本可能与HEAD 提交中的副本相同,也可能不同,可以随时覆盖。它开始时与HEAD 提交中的相同,2 但git add 复制从 工作树,到索引。
1如果你放弃或删除一个提交(通常通过git reset 或git rebase -i),你可能会导致Git 丢失冻结副本:冻结副本只持续与提交一样长(s) 包含它。然而,大部分 Git 都是围绕添加新提交的想法构建的,而不是删除旧提交。
2如果您Checkout another branch when there are uncommitted changes on the current branch,您可以击败“结帐或提交后 HEAD 和索引匹配”的正常情况。这个答案没有足够的空间来讨论这些细节。
git commit 将所有索引副本冻结到新的提交中
git commit 所做的是将当时索引中的每个文件以当时索引中的形式获取,并将其冻结为只读提交副本。这组提交的文件成为新的HEAD 提交。一旦 git commit 完成运行并返回提示,您将获得一个新的提交,并且您的 HEAD 提交和您的索引匹配 - 因为 Git 将新提交 from 设为索引!
未跟踪的文件
Git 非常简单地定义了一个未跟踪的文件:它是一个不在索引中的文件。就是这样——这就是它的全部——但它有一个强烈的后果:如果b.dat 不在索引中,git commit 不会将它放入新的提交中。此外,git checkout -- 无法找到 b.dat,因为它不在索引中,因此无法覆盖工作树副本。
请注意,仅仅因为存储库中的 some 提交中存在某些文件并不意味着该文件被跟踪!当且仅当该文件现在在索引中时,该文件才会被跟踪。如果您对 确实 包含该文件的提交运行 git checkout,然后——那时——Git 会将文件从提交复制到索引中,然后进入工作树。 那时该文件将被跟踪。如果您随后从索引中明确删除该文件,届时该文件将停止被跟踪。因此,您必须始终牢记或测试您的索引中是否存在某个特定文件的副本,以了解它是否被跟踪。
git checkout <em>commit</em> -- <em>path</em> 复制 from 提交,to 索引和工作树
在这里,使用git reset,Git 变得过于复杂,将多个不同的东西塞进一个命令中。当您使用git checkout with 路径,但没有 提交或树说明符时,Git 会从索引复制到工作树。当您使用 git checkout 和路径 和 提交或树时,Git 会将索引 和 工作树复制到 both 中。
git reset -- <em>path</em> 复制 from 提交,to 索引,只留下工作树
git reset 的这种特殊形式与路径一起使用,从提交复制到索引/暂存区域。请记住,索引已经包含所有跟踪文件的副本,因此这只是用其他副本覆盖这些副本。默认情况下,git reset 用于获取文件的提交是 HEAD 提交——因此它从活动的 HEAD 副本复制到索引中。
任何文件的工作树副本都会被单独保留。该文件存在于HEAD 提交中的事实意味着该文件可能被跟踪:文件未被跟踪的唯一方法是如果您签出提交,但随后显式删除了索引副本.在这种情况下,git reset -- <em>path</em> 将文件放回索引中,以便再次对其进行跟踪。
但是请注意,您可以使用 git reset <em>commit</em> -- <em>path</em> 从某个特定提交中复制文件。如果该文件在HEAD 提交中不,则该文件很可能在您的git reset 操作之前未被跟踪(不在索引中),但在之后被跟踪(在索引中)。这一切都取决于您已经对索引进行了哪些更改。
总结
运行git status 进行两个比较:
-
第一个比较是HEAD vs index。这里有什么不同的地方提交提交。
HEAD 中的文件可能也在索引中(因此所有这些文件都被跟踪)。如果索引中的副本与 HEAD 中的副本不同,Git 将调用 staged for commit。如果索引中的副本与HEAD 中的副本相同,则文件本身仍会被跟踪和暂存——只是git status 没有打扰提及 em>它。
这里的关键思想是,即使每次提交都是所有文件的完整快照,但我们倾向于了解提交的是:这次提交有什么不同比它的前身? 所以git status 告诉我们,如果我们采用当前的暂存区域(提议的新提交)并将其实际转变为新的提交,会有什么不同。
-
第二个比较是索引与工作树。此处的任何不同之处都没有提交提交。
跟踪索引中的文件。对于那些与工作树中的内容相匹配的人,git status 只是懒得提及它们。对于那些不同的,git status 提到它们,因为没有为提交上演。对于根本不在索引中但在在工作树中的文件,Git 抱怨它们未被跟踪。
再一次,这里的总体思路是,我们关心每次提交与其前身相比的实际不同之处。如果我们有未暂存或什至未跟踪的文件与其暂存副本不同(或缺少暂存副本),我们可以使用git add 将它们复制到暂存区域。如果我们有一个工作树a.ext 与暂存的a.ext 完全相同,它与HEAD:a.ext 完全相同,我们可能不关心它,所以我们根本看不到它。
要让 Git 关闭绝对不应该提交的未跟踪文件,您可以在 @987654390 中列出这些未跟踪文件(按名称或全局模式,例如 *.o 或 *.pyc) @指令。这可以防止 Git 自动将文件添加到 git add . 或 git add --all 中,并防止 git status 抱怨。但是请注意,如果某个文件已经在索引中——不管它可能已经到达那里——在.gitignore 中列出该文件的名称或模式没有效果。
复制文件的主要操作有:
-
git add <em>path</em>:从工作树复制到索引
-
git checkout -- <em>path</em>:从索引复制到工作树
-
git checkout <em>commit</em> -- <em>path</em>:从 commit 复制到索引和工作树
-
git reset [<em>commit</em>] -- <em>path</em>:从commit(默认HEAD)复制到索引
删除文件的主要操作是:
-
git add <em>path</em>:如果 path 在索引中但从工作树中丢失,则从索引中删除
-
git rm --cached <em>path</em>:从索引中删除,但不是工作树
-
git rm <em>path</em>:从索引和工作树中删除
-
git reset [<em>commit</em>] -- <em>path</em>:如果 path 不包含在 commit 中,则从索引中删除(默认为 HEAD)
除此之外,您还可以使用git commit --only <em>paths</em> 或git commit --include <em>paths</em> 触发一些特殊情况,但这些基本上等同于首先在这些路径上执行git add。始终牢记索引,并注意git status 总结了索引的差异,而不是列出索引的内容,因此如果您有一个 30,000 文件的大型项目,您只会看到几个 有趣的个文件,而不是全部 30,000 个文件。