(如何)我可以从预提交挂钩中 运行 git 签出?

(How) can I run git checkout from within the pre-commit hook?

有一个文件应该在我们的 git 存储库中,以便它在任何检出中。 它可能 由用户更改,但通常不应重新签入更改。 --assume_unchanged 和 --skip_work_tree 都没有提供所需的灵活性,而且文件太笨重,无法合理地 'modified' 使用 smudge/clean 过滤器。

所以我编写了一个预提交挂钩,成功询问用户是否确定要提交对此文件的更改。如果他们说是,则文件被签入(挂钩 returns 0,提交继续),否则,提交将中止。

我不想中止,而是让用户可以选择恢复对文件的更改并继续提交。

要将文件恢复到未更改的状态,我正在使用 git checkout -- file/in/question

鉴于文件已修改并准备提交,我运行以下预提交挂钩:

#!/bin/bash
echo "git checkout -- file/in/question"
git checkout -- file/in/question
echo "git status"
git status
exit 1 #would be 0 if the hook worked as expected

我得到以下输出:

git checkout -- file/in/question
git status
On branch blah
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   file/in/question

为什么 git 状态报告 git 结帐没有效果? (正确 - 从挂钩返回 0 会导致文件被错误提交)

当给定路径时,默认情况下 checkout 从索引更新工作树(即从为提交准备的更改)。

你想要的是更新索引(大概来自 HEAD,以便在这次提交时保持文件不变)。这可以用

来完成
git reset HEAD -- file/in/question

默认情况下,这会将工作树中的更改保留为未暂存的更改。这可能比同时还原索引和工作树更安全,但如果你真的想还原两者,你可以说

git checkout HEAD -- file/in/question

在任何时候,每个文件都有三个(!)副本(不包括添加或删除文件的特​​殊情况)。这也适用于您的特定文件。为了方便起见,我们称这个文件为 F.

  • F 的一个副本在当前或 HEAD 提交中。此副本是只读的。

  • F 的一个副本在索引中,可以提交。此副本是可以更改的,但我们必须仔细查看谁会注意到任何更改以及何时。

  • F 的最终副本在工作树中。这个副本是可以更改的,尽管与索引副本一样,我们必须仔细查看谁会注意到任何更改以及何时。 (这里也值得一提,因为你评论过污迹和干净的过滤器,F 的工作树副本是唯一应用了污迹和行尾过滤的副本; 索引中的副本是干净的变体。)

  • 如果 Git 将写入一个提交,它会使用当时的索引。

  • 如果 git commit 是 运行 作为 git commit -agit commit <path1> <path2> ... <pathN>,Git 当前正在使用 临时 索引,将正常或“真实”索引放在一边。 (这可以稍后回来咬我们:Git 将在且仅当提交完成时更新 real 索引。)

  • 您处于预提交挂钩中,这意味着您已经 运行 git commit 并且正在制作一个 yes/no 决定是否允许提交继续进行。

现在让我们把这些放在一起,观察一些问题。

  • 如果您在预提交挂钩中进行 no 更改,则无需担心:您 return a go/stop 状态,并且 Git 继续从索引进行新提交(之后 HEAD 指向新提交),否则它不会。

  • 如果您 进行更改,您将在索引and/or 工作树中进行更改。谁将在何时看到这些变化?

  • 您实际要求的更改是 git checkout -- <em>F</em>。这从索引复制到工作树。这对将要提交的内容没有影响。

  • 您可以改用 git 重置 HEAD -- <em>F</em> git 结帐 HEAD -- <em>F</em>。这些将从当前提交复制到索引——如果我们使用的是真实索引,或者如果我们使用临时索引,则为临时索引。 checkout 表单也将从索引复制到工作树。

  • 如果让提交继续完成, Git 正在使用临时索引,Git 将作为最后一步,将它添加到此临时索引(由于 -a<path> 参数)的任何条目复制回真实索引;但如果它使用的是真实索引,则不需要更新真实索引。

    在 Git 的某些(非常)旧版本中,Git 无法注意到预提交挂钩中对索引所做的更改(例如,它从不重新读取索引) .我已经太久无法准确记住这有什么影响,或者它影响了哪些 Git 版本,但是值得围绕这个做一些仔细的测试:我有点模糊的记忆是 Git 有提交-树代码内置在 C 代码中,通过不重新读取索引,它使用原始索引内容而不是新内容构建树,因此在预提交挂钩期间复制到索引中的文件实际上并没有进入提交。

在任何情况下,如果您更新索引中的文件,在工作树中也更新它可能是明智的,但请考虑对仔细安排不同[的人的影响=96=] 文件的版本比工作树中的版本。在这种情况下,您将覆盖精心准备的版本 工作树版本。


在一个不同但相关的极端情况下,我们应该注意何时 Git 确实将索引条目从临时索引复制回真实索引(在 commit -acommit <path> 情况下),这也会清除所有精心设计的不同阶段的文件。也就是说,如果您这样做:

git add -p somefile

并小心地暂存一个版本,然后 运行 git commit somefile 提交当前工作树版本 首先 ,你失去了精心暂存的版本。这可能(或可能不会)建议您如何处理这个问题。特别是,如果要对暂存的内容和工作树中的内容进行任何更改,则让预提交挂钩完全拒绝使用临时索引可能很有用,只是为了避免意外。