TL;DR
基本上,git status 是 Git 一次告诉你一切的最佳尝试。
如果一个文件没有显示,并且您认为它可能会被忽略,git check-ignore -v 很有用:它会告诉您为什么该文件被忽略,如果它被忽略,或者说如果不被忽略,什么都没有。
缺少一件事,这是一种查看可以在索引中设置的特殊标志的快速简便的方法。
git ls-files 可以显示索引中的内容,但它对用户不太友好。
长
在 Git 中,tracked 仅表示 存在于索引中。同时,只能忽略未跟踪的文件。根据定义,未跟踪文件是一个不在在索引中,但在在你的工作树中的文件——你做你的日常工作。
索引也可能最好描述为下一次提交的内容。 (它比这更复杂——因为索引有一些额外的角色——但这是简短的版本。)一个新的git commit 只是将索引中的文件打包为新提交制作快照。其他一切都源于此。
请记住,在任何时候,实际上都有三个跟踪文件的活动副本,尽管其中两个可能会丢失:
-
有HEAD(当前提交)副本,这是文件最初的来源。如果没有HEAD 副本,则该文件是新文件:与当前提交相比,它将是下一次提交中添加的文件。
文件的HEAD 副本与任何文件的任何已提交副本一样,都是只读的。你所做的任何事情都无法改变它。它是一种特殊的仅 Git 格式,而且很难找到:您必须让 Git 提取它成可用的形式。
-
有索引副本。从技术上讲,这实际上是对其他地方的副本的引用。它也是文件的特殊内部 Git 格式(在 Git 术语中是“blob 对象”),但出于您自己的目的,您可以将其视为一个完整的单独物理副本,因为您可以随时 用一个新的副本覆盖它,或者删除它。
-
最后是工作树副本。这是您可以使用普通计算机工具查看的文件的唯一版本。它采用您计算机的普通格式,因此您可以使用此副本做任何您想做的事情。
如果跟踪文件的工作树副本丢失,与(未删除的)索引副本相比,它看起来已被删除。
当你第一次git checkout 某个提交时——每个提交都由它的大而丑陋的哈希 ID 唯一指定,这样你就可以根据需要检查旧的——Git 将提交的文件读入索引,以便所有提交的文件文件在那里,但没有其他文件。1 现在HEAD,这是您刚刚签出的提交,并且索引匹配。同时,同样的git checkout 还会从您的工作树中删除您刚才提交的所有跟踪文件,并将它们替换为来自新@的索引中的文件987654333@ 提交。所以现在所有的索引副本都匹配所有的工作树副本。您的未跟踪文件都不会在这里被触及,只有被跟踪的文件。
效果是在git checkout 之后,每个跟踪文件的所有三个副本都匹配。 HEAD 提交中有不可更改的副本,已复制到索引中,索引已复制到您的工作树中。
从这里开始,您可以:
- 随意修改(或创建或删除)工作树文件;
- 使用
git add复制工作树文件到索引:现在两者匹配,如果文件之前根本不在索引中,现在它在索引中;
- 使用
git rm删除一个文件从两个索引和工作树:现在它从两者都消失了;李>
- 使用(一种形式)
git reset 将文件从HEAD 复制到索引而不接触工作树;和/或
- 使用各种其他 Git 命令,例如
git rm --cached 或特殊形式的 git checkout,现在通过新的 git restore 在 Git 2.23+ 中更容易
实现对索引和/或工作树的其他操作。
无论您做什么,文件的HEAD 副本都不会更改。索引副本要么存在,因此文件被跟踪,要么不存在,因此文件未被跟踪。工作树副本是否存在:Git 并不关心,因为 Git 将使用 index 副本进行下一次提交。
要查看有什么不同,您必须让 Git 进行比较:
-
HEAD vs index:任何相同的东西都很无聊; git status 对此只字未提。任何不同的东西都很有趣:git status 告诉你staged for commit,因为如果你现在运行git commit,你将做出的新提交将不同于当前提交。
-
index vs work-tree:任何相同的东西都很无聊,git status 什么也没说。任何不同的东西都很有趣:git status 告诉你没有为提交暂存,因为你可以更新文件的索引副本(或删除它)以匹配工作-tree,之后它可能不再匹配当前提交。
最后——好吧,几乎最后——给定任何未跟踪文件——根据定义,它不在索引中——git status 会抱怨它未被跟踪,除非你使用“忽略”文件让它闭嘴。你让 Git 闭嘴的是你被忽略的文件。
请注意,跟踪文件在所有三个副本中可能不同。在这种情况下,git status 运行的两个比较(HEAD-vs-index 和 index-vs-work-tree)都会产生“有趣”的结果。您将看到该文件同时暂存于提交和未暂存于提交。有时,git status --short 的结果在此处很有用,它将两个结果都放入每个文件一行的多列输出的前两列中。
1这有点过于简单化了。关于何时可以在Checkout another branch when there are uncommitted changes on the current branch 的结帐中进行未提交的更改,有很多血淋淋的细节。
假设不变并跳过工作树
您提到了--assume-unchanged 标志。还有一个--skip-worktree 标志。这些标志在内部略有不同,但在大多数情况下,实现相同的结果。它们只能在索引中 的文件上设置,因此它们仅适用于被跟踪的文件。此外,只有 您(或您运行的某些命令)可以设置它们:新克隆永远不会设置 任何这些标志。
它们的作用是告诉 Git——尤其是 git status,它正在进行很多文件比较——它不应该费心查看工作树文件的副本。相反,当 Git 会检查工作树副本时,Git 可以假设工作树副本与索引副本匹配。
这些标志有点难以查看,因为它们存储在索引中,而索引本身有点难以查看。 git ls-files 命令可以显示它们以及索引中的所有条目——在许多项目中往往有数千行,因此在实践中并不是那么有用。我写了一点Python script that I call git-flagged,这样我就可以运行git flagged 来查看哪些文件设置了这些标志,和/或重置这些标志,而不会看到很多其他不相关的东西。2
请注意,git checkout 和 git merge 之类的操作会越过这些标志,并在工作树文件被覆盖时发出警告。也就是说,您可以对某个文件进行一些本地更改,标记索引副本,以便 Git 通常停止向您显示 没有为提交暂存的更改,然后工作一段时间,包括进行新的提交,其中当然使用文件的 index 副本,以便它们与之前的提交保持匹配。但是,这样做了一段时间后,您忘记这些本地更改是故意隐藏的。
在某个时候,其他人,在您定期从中获取提交的某个其他 Git 存储库中,更改了文件。如果您 git checkout 他们的提交,或 git merge 他们的提交,Git 将不得不将文件的 他们的 版本写入您的工作树。幸运的是,Git 不会在没有警告的情况下这样做。不幸的是,您可能忘记了您已经标记了一些文件,并且您可能想要获取所有标记文件的完整列表。
2你也可以使用grep或类似的,但是当我经常使用这些标志时,最好有一些更方便的东西。