5

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

为了保留我们存储库的新(和漂亮)结构,我们希望所有合并都使用--no-ff标志(以及--no-commit允许更好的合并提交消息的标志)完成。不过,好像只是大家记住,有点不靠谱。有没有办法强制每个开发人员都必须与上述标志合并?

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

4

3 回答 3

3

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

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

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

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

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

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

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

在你的 pre-receive 钩子中,发生的事情是存储库实际上已经用新的提交(合并或不合并)进行了更新,并且 git 只是以元组的形式将每个引用更新请求交给你。换句话说,给定上面的压缩更新图,我们可以用大写和小写(我不能做颜色,唉)用大写表示传入和值来重写它,给出:<old-hash, new-hash, name>br1old-hashnew-hash

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

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

现在,合并通常在您的系统中很好,但是您想要确保当br1获得稍后将 on 的合并提交时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级;大概在这些图中,直接通向左侧的线都是--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

(这里有人git merge br2在 onbr1时做了,然后git merge br1br2快进时做了,然后在 上做了两次提交br2;他们也在 上做了两次提交br1;然后他们再次合并br1br2然后合并br2br1一个--no-ff合并;然后将两者都推br1br2一个git push)。

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

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

有多种写法,尝试--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 合并”,因为第一个父项rev-list不会返回原始哈希。

(我没有测试过这些。)

于 2016-03-23T10:29:21.917 回答
0

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

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

于 2016-08-02T08:02:08.163 回答
0

这里有一个示例钩子代码来做到这一点:

#!/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" ]
于 2016-03-23T07:55:42.660 回答