阻止将微不足道的合并推送到Git服务器

24

有一段时间以来,我已经要求我们的开发人员在推送之前使用rebase而不是merge。消除琐碎的合并可以使提交图更容易跟踪(即:gitk,git log)。

有时候人们仍然会意外进行琐碎的合并,然后推送。是否有方便或提示编写服务器端挂钩来阻止琐碎的合并?

通过“琐碎的合并”,我的意思是没有冲突的合并。 这里有一个例子, 以及 这里有一个更好的解释git中的琐碎合并

更新于Wed Nov 10 01:26:41 UTC 2010: 所有评论都很棒!谢谢你。

  • 请考虑以下事项:我真正要求的只是让大家做到这一点:
    • 如果git pull --ff-only失败,请使用git pull --rebase而不是git pull
  • git.git只有一个或两个committer,对吧?理论上,应该很容易跟踪提交图,但看起来对我来说还是有点凌乱。

2010年11月11日23:49:35更新::

2010年12月15日18:34:52更新::

  • adymitruk接近成功了!只有一个案例仍未解决:非琐碎合并必须仍然可行。
  • 还有一个相当完整的测试套件可用,请查看。
  • 我在git邮件列表上请求帮助。

2
有很多反对使用变基(rebase)而不是合并(merge)的论点。个人而言,我讨厌被强制进行变基。只是说一下 ;) - August Lilleaas
你无法区分一个普通的“pull”合并和一个没有冲突的主题/特性分支的合并。 - knittl
2
正如August所说,你应该三思而后行。你的合并是否必要的标准(冲突的存在)没有多大意义。你可以在合并或变基中解决合并冲突,并且可以使用合并或变基组合内容而不产生冲突。重要的是提交历史记录。如果你想做的是“合并此功能分支上完成的工作”,那么表示这一点的方法是合并。合并不一定会使历史记录难以跟踪-看看git.git。 - Cascabel
我在下面添加了一个适当的答案。没有处理章鱼合并。 - Adam Dymitruk
还有谁想测试下面的脚本吗?我刚刚修复了最后一个字数计数问题。 - Adam Dymitruk
显示剩余4条评论
2个回答

8
我在寻找解决方案时遇到了这段代码。它并不完全符合您的要求,但是在if语句中添加额外的分支名应该很容易。
目前来看,对我很有效。它会强制同一分支使用pull --rebase,并允许与其他分支进行常规合并。
所有的功劳归原作者所有。
#!/bin/bash
#
# This git update hook will refuse unnecessary merge commits caused by pulling
# from being pushed to a shared repository. These commits make following the
# history of a project difficult and are always avoidable with rebasing.
#
# by Scott Kyle (appden)
# modified by Olivier Refalo (orefalo)

refname="$1"
oldrev="$2"
newrev="$3"

