Git 预合并提交挂钩:如何在合并期间忽略文件?

Git pre-merge-commit hook : How do I ignore a file during a merge?

上下文

我在一个复杂的 git 流程中工作,其中一些特定的分支获得特定的子模块和一些需要提交但不能合并的特定配置文件。

这些文件很少,但让任何人在不注意合并分支的情况下合并分支太危险了。

为了使其自动化,我在服务器端和本地端都使用了预合并提交挂钩。

如果发生冲突,我会使用 .gitattributesgit/config 文件来解决与自定义合并驱动程序的冲突。它就像一个魅力。

问题

但是,我很难在没有冲突的情况下让它工作。在这种情况下,合并成功执行并触发了我的预合并挂钩。它发挥了它的魔力,然后成功退出。虽然,出于某种原因,git 在 hook 之后重新做一些合并的东西,这使它变得无用。这是我目睹的行为:

合并前

我有两个分支,比方说 A_currentB_incoming

两者都有一个我不想合并的文件。该文件名为 do_not_merge_me。在某些时候,do_not_merge_me 内容在 B_incoming 中发生了变化。假设它从 contentA 变为 contentB

合并期间

当我将 B_incoming 合并到 A_current 时,我看到的是:

Merge made by the 'recursive' strategy.
 do_not_merge_me               | 2 +-

问题

git 文档,可在此处 https://git-scm.com/docs/githooks#_pre_merge_commit 获取,说明在成功处理合并之后和验证提交之前触发合并前提交。

我的问题是:

  1. 为什么我在暂存区得到的是正确的版本?
  2. 有什么方法可以实现我想要做的事情吗?
  3. 为什么 git 在 hook 之后做一些合并的事情?这是一个错误吗?

简短的回答是你不能。

git merge 运行时,它将三个提交读入 Git 的 index。这三个提交是:

  • 合并基地(在插槽 1 中);
  • --ours 提交(在插槽 2 中);和
  • --theirs 提交(在插槽 3 中)。

这些以通常的索引格式存储:包括斜杠的路径名、mode(常规文件为 100644 或 100755、符号链接为 120000、gitlinks 为 160000)和哈希 ID。

合并的第一部分然后比较模式以确保它们合适(如果不合适,这是合并冲突)。假设这里是普通文件和合适的模式,它继续比较哈希 ID:

  • 三个都相等?文件已成功合并,放入槽 0,擦除槽 1-3
  • 两个相等?拿第三个:掉到插槽 0,擦除插槽 1-3
  • 三个都不相等?留到后面,真正的合并代码。

还有一些特殊情况(例如,文件存在于合并库和 theirs/ours 中,但在 ours/theirs 中被删除)我认为也在索引中直接处理,但是你的特殊情况——文件在 theirs 中修改,但在 ours 中相同,并且基础——命中中间的“两个相等?取第三个”案例:文件在你的提交和合并基础中是相同的,所以 Git 只是 假设 他们更新的文件是正确的结果。

当 Git 在早期阶段执行此操作时 ,它根本不会运行您的合并驱动程序。文件进入暂存槽零——“准备提交”——而不是冲突,你永远没有机会做任何事情。您的 pre-merge-commit 将被调用,但索引中的文件副本将是 theirs 提交的副本。

我们现在进入非常黑魔法的部分:“索引”假定始终使用一个索引 (.git/index)。事实并非如此:大部分是正确的,但是:

  • $GIT_INDEX_FILE覆盖名称;
  • 添加的工作树(来自git worktree add)有自己的索引;和
  • 各种 Git 命令将索引读入内存,然后使用它。

在这种情况下,git merge 似乎在内存中有索引,只是按原样使用它来进行新的提交。您的 git add 替换了 .git/index 文件中的零阶段副本,但是 git merge 没有注意到这一点,并继续使用之前的传入副本生成新的合并提交甚至 运行 你的预合并提交钩子。

假设这一切都是真的——它可能会从一个 Git 版本更改为另一个版本,具体取决于 Git 何时以及是否重新读取索引——这将回答你的问题#1,将#2 的答案设为“否”,将#3 的答案设为“您正在尝试在 Git 处理的 运行ge 之外做一些事情”。

你想做的事情本身并不是不合理,只是Git不支持

对于需要在合并期间应用更改的任何人,这是我提供的解决方案。

keep in mind this solution can possibly create some issues in some corner cases as pointed by @torek in .

Most of the time, you want to avoid doing modifications at merge. Prefer verification.

这些步骤适用于我的 git (2.31.1) 版本。我不知道这种行为是否跨版本一致。

  1. 使用 .gitattributes 为您需要修改的文件实施自定义合并策略。它必须应用这些修改。这与步骤 3 的作用相同,但如果目标文件发生冲突

  2. 实施预合并提交挂钩。这将在冲突解决后触发。这意味着您将可以访问反映合并结果的临时区域。

  3. 修改暂存区域:使用预合并提交挂钩,您可以修改暂存区域,这实际上不会修改合并结果(存储在其他地方)。相反,当您的脚本成功退出时,您的修改将进入暂存区域。这是我第一次看到合并后暂存区中还有东西。

Note : The reason why it does that is git seems to initiate a second merge

  1. 最后,您需要实现一个 post-merge 挂钩,以使用实际暂存区域内容修改合并提交。您需要先删除文件 .git/MERGE_HEAD

我自己也遇到过这个需求,我用git别名解决了

我在this repository上发布了它,所以你也可以使用它。

非常欢迎您将自己的想法添加到其中以供将来更新和贡献。