如何在 运行 服务器预接收挂钩时有效地找到分支的最新共享提交?

How to efficiently find the newest shared commit of a branch while running server pre-receive hook?

当服务器预接收挂钩脚本正在执行时,假设它接收到一个名为baz 的新分支。分支 foo、qqaz 和 bar 是现有分支。如何高效地找到baz的最新共享提交(下例中是commit 3,不是commit 2和1)?

---0---1                   foo
        \
         \   8---9---10    qqaz
          \ /
           2---3---4       bar
                \
                 5
                  \
                   6---7   baz

在任何情况下都正确地做到这一点是从“非常棘手”到“不可能”的任何地方,因为 git push 可以接收请求更改,添加,and/or 多个分支名称一次性完成.也就是说,仅仅因为您的接收 Git 具有名为 fooqqazbar 的分支,分别标识提交 1、10 和 4 now 并不意味着推送完成时会出现这种情况:它可能包含一个请求,将 foo 设置为提交 0,添加一个新的提交 11 并设置 qqaz 指向那个提交,在 4 之后添加几个新提交并更新 bar,依此类推。

但是,如果您将 git push 限制为单个分支名称更新——如果您控制各种挂钩,您 可以 这样做——问题变得相当严重更容易处理。诀窍是识别预接收挂钩本身运行 在对任何分支名称进行任何更改之前 。如果 baz 是一个 新分支名称 ,那么在存储库的所有分支名称中还没有名称,用于提交 7。(可能已经有一个标签任何这些提交的名称,或标签名称的创建或在要通过此推送更新的引用列表中更新:请务必在设计算法时考虑这些。)

如果我们利用它,那么:

  • push请求中有一行000...000 <hash-of-7> refs/heads/baz形式,表示如果接受此推送,将添加分支名称baz
  • a git rev-list <hash-of-7> --not --branches --topo-order 将按顺序列出提交 7、6 和 5;
  • 5 的父级是 3。

如果在新的提交序列中有任何合并提交,事情会变得更复杂:

---0---1                   foo
        \
         \   8---9---10    qqaz
          \ /
           2---3---4       bar
            \   \
             ----5
                  \
                   6---7   baz

这里的提交 5 是一个合并,合并了提交 2 和 3。我不确定在这种情况下你想找到哪个提交。

一般来说,当您希望检查提交图时,git rev-list 是执行此操作的 Git 工具。 --branches 选项告诉它使用所有分支名称; --tags 告诉它使用所有标签名称; --all 告诉它使用所有引用。选项中的 --not 会导致后续引用成为“负面引用”,就好像您列出了 ^refs/heads/dev 以排除分支 dev.1 您也可以列出要包含或排除的特定模式。有关详细信息,请参阅 the git rev-list documentation


1后面的 --not 取消前面的,所以:

git rev-list HEAD --not br1 br2 br3 --not br4

与以下意思相同:

git rev-list HEAD ^br1 ^br2 ^br3 br4

一般来说,正负引用的顺序无关紧要。

有点复杂,但应该给出正确的结果:

git rev-list baz | grep -Ff <(git rev-list foo qqax bar) | head -1

如果您的 shell 不理解 <(...) 运算符,您可以使用临时文件:

git rev-list foo qqax bar > /tmp/commit-list.txt
git rev-list baz | grep -Ff /tmp/commit-list.txt | head -1

另一种方法可以是连续获取 bazmerge-base 和目标分支,并保留这些提交中的最新提交。