了解 git commit --only 和 pre-commit 钩子

Understanding git commit --only and pre-commit hooks

我正在研究重新格式化代码的预提交挂钩,总的来说,它可以工作;它重新格式化和 git adds 任何暂存文件,并且生成的提交包含所需的重新格式化的代码。

但是,它不能很好地与 git commit --only(JetBrains IDE 使用的变体)一起使用,我正在尝试理解原因。 git commit --only 和预提交挂钩的组合导致不希望的 index/working 树状态,如以下事件序列所述:

如果我对文件进行了格式错误的小改动,然后 运行 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、预提交挂钩 运行s 提交,并提交更改和重新格式化的 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 如何与索引交互以产生上面显示的结果 — 更好的是,是否有办法让我的预提交挂钩与它很好地配合?

我的理解是 git commit --only 与工作树中的文件版本一起工作,所以我尝试从预提交挂钩中删除 git add 步骤以查看会发生什么,并且它导致提交格式不正确的文件版本和工作树中格式正确的版本(这符合我对标准 git commit 的期望,但我不确定在上下文中会发生什么git commit --only)。

我知道可以使用 clean 过滤器来重新格式化代码,而不是使用预提交挂钩,但是这种方法引入了一些情况复杂性,这对尽可能避免。

注意: 这个问题与 Phpstorm and pre commit hooks that modify files 有关,但重点是在 git commit --only 的上下文中解决问题。此外,JetBrains 似乎并未解决该问题,正如该问题的已接受答案中所建议的那样。

确切的细节因 Git 的一个版本而异,有些人——我不是说 JetBrains 的人也在其中,因为我不知道——试图绕过 Git =86=] 做事并在此过程中把事情搞砸,以至于它们无法解决,或者解决方法取决于 Git-version-dependent。然而,这些 Git 钩子的主要思想都是一样的:

  • index 包含 commit-to-make,并且
  • 工作树包含工作树。

当您第一次 运行 git commit 时,这两个不需要同步,如果您将文件添加到 git commit 命令,使用 --only--include,Git然后必须做一个new索引,可能和常规的普通索引不同。所以现在我们结束了一个环境变量,GIT_INDEX_FILE,设置为一个新的临时索引的路径。1 因为所有 Git 命令自动尊重环境变量,预提交挂钩将使用临时索引的文件,git write-tree 将使用临时索引的文件。

当然,任何未能尊重临时索引的东西——或者,可能,取决于--include vs --only,只是使用的内容工作树——会得到错误的答案。

尽管如此,即使是 尊重环境变量的程序,仍然存在一个问题。假设我们有一个文件——我们称它为 test,因为这是它的目的——最初包含 "headvers",并匹配当前 (HEAD) 提交。现在我们在工作树中修改它以包含 "indexvers" 和 运行 git add testtest 的索引版本因此显示为 "indexvers"。现在我们在工作树中再次 修改它,以包含 "workvers" 和 运行 git commit --only testgit commit --include test

我们肯定知道新提交中应该包含什么:它应该是包含 workvers 的测试版本,因为我们特别告诉 Git 提交工作树版本。但是索引和工作树 之后 应该留下什么?这是否取决于我们使用的是 --include 还是 --only?我不知道这里的 "right" 答案应该考虑什么!我只能告诉你的是,当我之前尝试使用 Git 时,它往往会在之后包含 workvers (在索引和工作树中)。也就是说,临时索引的版本变成了普通索引的版本,工作树文件没有受到影响。

(如果你有 Git 操纵索引 and/or 工作树的挂钩,你将能够撬开 "copying index to saved-index, then copying back" 与 "copying index to temp-index, then using temp-index" 之间的区别。 )


1这是我一次测试各种行为时的实际实现,但实际实现可能会发生一些变化。例如,Git 可以将 "normal" 索引保存在一个临时文件中,然后替换普通索引,这样 GIT_INDEX_FILE 而不是 设置了.而且,同样,它可能取决于 --include--only.

注意 git commit -a 也可能使用临时索引,也可能不使用。我相信这种行为在 Git 1.7 和 Git 2.10 之间发生了变化,基于 运行ning git status 在另一个 window 中的结果,同时仍在编辑提交消息window 运行宁 git commit -a.

我遇到了同样的问题。这是我从 Jetbrains 开发人员 Dmitriy Smirnov 那里得到的解决方案。


使用

git commit --only 有几个原因:

    不支持
  1. Git 阶段 - https://youtrack.jetbrains.com/issue/IDEA-63391
  2. 它允许进行部分提交——提交单个文件。这对于支持 IDE.
  3. 中的更改列表至关重要

目前无法更改行为。

鉴于预提交挂钩如下 (ruby):

`git status --porcelain`.lines do |line|
    changed_file = line.split(' ', 2)[1].strip()
    if (File.extname(changed_file).downcase() == '.java')
        system "java -jar bin/google-java-format-1.4-all-deps.jar --aosp --replace #{changed_file}"
        system "git add #{changed_file}"
    end
end

添加一个post-commit挂钩:

git update-index -g

https://youtrack.jetbrains.com/issue/IDEA-81139#comment=27-295117