【问题标题】:Understanding git commit --only and pre-commit hooks理解 git commit --only 和 pre-commit 钩子
【发布时间】:2017-05-15 01:16:57
【问题描述】:

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

但是,它与git commit --only(这是 JetBrains IDE 使用的变体)不能很好地配合,我试图了解原因。 git commit --only 和 pre-commit 挂钩的组合会导致不期望的索引/工作树状态,如以下事件序列所述:

如果我对文件进行格式错误的小改动,然后运行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 与工作树中的文件版本一起工作,所以我尝试从 pre-commit 挂钩中删除 git add 步骤以查看会发生什么,结果不正确 -正在提交的文件的格式化版本和工作树中正确格式化的文件(这符合我对标准git commit 的期望,但我不确定在git commit --only 的上下文中会发生什么)。

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

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

【问题讨论】:

标签: git githooks jetbrains-ide


【解决方案1】:

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


使用git commit --only有几个原因:

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

目前无法更改行为。

给定预提交钩子如下(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

【讨论】:

    【解决方案2】:

    确切的细节因 Git 的一个版本而异,而且有些人(我不是说 JetBrains 的人也在其中,因为我不知道)试图绕过 Git 做事的方式和过程,把事情搞砸了,要么无法解决,要么解决方法取决于 Git 版本。然而,这些 Git 钩子的主要思想都是一样的:

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

    这两个在你第一次运行git commit 时不需要同步,如果你用--only--includegit commit 命令添加文件,那么Git 必须创建一个new 索引,它可能不同于常规的普通索引。所以现在我们结束了一个环境变量GIT_INDEX_FILE,设置为一个新的临时索引的路径。1 由于所有 Git 命令都会自动尊重环境变量,因此 pre-commit 挂钩将使用临时索引的文件,git write-tree 将使用临时索引的文件。

    当然,未能遵守临时索引的任何内容(或者,可能取决于 --include--only,仅使用工作树的内容)都会出错回答。

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

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

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


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

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

    【讨论】:

      猜你喜欢
      • 2014-02-15
      • 2018-07-28
      • 2021-03-19
      • 1970-01-01
      • 1970-01-01
      • 2014-06-27
      • 1970-01-01
      • 2020-05-21
      • 1970-01-01
      相关资源
      最近更新 更多