git - 理解 git commit --only 和 pre-commit 钩子(Hook)

标签 git githooks jetbrains-ide

我正在研究一个预提交 Hook 来重新格式化代码,一般来说,它是有效的;它重新格式化并 git add 任何暂存文件,并且生成的提交包含所需的重新格式化的代码。

但是,它不能很好地与 git commit --only(这是 JetBrains IDE 使用的变体)一起使用,我正在尝试理解原因。 git commit --only 和 pre-commit Hook 的组合会导致不希望的索引/工作树状态,如以下事件序列中所述:

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

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

我知道可以使用 clean 过滤器来重新格式化代码,而不是使用预提交 Hook ,但是这种方法会带来一些复杂的情况,这会很好尽可能避免。

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

最佳答案

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

  • 索引 包含了 commit-to-make,并且
  • 工作树 包含工作树。

当你第一次运行 git commit 时,这两个不需要同步,如果你将文件添加到 git commit 命令,使用 --only --include,Git 必须创建一个 索引,该索引可能不同于常规的普通索引。所以现在我们结束了一个环境变量,GIT_INDEX_FILE,设置为一个新的临时索引的路径。1 因为所有 Git 命令自动遵守环境变量,pre -commit hook 将使用临时索引的文件,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?我不知道在这里考虑什么是“正确”答案!我只能告诉你的是,当我之前尝试使用 Git 时,它倾向于在之后包含 workvers(在索引和工作树中)。也就是说,临时索引的版本变成了普通索引的版本,工作树文件没有被修改。

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


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

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

关于git - 理解 git commit --only 和 pre-commit 钩子(Hook),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41401296/

相关文章:

clion - 向 Jetbrain 的 CLion IDE 添加代码完成功能?

git - 为什么我不应该使用这个瓷器 Git 命令来判断我的存储库是否有任何更改?

git - 构建并运行一个 go 项目的分支

git - NPM install git repo 失败,因为它使用我的 mac 用户名而不是 'git'

git - 将 git 钩子(Hook)添加到谷歌云存储库

python - 如何通过 git pre-receive hook 验证用户身份

macos - ubuntu 12.04 virtual box vm 中用于我 mac 上共享文件夹的 git 行结束选项

git pull in hooks/post-receive 失败

tomcat - 在 IntelliJ 中,Live Edit 可以与 Tomcat 一起使用吗?

GitHub promise 行为相当奇怪