我正在处理一个预提交挂钩来重新格式化代码,一般来说,它是有效的;它重新格式化并git add
s任何暂存文件,结果提交包含所需的重新格式化代码。
但是,它不能很好地与git commit --only
(jetbrains ides使用的变体)一起使用,我试图理解为什么。git commit --only
和预提交挂钩的组合会导致不需要的索引/工作树状态,如以下事件序列所述:
如果我对一个文件做了一个小的更改,但出现格式错误,然后运行git status
,我将看到:
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: file.php
no changes added to commit (use "git add" and/or "git commit -a")
如果然后使用
git commit --only -- file.php
提交,则会运行预提交挂钩,并提交已更改和重新格式化的file.php
。但是,如果我再次运行
git status
,这就是结果(我的箭头注释):On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: file.php <-- contains original change, improperly formatted
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: file.php <-- contains original change, properly formatted (per the most recent commit)
新的阶段性更改和工作树中的更改从何而来?
有人能准确地解释
git commit --only
如何与索引交互以产生上面所示的结果吗-甚至更好,是否有办法让我的pre-commit钩子很好地处理它?我的理解是
git commit --only
与工作树中的文件版本一起工作,因此我尝试从预提交挂钩中删除git add
步骤,以查看会发生什么,它导致提交的文件的格式不正确,并且在工作树中提交的文件的格式正确(这与我对标准git commit
的期望相符,但我不确定在git commit --only
上下文中会发生什么)。我知道使用
clean
过滤器来重新格式化代码的可能性,而不是使用预提交挂钩,但是这种方法引入了一些情况上的复杂性,如果可能的话,最好避免这种情况。注意:这个问题与Phpstorm and pre commit hooks that modify files有关,但重点是在
git commit --only
上下文中解决这个问题。此外,这一问题似乎并没有得到喷气式飞机的解决,正如对这一问题的公认答案所建议的那样。 最佳答案
确切的细节因git的不同版本而不同,有些人——我不是说jetbrains的人就在其中,因为我不知道git是怎么做的,在这个过程中,把事情搞砸了,要么他们无法解决,要么解决的问题取决于git的版本。但是,这些git钩子的主要思想是相同的:
索引包含要进行的提交,并且
工作树包含工作树。
第一次运行git commit
时,这两个索引不需要同步,如果使用git commit
或--only
向--include
命令添加文件,git必须创建一个新索引,该索引可能不同于常规的普通索引。因此,现在我们得到一个环境变量GIT_INDEX_FILE
,设置为一个新的临时索引的路径。1因为所有git命令都自动尊重环境变量,预提交挂钩将使用临时索引的文件,而git write-tree
将使用临时索引的文件。
当然,任何不尊重临时索引的东西,或者,根据--include
vs--only
,仅仅使用工作树的内容都会得到错误的答案。
尽管如此,仍然存在一个问题,即使程序确实尊重环境变量。假设我们有一个文件,我们称它为test
,因为它的目的是最初包含“headvers”,并匹配当前(HEAD
)提交。现在我们在工作树中修改它以包含“indexvers”并运行git add test
。因此,test
的索引版本读作“indexvers”。现在我们在工作树中再次修改它,以包含“workvers”,并运行git commit --only test
或git commit --include test
。
我们肯定地知道新提交中应该包含什么:它应该是包含workvers
的测试版本,因为我们特别告诉git提交工作树版本。但是之后索引和工作树中应该留下什么呢?这是否取决于我们是否使用了--include
与--only
?我不知道该怎么考虑这个“正确”的答案!我所能告诉你的是,当我以前使用git进行实验时,它往往在之后(在索引和工作树中)包含workvers
。也就是说,临时索引的版本变成了普通索引的版本,并且工作树文件未被修改。
(如果您有操作索引和/或工作树的git钩子,则可以撬开“将索引复制到保存的索引,然后复制回”与“将索引复制到临时索引,然后使用临时索引”之间的区别。)
1这是一次真正的实现,当时我正在测试各种行为,但有可能实际的实现发生了一些变化。例如,git可以将“normal”索引保存在临时文件中,然后替换normal索引,这样就不会设置GIT_INDEX_FILE
。同样,它可能依赖于--include
vs--only
。
注意git commit -a
也可以使用临时索引,或者不使用。我相信这种行为在git 1.7和git 2.10之间发生了变化,这是基于在另一个窗口中运行git status
的结果,同时仍然在运行git commit -a
的窗口中编辑提交消息。