理解git commit --only和pre-commit hooks

时间:2016-12-30 18:57:38

标签: git githooks jetbrains-ide

我正在使用预提交钩子重新格式化代码,一般来说,它可以工作;它重新格式化git add任何暂存的文件,结果提交包含重新格式化的代码。

然而,它与git commit --only(这是JetBrains IDE使用的变体)不能很好地协作,我试图理解为什么。 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如何与索引交互以产生上面显示的结果 - 更好的是,是否有办法让我的预提交钩子很好地与它一起玩?

我的理解是git commit --only与工作树中的文件版本一起使用,所以我尝试从预提交钩子中删除git add步骤,看看会发生什么,结果在提交的文件的格式不正确的版本和工作树中正确格式化的版本(符合我对标准git commit的期望,但我不确定在{{的上下文中会发生什么1}})。

我知道使用git commit --only过滤器来重新格式化代码的可能性,而不是预先提交的钩子,但是这种方法引入了一些情境复杂性,如果避免可能的。

注意:此问题与Phpstorm and pre commit hooks that modify files有关,但重点是在clean的上下文中解决问题。此外,JetBrains似乎没有解决这个问题,正如在该问题的公认答案中所建议的那样。

2 个答案:

答案 0 :(得分:5)

精确的细节因Git的一个版本而异,有些人 - 我不是说JetBrains的人就在其中,因为我不知道 - 试图绕过Git做事的过程和过程,搞砸了,要么他们不能解决,或者解决方案是Git版本依赖。但是,这些Git钩子的主要思想是完全相同的:

  • index 包含commit-to-make和
  • work-tree 包含工作树。

首次运行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 test。因此test的索引版本为“indexvers”。现在我们在工作树中再次修改,包含“工作者”,然后运行git commit --only testgit commit --include test

我们肯定知道应该进入新提交的内容:它应该是包含workvers的测试版本,因为我们特意告诉Git提交工作树版本。但是应该在索引和工作树之后留下什么?这取决于我们是否使用--include vs --only?我不知道在这里考虑什么“正确”的答案!我可以告诉你的是,当我之前尝试使用Git时,它往往会包含workvers(在索引和工作树中)。也就是说,临时索引的版本成为普通索引的版本,并且工作树文件没有受到影响。

(如果您有操纵索引和/或工作树的Git钩子,您将能够撬开“将索引复制到已保存索引,然后复制回”与“将索引复制到temp-index”之间的区别,然后使用临时索引“。)

1 这是我在测试各种行为时的实际实现,但实际实现可能有所改变。例如,Git可以将“普通”索引保存在临时文件中,然后替换普通索引,以便GIT_INDEX_FILE 设置。而且,它可能取决于--include vs --only

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

答案 1 :(得分:4)

我遇到了同样的问题。这是我从Jetbrains dev 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