【问题标题】:Enforce no-ff merge across a team在团队中强制执行 no-ff 合并
【发布时间】:2016-07-10 09:42:50
【问题描述】:

所以在工作中,我们正在实施一个新的、很好的 Git 分支策略 - 太棒了!

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

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

【问题讨论】:

    标签: git merge fast-forward


    【解决方案1】:

    我相信您可以在服务器上的 pre-receive 挂钩中检查这一点,但要定义允许的内容是很棘手的,并且它可能会使执行这些操作的人更加难以推送。而且,它只会给那些做错事的人一个错误:由他们来修复它,这对某些用户来说可能有点复杂。

    此外,您(和开发人员)可能不想在某些合并上强制 --no-ff:特别是,如果您在分支 X 上没有提交并且您获取新的 origin/X 提交,您可能想要快速-将您的X 转发至origin/X。 (另一方面,在这种情况下,您/他们始终可以使用 git rebase。)

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

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

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

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

    在此图中,我们表示一个提议的更新(如在 pre-receive 钩子中所见),现有提交写为o,新提交写为n。标签br1(更准确地说是refs/heads/br1)曾经指向最尖端的o,现在将指向最尖端的n

    在您的 pre-receive 钩子中,实际发生的情况是存储库实际上已经更新新的提交(合并与否),并且 git 只是将每个引用更新请求交给您,以&lt;<em>old-hash, new-hash, name</em>&gt; 元组的形式。换句话说,给定上面压缩的br1 更新图,我们可以用大写和小写(我不能做颜色,唉)用大写表示传入的old-hashnew-hash 值,给出:

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

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

    现在,合并在您的系统中通常是可以的,但您需要确保当br1 获得稍后将在br2 上的合并提交时,分支br2 不会 /em> 移动以直接包含该合并提交。也就是说,这样就OK了:

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

    甚至 this 都可以(也许——我们可能会假设您会在稍后的推送中收到 br2 更新,我们将检查 that 部分em>那么;我们还不能这样做,因为我们还没有得到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 上允许它通过;大概在这些图中,直线通向左侧都是--first-parent链接):

    ...--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 合并;然后将br1br2 合并为一个git push)。

    那么:确切地说,我们应该执行什么规则?我认为,我们可以通过在遍历合并提交时强制执行有关 --first-parent 的规则来使这更容易。特别是,我们想要的是:

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

    有多种写法,尝试使用--boundary 很诱人,但这不起作用,因为为合并显示的边界提交包括其所有父项,即使使用--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 不会返回原始哈希。

    (我没有测试过这些。)

    【讨论】:

    • 首先:很抱歉回复晚了;我赶上了复活节:-) 其次:乍一看,您的回答确实很有意义。我没有想到 --first-parent 方法。但是,我看不出我们如何区分普通推送到br1,以及从本地分支快进的推送。我在几个示例上尝试了您的代码,但无法使其正常工作。 (据我了解,它应该是一个更新挂钩,但为了更好的措施,我也尝试将它作为预接收挂钩来实现,但无济于事)。我只是没有正确理解它吗?
    • 我在考虑预接收(它允许您对所有要更新的标签实施更多的全局测试),但上面的片段也可以在更新挂钩中工作。但是,对于真实场景的实际测试,您需要提供脚本来创建这些场景:git init、在分支上进行提交、进行各种合并以及尝试推送。该脚本还需要创建和设置远程存储库。 (带有跟踪/日志记录代码的示例钩子也会有所帮助)。
    【解决方案2】:

    由于几乎任何选项都会让团队成员在他们的 git 设置中执行额外的步骤,所以最好让每个人将他们的全局配置默认设置为 --no-ff 吗?

    唯一使用 ff 的将是那些明确声明它的人,而不是那些忘记使用 --no-ff 的人。

    【讨论】:

    • 我更愿意以某种方式强制执行该策略 - 它可能涉及设置本地配置,但我想要一种更...可控的方式来做这件事,而不仅仅是很好地询问人们。您知道将配置“推送”给开发人员的方法吗?
    【解决方案3】:

    这是一个示例钩子代码:

    #!/bin/sh
    
    # for details see here, 
    # http://git-scm.com/book/en/Customizing-Git-An-Example-Git-Enforced-Policy
    # it seems that git on Windows doesn't support ruby, so use bash instead
    # to function, put it into remote hook dir
    # to disable, rename or delete file in remote hook dir
    
    refname=$1
    oldrev=$2
    newrev=$3
    
    
    # enforces fast-forward only pushes
    check_fast_forward ()
    {
      all_refs=`git rev-list ${oldrev}..${newrev} | wc  -l`
      single_parent_refs=`git rev-list ${oldrev}..${newrev} --max-parents=1 | wc  -l `
      if [ $all_refs -eq $single_parent_refs ]; then
        echo "This is the section for fast-forward commits ..."
        exit 0
      fi
    }
    
    check_fast_forward
    

    根据需要设置:

    -eq 等于

    if [ "$a" -eq "$b" ]
    

     

    -ne 不等于

    if [ "$a" -ne "$b" ]
    

    【讨论】:

    • 那个钩子 enforces 快进推动 - 我想 防止 快进(但仅在合并时)
    • 现在就是你想要的。根据您的需要更改比较标志
    • 这里我可能错了,但我不认为这是一个对称问题。如果错了,请纠正我:在新测试中,我们检查是否所有 refs 共享一个单亲。换句话说,如果我对单个分支进行更改(即不涉及合并),钩子将拒绝推送。由于显而易见的原因,这是不可取的。
    • 好的,我已经测试了你代码的不同配置。我可以让它拒绝所有合并(不管--no-ff 标志),接受我能想到的所有东西,或者拒绝所有快进(包括不合并的常规推送)。我只是在这里愚蠢吗?感谢您的帮助:-)
    猜你喜欢
    • 2014-04-25
    • 1970-01-01
    • 2013-05-25
    • 2011-07-31
    • 1970-01-01
    • 1970-01-01
    • 2017-03-13
    • 2013-08-10
    • 1970-01-01
    相关资源
    最近更新 更多