# if this is not run as a hook, you may have messed up
if [ -z "$GIT_DIR" -o -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
    echo "Usage: GIT_DIR=<path> $0 <ref> <oldrev> <newrev>" >&2
    exit 1
fi

# if the new revision is all 0's then it's a commit to delete a ref
zero="0000000000000000000000000000000000000000"
# also check if the new revision is not a commit or is not a fast forward
# detect branch deletion, branch creation... and more
if [ "${refname#refs/heads/}" = "master" ] || [ "$newrev" = "$zero" ] || [ "$oldrev" = "$zero" ] || [ $(git cat-file -t $newrev) != "co
mmit" ] || [ $(git merge-base $oldrev $newrev) != "$oldrev" ]; then
    exit 0
fi

# loop through merges in the push only following first parents
for merge in $(git rev-list --first-parent --merges $oldrev..$newrev --); do
    # lazily create the revision list of this branch prior to the push
    [ -z "$revlist" ] && revlist=$(git rev-list $oldrev)
    # check if the second parent of the merge is already in this branch
    if grep -q $(git rev-parse $merge^2) <<< "$revlist"; then
        cat >&2 <<-EOF
            *** PUSH REJECTED ***
            *** TRIVIAL merge detected on local branch ${refname#refs/heads/}
            *** To fix: git rebase origin/${refname#refs/heads/}
            ***
            *** Next time use: git pull --rebase
            ***
            *** Permanent fix: git config [--global] branch.autosetuprebase always
            *** Then for existing branches: git config branch.<name>.rebase true
        EOF
        exit 1
    fi
done

echo -Info- Clean history successfully preserved!
exit 0

1
这也会阻止非平凡的合并。请参见 https://gist.github.com/737842 。我将此代码添加到该要点中,只需键入“make”即可运行测试。 - Adam Monsen
当部署在服务器上(更新钩子)时,它会强制用户执行“pull --rebase”。从另一个分支进行常规合并可以正常工作,但从同一分支进行微不足道的合并将失败。 - Olivier Refalo
啊,我明白了。抱歉,应该更仔细地阅读您的描述。当我一年前发布这个问题时,我想我正在寻找一些东西,可以允许从origin/master到master进行非平凡合并,这样人们仍然可以进行合并。在我的旧工作中(在Mifos上工作),我遇到了几个集成,其中重新定位很困难,但合并很容易。现在我对rebase更加熟悉,我可能会使用您提供的更新脚本。让我们结束这个问题吧。你赢了! - Adam Monsen

4

这个更新钩子将检查您是否推送到特定分支(它允许在wip、topic和其他分支中进行平凡合并)。

当推送时,它不会涉及章鱼合并的其余父级,因为它仅引用正在推送的每个合并提交中的第二个父级。欢迎随时更新脚本。

更新:保留分支需要在远程存在。

#!/bin/bash
refname="$1"
oldrev="$2"
newrev="$3"
branches="refs/heads/hotfixes refs/heads/dev refs/heads/qa refs/heads/master"
cont="no"
for branch in $branches ; do
  if [[ $refname == $branch ]] ; then
    cont="yes"
  fi
done
if [[ $cont == "no" ]] ; then
  exit 0
fi
echo "inspecting branch $refname for trivial merges" >&2
hashes="$(git log --format=%H --merges $oldrev..$newrev)"
for hash in $hashes ; do
  echo "checking merge commit $hash" >&2
  cont="no"
  for branch in $branches ; do
    if [[ $refname == $branch ]] ; then
      continue
    fi
    # if [[ "$(git log --format=%H $hash^2 ^$branch | wc -l)" == "0" ]] ; then
    if [[ "$(git log --format=%H $hash^2 ^$branch | wc -l)" == "    0" ]] ; then
      cont="yes"
    fi
  done
  if [[ $cont == "no" ]] ; then
    echo "No trivial merges allowed. Please rebase and push again." >&2
    exit 1
  fi
done
exit 0

抱歉,这是一个更新钩子。如果您正在使用gitolite,请确保使用update.secondary并使用安装程序将更改推送通过。我会更新答案以指定哪个钩子。 - Adam Dymitruk
哎呀,又出问题了。对于我“已经解决”的版本(或者我认为是这样),它也会阻止非平凡合并,即已经解决冲突的合并。哈哈,我想我终于达到了我想要深入了解git的极限。:) 再次感谢,我相信这对于那些有更多git经验/时间来hack的人来说是一个很好的开始!我会等待标记“接受答案”以完成更新钩子。 - Adam Monsen
git log命令的作用如下:--format=%H表示只显示提交的完整40个字符SHA-1,^$hash^2表示排除由$hash表示的提交的第二个父提交(前面的^表示从结果中排除这些提交,^2表示提交的第二个父提交),qa表示包括qa的提交。这是一种集合操作,明白了吗? - Adam Dymitruk
不行,但是我可以看到这个脚本仍然有问题,至少在我的系统上(Ubuntu 10.04 桌面版)。wc 的输出永远不会等于“0”。我从 cat /dev/null | wc 得到的是 0 0 0。一个测试用例真的可以帮助你证明它是否有效。 - Adam Monsen
好的,如果你想满足一个条件,你就提供测试吧 ;) 无论如何...我认为我已经找到了问题所在,是wc调用的问题。让我知道吧。 - Adam Dymitruk
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接