git – 在团队中实施no-ff合并

所以在工作中我们正在实施一个新的,不错的
Git分支策略 – 太棒了!

为了保留我们的存储库的新(和漂亮)结构,我们希望所有的合并都使用–no-ff标志(和–no-commit标志来完成,以允许更好的合并提交消息).然而,似乎只是要求每个人都记住它,有点不可靠.有没有办法强制每个开发人员必须与上述标志合并?

据我所知,用钩子检查这个是不可能的(因为git不能可靠地存储有关快进的任何信息).我知道可以在每台开发人员机器上设置配置(通过运行git config –global merge.ff no).如果这是解决方案,我如何确保每个开发人员都具有此配置集?

最佳答案 我相信你可以在服务器上的预接收挂钩中检查这个,但是定义允许的内容很棘手,并且它可能会使那些做这些的人更加难以推动.而且,它只会给那些做错了事的人一个错误:由他们来修复它,这对某些用户来说可能有点复杂.

此外,您(和开发人员)可能不希望在某些合并上强制使用–no-ff:特别是,如果您在分支X上没有提交并且您获取新的origin / X提交,那么您可能希望快进X到原点/ X. (另一方面,你/他们总是可以使用git rebase来处理这种情况.)

也就是说,让我们看看我们是否可以定义正确的行为.

首先,我们可能会注意到“快进”实际上是标签移动的属性,而不是合并的属性.当标签的先前提交是其新提交的祖先时,标签以快进方式移动. (因此Git使用术语“快进合并”来指代实际上根本不是合并的东西.)git用于任何分支推送更新的默认测试是标签更新必须是快进操作,除非强制标志已设置.

我们不想拒绝标签快进,因为这是扩展分支的正常情况,有或没有合并:

...--o--o--n--n--n   <-- br1

在此图中,我们表示建议的更新(如预接收挂钩中所示),现有提交写为o,新写入为n.标签br1(更准确地说,refs / heads / br1)用于指向最尖端的o,现在将指向最尖端的n.

在您的预接收挂钩中,会发生的事实是存储库实际上已经使用新提交进行了更新(合并与否),并且git只是以< old-hash的形式向您发送每个引用更新请求, new-hash,name>元组.换句话说,给定上面的压缩br1更新图,我们可能使用大写和小写(我不能做颜色,唉)用大写字母表示传入的旧哈希值和新哈希值来重写它,给出:

...--o--O--n--n--N   <-- br1

如果你拒绝推送,git会收集垃圾收集新的提交,因为你告诉它不允许任何引用更新(包括分支名称),所以br1结束仍然指向提交O.

现在,合并通常在您的系统中很好,但您想要的是确保当br1获得稍后将在br2上的合并提交时,分支br2将不会移动以直接包含该合并提交.也就是说,这没关系:

...--o--O--n--n--N   <-- br1
...             /
...---o--O--n--N     <-- br2

甚至这是可以的(也许 – 我们可能会假设您将在稍后推送时获得br2更新,然后我们将检查该部分;我们还不能这样做因为我们还没有获得br2更新):

...--o--O--n--n--N   <-- br1
...             /
...         n--n
...        /
...----o--o          <-- br2 (you get no update since br2 did not move)

但这是被拒绝的:

...--o--O--n--n--N   <-- br1, br2
...             /
...---o--O--n--n

这是这样的:

...--o--O--n--n--N     <-- br1
...             / \
...---o--O--n--n   N   <-- br2

另一方面,这是可以的(尽管我们可能想要限制我们允许在br2上执行哪个父级;可能在这些图中,直接向左引出的行都是 – 第一个父链接):

...--o--O--n--n--N     <-- br1
...             / \
...---o--O--n--n---N   <-- br2

此外,合并后可以获得额外的非合并提交:

...--o--O--n--n--n--N   <-- br1
...             / \
...---o--O--n--n---N    <-- br2

(同样在br2上).但是,我们必须检查每个合并,因为这不行:

...--o--O--n--n--n--n--n---N   <-- br1
...             / \     \ /
...---o--O--n--n   n--n--N     <-- br2

(这里有人在br1上执行git merge br2,然后在br2获得快进时执行了git merge br1,然后在br2上进行了两次提交;他们还在br1上进行了两次提交;然后他们再次将br1合并到br2中,然后将br2合并为br1作为–no-ff merge;然后将br1和br2都推入一个git push中.

那么:我们应该执行什么规则呢?我认为,通过在遍历合并提交时强制执行有关–first-parent的规则,我们可以更轻松,更好.特别是,我们想要的是:

>给定分支更新(不是创建或删除)
>按图形顺序执行old-hash..new-hash的–first-parent遍历(git rev-list –topo-order)
>要求结果列表中最早的提交作为其第一个父项具有旧哈希.

有很多方法可以编写这个,并且尝试使用–bound是很诱人的,但这不能正常工作,因为合并显示的边界提交包括其所有父项,即使使用–first-parent也是如此.所以,让我们最简单的方法:

# Operation must be a fast-forward.  This ensures that
# $oldsha is an ancestor of (and thus related to) $newsha,
# and thus we are not discarding any commits.
if ! git merge-base --is-ancestor $oldsha $newsha; then
    ... reject as non-fast-forward
fi
edge=$(git rev-list --topo-order --first-parent $oldsha..$newsha | tail -1)
# If the rev-list is empty then $oldsha is not related to $newsha.
# However, we checked that first.  (The only other case where this
# can occur is if $oldsha equals $newsha, which is not an update,
# so we won't be running this code at all.)
#
# The listed edge commit may, however, be a root commit (have
# no parent).  We must reject this case as well as "parent is
# not $oldsha".  Fortunately that happens automatically since
# the empty string does not match a valid hash; we just need
# to be sure to quote "$parent".
parent=$(git rev-parse -q --verify $edge^)
if [ "$parent" = $oldsha ]; then
    ... update is OK
else
    ... reject as containing a bogus merge
fi

请注意,这也拒绝“foxtrot merges”,因为第一个父级rev-list不会返回到原始哈希.

(我没有测试过这个.)

点赞