【问题标题】:(How) can I run git checkout from within the pre-commit hook?(如何)我可以从 pre-commit 钩子中运行 git checkout 吗?
【发布时间】:2017-07-18 14:53:15
【问题描述】:

有一个文件应该在我们的 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 会导致文件被错误提交)

【问题讨论】:

  • 你为什么不用git reset HEAD file/in/question
  • @Leon 这也没有按预期运行。我希望它能够删除所有更改,以便文件不会显示为已更改。相反,它仍然显示为已更改,但未上演。一种改进,但不完全是一个解决方案。
  • 第一个选择 revert 选项的开发人员,期望它保持提交干净,同时让他们保持本地版本,可能会不同意这种评估,@M_M
  • @Mark:我对文件的“删减”而不是仅仅取消暂存它不太满意,但是可以根据需要重新创建文件,只需付出少量努力,所以这是“同意”的解决方案。 :-p
  • 好吧,我的回答解决了如何做到这两点。而且我想由于这是一个本地钩子,任何安装它的开发人员只能在它出现问题时责怪自己。但我预测你迟早会寻找“unstage”语法

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


【解决方案1】:

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

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

git reset HEAD -- file/in/question

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

git checkout HEAD -- file/in/question

【讨论】:

  • 我这里的误解是结帐会影响索引,而不是使用它作为源。这就是我想要的信息。简洁并解决了问题。谢谢你。 (感谢@torek 的冗长解释)
【解决方案2】:

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

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

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

  • F 的最终副本在工作树中。该副本是可变的,尽管与索引副本一样,我们必须小心查看谁会在何时注意到任何更改。 (这里还值得一提,因为您提到了涂抹和清洁过滤器,F 的工作树副本是唯一应用了涂抹和行尾过滤的副本; index 是干净的变体。)

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

  • 如果git commitgit commit -agit commit &lt;path1&gt; &lt;path2&gt; ... &lt;pathN&gt; 运行,Git 当前正在使用 临时 索引,而将普通或“真实”索引放在一边。 (这可能会在稍后再次咬我们:当且仅当提交完成时,Git 才会更新 real 索引。)

  • 您处于预提交钩子中,这意味着您已被 git commit 运行,并且正在就是否允许提交继续进行是/否决定。

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

  • 如果您在 pre-commit 钩子中进行 no 更改,则无需担心:您返回 go/stop 状态,Git 继续从索引进行新的提交(之后HEAD 指向新的提交),或者它没有。

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

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

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

  • 如果您让提交继续完成,并且 Git 正在使用临时索引,作为最后一步,Git 将复制它添加到该临时索引的所有条目(由于 -a&lt;path&gt; 参数)返回真实索引;但如果是使用真实索引,则不需要更新真实索引。

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

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


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

git add -p somefile

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-11-26
    • 1970-01-01
    • 2014-02-15
    • 2017-05-15
    • 1970-01-01
    • 2021-03-19
    • 1970-01-01
    • 2014-06-27
    相关资源
    最近更新 更多