(如何)我可以在预提交钩子中运行git checkout吗?

时间:2017-07-18 14:53:15

标签: git githooks git-checkout pre-commit-hook pre-commit

有一个文件应该在我们的git存储库中,以便它可以在任何结帐中。 用户可以 更改,但通常不应该重新检入更改。 --assume_unchanged和--skip_work_tree都没有提供所需的灵活性,而且文件太麻烦,不能用涂抹/清洁过滤器合理地“修改”。

所以我编写了一个预提交挂钩,成功地询问用户是否确定要将更改提交到此文件。如果他们说是,则检入文件(钩子返回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 status报告git checkout没有效果? (它是正确的 - 从钩子返回0导致文件被错误地提交)

2 个答案:

答案 0 :(得分:3)

当给定路径时,默认情况下checkout会更新索引中的工作树(即来自提交的更改)。

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

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

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

git checkout HEAD -- file/in/question

答案 1 :(得分:3)

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

  • F 的一个副本位于当前或HEAD提交中。此副本为只读。

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

  • F 的最终副本位于工作树中。此副本是可更改的,但与索引副本一样,我们必须小心查看谁将注意到任何更改以及何时更改。 (这里也值得一提,因为你评论了涂抹和干净的过滤器, F 的工作树副本是唯一一个带有污迹和行结束过滤的副本已应用;索引中的副本是干净的变体。)

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

  • 如果git commit作为git commit -agit commit <path1> <path2> ... <pathN>运行,Git当前正在使用临时索引,正常或&#34;真实&#34;指数放在一边。 (这可以回过头来讨论我们:当且仅当提交完成时,Git才会更新真实的索引。)

  • 您处于预提交挂钩状态,这意味着您已经 git commit运行了,并且是否允许提交继续进行是/否决定

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

  • 如果您在预提交挂钩中进行了 no 更改,则无需担心:您返回一个go / stop状态,并且Git继续从index(在HEAD指向新提交之后),或者不是。

  • 如果执行进行更改,您将在索引和/或工作树中进行更改。谁会看到这些变化以及什么时候?

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

  • 您可以使用git reset HEAD -- Fgit checkout HEAD -- F。这些将从当前提交复制到索引 - 如果是我们正在使用的那个,则为真实索引,如果我们使用临时索引,则为临时索引。 checkout表单也将从索引复制到工作树。

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

    在一些(非常)旧版本的Git中,Git没有注意到在预提交钩子中对索引所做的更改(因为,它从不重新读取索引)。我已经太长时间没有记住它具有什么效果,或者它影响的Git版本,但值得对此进行一些仔细的测试:我有点模糊的记忆是Git有承诺 - 在C代码中内置的树代码,并且不重新读取它使用原始索引内容而不是新内容构建树的索引,因此在预提交挂钩期间复制到索引中的文件实际上并没有进入提交。

在任何情况下,如果您更新索引中的文件,在工作树中更新它可能也是明智之举,但请​​考虑对经过精心设置不同<的人的影响/ em>文件的版本比工作树中的版本。在这种情况下,您将覆盖精心设计的版本工作树版本。

在一个不同但相关的极端情况下,我们应该注意当Git将索引条目从临时索引复制回真实索引时(在commit -acommit <path>情况下),这也是消除任何仔细不同的阶段文件。也就是说,如果你这样做:

git add -p somefile

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