这里的根本问题是 Git 不是从工作树而是从索引进行提交,这就是为什么你需要git add
首先是文件——但是the索引是一种善意的谎言,因为can be更多的索引文件不仅仅是一个标准文件。 (该指数也称为暂存区 or the cache,具体取决于 Git 的哪一部分正在执行调用。)
The索引,我的意思是一个标准索引,是一个文件.git
named index
。如果您检查您的.git
目录下你会发现这样一个文件。过去,确实只有这一个文件。在现代 Git(2.5 及以上)中,由于添加了工作树,情况变得更加模糊:实际上每个工作树都有一个索引文件,因此.git/index
只是the索引为main工作树。有一个辅助the每个工作树的索引——但这并不是我想要表达的意思,这里,这只是一个例子,展示了存在一个索引的假设如何已经在边缘磨损。诚然,您使用的是 Git 1.8.3.1(它确实很旧),但它也比简单的善意的“一个索引”设置更复杂。
当你使用git commit -a
, Git 创建一个新的额外索引。当你使用git commit .
,你正在调用git commit --only .
(详细信息请参阅文档),Git 使得two新的额外索引(索引?)。
Git 的所有部分都能够重定向restGit 使用不同的非标准索引,以及这些不同的选项git commit
利用这个功能。注意git commit -a
相当于git commit --include
后跟需要添加的任何文件的名称。真正棘手的情况是你正在使用的情况,git commit --only
.
一旦开始增加索引文件,事情就会变得混乱!
请记住,索引本质上是提议的下一次提交。如果只有一个索引(对于此工作树,如果我们谈论的是 Git 2.5 或更高版本),则只有一个建议的下一次提交。这并不是太难,我们只需要考虑有three每个文件的副本。让我们选择一个文件,例如README.md
:
-
HEAD:README.md
是当前提交的版本README.md
。你无法改变它。 (你可以移动HEAD
本身,但是提交的副本README.md
位于提交内部,通过提交的哈希 ID 找到,并且不会更改。)
名字HEAD:README.md
只能在 Git 内部工作。该名称访问该文件的冷冻、Git 化、冻干副本;这个副本永远不会改变。你可以看到它git show HEAD:README.md
, 例如。
-
:README.md
是的副本README.md
在索引中。它原本是一样的HEAD:README.md
但如果你跑了git add README.md
,现在可能有所不同。
名字:README.md
也只能在 Git 内部工作。该名称可访问存储在索引中的可替换但经过 Git 化(冻干格式)的文件副本。您可以随时将其替换为git add
.
-
最后,README.md
是一个普通(非 Git 化)文件。它不在 Git 中!它不在索引中!它在你的工作树,您可以使用所有常用的计算机工具查看并处理它。 Git 实际上不会将此文件用于任何用途,它只是在您检查其他提交时覆盖它或删除它。 Git 用它做的唯一一件事就是用它来检查它git status
等等,就是让你使用git add
to copy它回到索引中,覆盖之前的内容(并在此过程中将其冷冻干燥)。
Running git status
运行两个git diff
s:
-
第一个比较HEAD
提交到索引,即当前提交中的内容与建议的下一次提交中的内容。任何事物不同的这里被列为暂存以供提交。任何相同的事情,Git 都会默默地什么也不说。
-
第二git diff
将索引与工作树进行比较,即建议提交中的内容与可以复制到索引中的内容。任何事物不同的这里被列为未暂存提交。任何相同的事情,Git 都会默默地保持沉默。
-
(然后最后一步是检查工作树中根本不在索引中的文件。Git 会抱怨这些文件,说它们未被跟踪,除非您将它们列出在.gitignore
。被列入.gitignore
不会改变索引中是否存在该文件的副本,它只会改变 Git 是否发出呜呜声。)
当你跑步时git commit
,Git 打包索引中的所有内容,并使用它来进行新的提交...unless你用--only
, --include
, or -a
.
Indices 出胡事
With git commit --only
, Git 使three索引文件:
- 一种是标准的。一开始就没有动过。这才是正常的
.git/index
.
- 一个是那个的副本,带有
--only
files git add
编辑它。在里面.git/index.lock
在某一点。Maybe它总是在这里!如果是这样,这将提供一种处理我在下面概述的情况的方法。但没有任何文件可以保证这一点。
- 第三种是第一次提取出来的新鲜的
HEAD
, then git add
ing the --only
文件到它。
如果你没有git add
任何事物before你跑了git commit -a
,第一个和第三个索引文件匹配,因为添加了--only
将文件添加到常规索引与从其中创建新的临时索引具有相同的效果HEAD
并添加--only
文件到它。但否则所有三个文件可能会有所不同!
然后 Git 从third指数。如果新提交成功,Git 会将常规索引替换为second索引(此替换通过rename
系统调用)。否则 Git 将返回到正常索引。 (请注意,工作树根本没有发生任何事情。)
如果你使用git commit --include
or git commit -a
, Git 仅使one额外的索引,这样你就有:
- 标准指数在
.git/index
,以及您到目前为止添加的内容;和
- 临时文件中的额外索引:这作为标准索引的副本开始,但随后 Git 将列出的文件或其他修改的文件添加到该索引。
然后 Git 开始提交过程。如果一切顺利,当 Git 完成时,Git 会重命名临时索引,使其成为标准索引。如果情况不妙,Git 会删除临时索引,而标准索引保持不变。同样,工作树没有任何反应。
引入预提交钩子
Git 在准备好任何额外的索引文件后运行您的预提交挂钩。特殊环境变量$GIT_INDEX_FILE
命名 Git 将用于进行新提交的索引。所以有三种情况,其中两种还不错,一种很糟糕:
- 你正在做一个正常的提交。
GIT_INDEX_FILE
命名正常索引,一切正常。
- 你正在做一个
git commit --include
or git commit -a
and GIT_INDEX_FILE
命名第二个索引;没有第三个索引;如果提交完成,Git 将重命名第二个索引。
- 你正在做一个
git commit --only
and GIT_INDEX_FILE
命名第三个索引。没有简单的方法find第二个索引,如果提交成功,该索引将在提交后到位!
如果您选择对存储在索引中的文件进行更改,您的工作就是将它们更改为 Git 用于提交的索引。为此,您可以使用git add
如果您愿意,因为这会将文件从工作树复制到名为的索引$GIT_INDEX_FILE
.
但第一个问题是你must not查看中的文件工作树。他们是无关紧要的!它们可能包含与索引中完全不同的内容。这期间尤其如此git commit --only
.
第二个也是更大的问题是,如果您更新了third索引那个git commit --only
正在使用,您还应该更新second索引那个git commit --only
正在使用。这部分很棘手,因为除了假设它位于.git/index.lock
。虽然这可能有效,但我不会在这里建议这样做。
我真的对此没有什么建议——你发现的任何偷偷摸摸的方法都可能会失败,因为处理第三个索引的代码(当前 2.21 版本的 Git 称之为“假索引”)在 1.8 和现代 Git 之间已经发生了很大的变化。通常的最佳实践建议是not在 Git 挂钩中进行任何特殊格式化。相反,让 Git 挂钩仅仅检查whether文件的索引副本格式正确:如果是,则继续提交,如果不是,则中止提交。剩下的就交给用户吧。
另一种选择
我见过和使用的另一种选择是检查实际设置$GIT_INDEX_FILE
。如果设置为.git/index
,用户正在使用git commit
无需任何特殊设置。另一个技巧这个相同的预提交钩子(调用 clang-format 和 autopep8)是比较要格式化的文件的索引和工作树,如果不匹配则拒绝运行。