如何防止 'master' 分支中的狐步舞合并?
How can I prevent foxtrot merges in my 'master' branch?
狐步舞合并是 'origin/master' 作为第二个(或更高版本的)父级合并的合并,如下所示:
提交 'D' 是一个狐步舞合并,因为 'origin/master' 是它的第二个父级。请注意 'origin/master' 中的第一父历史记录此时如何包含提交 'B'。
但是在我的 git 存储库中,我需要涉及 'origin/master' 的所有合并,以保持 'origin/master' 作为第一个父级。不幸的是 git 在评估提交是否符合快进条件时并不关心父顺序。这导致我的 master 分支上的第一个父历史有时会丢失曾经存在的提交(例如 "git log --first-parent" 的输出)。
这是推送前面图表中的提交 'D' 时发生的情况:
如何阻止此推送? 'origin/master' 的第一父历史在推送 foxtrot 合并后不再包含提交 'B'!
显然没有任何提交或工作实际上丢失,只是在我的环境中我真的需要 "git log --first-parent" 来作为提交的稳定累积记录 - 如果你愿意,一种 "Write-Once Read-Many"(蠕虫)数据库。我的脚本和流程使用 "git log --first-parent" 来生成变更日志和发行说明,以及在我的票务系统 (JIRA) 中管理票证转换。 Foxtrot 合并破坏了我的脚本!
是否可以在我的 git 存储库中安装某种预接收挂钩以防止推送 foxtrot 合并?
p.s。这个 Whosebug 问题中的提交图是使用 http://bit-booster.com/graph.html.
生成的
以下预接收挂钩将阻止那些:
#/bin/bash
# Copyright (c) 2016 G. Sylvie Davies. http://bit-booster.com/
# Copyright (c) 2016 torek. http://whosebug.com/users/1256452/torek
# License: MIT license. https://opensource.org/licenses/MIT
while read oldrev newrev refname
do
if [ "$refname" = "refs/heads/master" ]; then
MATCH=`git log --first-parent --pretty='%H %P' $oldrev..$newrev |
grep $oldrev |
awk '{ print $2 }'`
if [ "$oldrev" = "$MATCH" ]; then
exit 0
else
echo "*** PUSH REJECTED! FOXTROT MERGE BLOCKED!!! ***"
exit 1
fi
fi
done
如果您使用的是 Github / Gitlab / Bitbucket Cloud,您可能需要考虑创建某种对他们 commit status apis (here's api docs for: bitbucket, github 的调用;不确定 gitlab 是否有),因为您无权访问预接收挂钩,即使您访问了,您仍然需要处理直接在网络中单击 "merge" 按钮的人 ui 的那些产品(在这种情况下没有 "push")。
使用 Bitbucket Server,您可以安装 add-on I created。
安装后,在给定存储库的 "hook" 设置中的 "Protect First Parent Hook" 上单击 "enable":
它将通过推送和 Bitbucket 服务器 UI 中的 "merge" 按钮阻止狐步舞合并。即使其许可证已过期,它也会这样做,从而使 "Protect First-Parent Hook" 成为更大附加组件的免费组件。
这是我的 Bit-Booster "Protect First Parent" 预接收挂钩的一个例子:
$ git pull
$ git push
remote: *** PUSH REJECTED BY Protect-First-Parent HOOK ***
remote:
remote: Merge [1f70043b34d3] is not allowed. *Current* master must appear
remote: in the 'first-parent' position of the subsequent commit. To see how
remote: master is merging into the wrong side (not as 1st parent), try this:
remote:
remote: git show --graph -s --pretty='%h %d%n' \
remote: 1f70043b34d3 1f70043b34d3~1 origin/master
remote:
remote: To fix, there are two traditional solutions:
remote:
remote: 1. (Preferred) rebase your branch:
remote:
remote: git rebase origin/master
remote: git push origin master
remote:
remote: 2. Redo the merge in the correct direction:
remote:
remote: git checkout master
remote: git reset --hard origin/master
remote: git merge --no-ff 1f70043b34d3eaedb750~1
remote: git push origin master
remote:
有关狐步舞合并的更多背景信息 I wrote a blog post。
这是一个钩子代码,可以满足您的要求:
pre-receive hook
#!/bin/sh
# Check to see if this is the first commit in the repository or not
if git rev-parse --verify HEAD >/dev/null 2>&1
then
# We compare our changes against the previous commit
against=HEAD^
else
# Initial commit: diff against an empty tree object
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
# Redirect output to screen.
exec 1>&2
# Check to see if we have updated the master branch
if [ "$refname" eq "refs/heads/master" ];
then
# Output colors
red='3[0;31m';
green='3[0;32m';
yellow='3[0;33m';
default='3[0;m';
# personal touch :-)
echo "${red}"
echo " "
echo " |ZZzzz "
echo " | "
echo " | "
echo " |ZZzzz /^\ |ZZzzz "
echo " | |~~~| | "
echo " | |- -| / \ "
echo " /^\ |[]+ | |^^^| "
echo " |^^^^^^^| | +[]| | | "
echo " | +[]|/\/\/\/\^/\/\/\/\/|^^^^^^^| "
echo " |+[]+ |~~~~~~~~~~~~~~~~~~| +[]| "
echo " | | [] /^\ [] |+[]+ | "
echo " | +[]+| [] || || [] | +[]+| "
echo " |[]+ | || || |[]+ | "
echo " |_______|------------------|_______| "
echo " "
echo " "
echo " ${green}You have just committed code ${red} "
echo " Your code ${yellow}is bad.!!! "
echo " ${red} Do not ever commit again "
echo " "
echo "${default}"
fi;
# set the exit code to 0 or 1 based upon your needs
# 0 = good to push
# 1 = exit without pushing.
exit 0;
我写这个是为了提早提供反馈(pre-receive
钩子也是需要的)。我为此使用 post-merge
和 pre-push
钩子。无法阻止 commit-msg 钩子中的狐步舞合并,因为检测一个的信息仅在之后可用。在post-merge
钩子中,我只是警告。在 pre-push
钩子中,我抛出并阻止推动。参见 d3f1821("foxtrot: Add sub-hooks to detect foxtrot merges",2017-08-05)。
~/.git-hooks/helpers/foxtrot-merge-detector:
#!/bin/sh
#usage:
# foxtrot-merge-detector [<branch>]
#
# If foxtrot merge detected for branch (current branch if no branch),
# exit with 1.
# foxtrot merges:
# See http://bit-booster.blogspot.cz/2016/02/no-foxtrots-allowed.html
#
remoteBranch=$(git rev-parse --abbrev-ref ""@{u} 2>/dev/null)
# no remote tracking branch, exit
if [[ -z "$remoteBranch" ]]; then
exit 0
fi
branch=$(git rev-parse --abbrev-ref "${1-@}" 2>/dev/null)
# branch commit does not cover remote branch commit, exit
if ! $(git merge-base --is-ancestor $remoteBranch $branch); then
exit 0
fi
remoteBranchCommit=$(git rev-parse $remoteBranch)
# branch commit is same as remote branch commit, exit
if [[ $(git rev-parse $branch) == $remoteBranchCommit ]]; then
exit 0
fi
# remote branch commit is first-parent of branch, exit
if [[ $(git log --first-parent --pretty='%P' $remoteBranchCommit..$branch | \
cut -d' ' -f1 | \
grep $remoteBranchCommit | wc -l) -eq 1 ]]; then
exit 0
fi
# foxtrot merge detected if here
exit 1
然后用作
预推钩:
#!/bin/sh
remote=""
url=""
z40=0000000000000000000000000000000000000000
while read local_ref local_sha remote_ref remote_sha
do
if [ "$local_sha" = $z40 ]; then
# handle delete, do nothing
:
else
# ex $local_ref as "refs/heads/dev"
branch=$(git rev-parse --abbrev-ref "$local_ref")
~/.git-hooks/helpers/foxtrot-merge-detector "$branch"
# check exit code and exit if needed
exitcode=$?
if [ $exitcode -ne 0 ]; then
echo 1>&2 "fatal: foxtrot merge detected, aborting push"
echo 1>&2 "fatal: branch $branch"
exit $exitcode
fi
fi
done
post-合并:
#!/bin/sh
~/.git-hooks/helpers/foxtrot-merge-detector
# check exit code and exit if needed
exitcode=$?
if [ $exitcode -ne 0 ]; then
echo -e " ${Yellow}WARNING:${None} foxtrot merge detected"
# swallow exit code
fi
狐步舞合并是 'origin/master' 作为第二个(或更高版本的)父级合并的合并,如下所示:
提交 'D' 是一个狐步舞合并,因为 'origin/master' 是它的第二个父级。请注意 'origin/master' 中的第一父历史记录此时如何包含提交 'B'。
但是在我的 git 存储库中,我需要涉及 'origin/master' 的所有合并,以保持 'origin/master' 作为第一个父级。不幸的是 git 在评估提交是否符合快进条件时并不关心父顺序。这导致我的 master 分支上的第一个父历史有时会丢失曾经存在的提交(例如 "git log --first-parent" 的输出)。
这是推送前面图表中的提交 'D' 时发生的情况:
如何阻止此推送? 'origin/master' 的第一父历史在推送 foxtrot 合并后不再包含提交 'B'!
显然没有任何提交或工作实际上丢失,只是在我的环境中我真的需要 "git log --first-parent" 来作为提交的稳定累积记录 - 如果你愿意,一种 "Write-Once Read-Many"(蠕虫)数据库。我的脚本和流程使用 "git log --first-parent" 来生成变更日志和发行说明,以及在我的票务系统 (JIRA) 中管理票证转换。 Foxtrot 合并破坏了我的脚本!
是否可以在我的 git 存储库中安装某种预接收挂钩以防止推送 foxtrot 合并?
p.s。这个 Whosebug 问题中的提交图是使用 http://bit-booster.com/graph.html.
生成的以下预接收挂钩将阻止那些:
#/bin/bash
# Copyright (c) 2016 G. Sylvie Davies. http://bit-booster.com/
# Copyright (c) 2016 torek. http://whosebug.com/users/1256452/torek
# License: MIT license. https://opensource.org/licenses/MIT
while read oldrev newrev refname
do
if [ "$refname" = "refs/heads/master" ]; then
MATCH=`git log --first-parent --pretty='%H %P' $oldrev..$newrev |
grep $oldrev |
awk '{ print $2 }'`
if [ "$oldrev" = "$MATCH" ]; then
exit 0
else
echo "*** PUSH REJECTED! FOXTROT MERGE BLOCKED!!! ***"
exit 1
fi
fi
done
如果您使用的是 Github / Gitlab / Bitbucket Cloud,您可能需要考虑创建某种对他们 commit status apis (here's api docs for: bitbucket, github 的调用;不确定 gitlab 是否有),因为您无权访问预接收挂钩,即使您访问了,您仍然需要处理直接在网络中单击 "merge" 按钮的人 ui 的那些产品(在这种情况下没有 "push")。
使用 Bitbucket Server,您可以安装 add-on I created。
安装后,在给定存储库的 "hook" 设置中的 "Protect First Parent Hook" 上单击 "enable":
它将通过推送和 Bitbucket 服务器 UI 中的 "merge" 按钮阻止狐步舞合并。即使其许可证已过期,它也会这样做,从而使 "Protect First-Parent Hook" 成为更大附加组件的免费组件。
这是我的 Bit-Booster "Protect First Parent" 预接收挂钩的一个例子:
$ git pull
$ git push
remote: *** PUSH REJECTED BY Protect-First-Parent HOOK ***
remote:
remote: Merge [1f70043b34d3] is not allowed. *Current* master must appear
remote: in the 'first-parent' position of the subsequent commit. To see how
remote: master is merging into the wrong side (not as 1st parent), try this:
remote:
remote: git show --graph -s --pretty='%h %d%n' \
remote: 1f70043b34d3 1f70043b34d3~1 origin/master
remote:
remote: To fix, there are two traditional solutions:
remote:
remote: 1. (Preferred) rebase your branch:
remote:
remote: git rebase origin/master
remote: git push origin master
remote:
remote: 2. Redo the merge in the correct direction:
remote:
remote: git checkout master
remote: git reset --hard origin/master
remote: git merge --no-ff 1f70043b34d3eaedb750~1
remote: git push origin master
remote:
有关狐步舞合并的更多背景信息 I wrote a blog post。
这是一个钩子代码,可以满足您的要求:
pre-receive hook
#!/bin/sh
# Check to see if this is the first commit in the repository or not
if git rev-parse --verify HEAD >/dev/null 2>&1
then
# We compare our changes against the previous commit
against=HEAD^
else
# Initial commit: diff against an empty tree object
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
# Redirect output to screen.
exec 1>&2
# Check to see if we have updated the master branch
if [ "$refname" eq "refs/heads/master" ];
then
# Output colors
red='3[0;31m';
green='3[0;32m';
yellow='3[0;33m';
default='3[0;m';
# personal touch :-)
echo "${red}"
echo " "
echo " |ZZzzz "
echo " | "
echo " | "
echo " |ZZzzz /^\ |ZZzzz "
echo " | |~~~| | "
echo " | |- -| / \ "
echo " /^\ |[]+ | |^^^| "
echo " |^^^^^^^| | +[]| | | "
echo " | +[]|/\/\/\/\^/\/\/\/\/|^^^^^^^| "
echo " |+[]+ |~~~~~~~~~~~~~~~~~~| +[]| "
echo " | | [] /^\ [] |+[]+ | "
echo " | +[]+| [] || || [] | +[]+| "
echo " |[]+ | || || |[]+ | "
echo " |_______|------------------|_______| "
echo " "
echo " "
echo " ${green}You have just committed code ${red} "
echo " Your code ${yellow}is bad.!!! "
echo " ${red} Do not ever commit again "
echo " "
echo "${default}"
fi;
# set the exit code to 0 or 1 based upon your needs
# 0 = good to push
# 1 = exit without pushing.
exit 0;
我写这个是为了提早提供反馈(pre-receive
钩子也是需要的)。我为此使用 post-merge
和 pre-push
钩子。无法阻止 commit-msg 钩子中的狐步舞合并,因为检测一个的信息仅在之后可用。在post-merge
钩子中,我只是警告。在 pre-push
钩子中,我抛出并阻止推动。参见 d3f1821("foxtrot: Add sub-hooks to detect foxtrot merges",2017-08-05)。
~/.git-hooks/helpers/foxtrot-merge-detector:
#!/bin/sh
#usage:
# foxtrot-merge-detector [<branch>]
#
# If foxtrot merge detected for branch (current branch if no branch),
# exit with 1.
# foxtrot merges:
# See http://bit-booster.blogspot.cz/2016/02/no-foxtrots-allowed.html
#
remoteBranch=$(git rev-parse --abbrev-ref ""@{u} 2>/dev/null)
# no remote tracking branch, exit
if [[ -z "$remoteBranch" ]]; then
exit 0
fi
branch=$(git rev-parse --abbrev-ref "${1-@}" 2>/dev/null)
# branch commit does not cover remote branch commit, exit
if ! $(git merge-base --is-ancestor $remoteBranch $branch); then
exit 0
fi
remoteBranchCommit=$(git rev-parse $remoteBranch)
# branch commit is same as remote branch commit, exit
if [[ $(git rev-parse $branch) == $remoteBranchCommit ]]; then
exit 0
fi
# remote branch commit is first-parent of branch, exit
if [[ $(git log --first-parent --pretty='%P' $remoteBranchCommit..$branch | \
cut -d' ' -f1 | \
grep $remoteBranchCommit | wc -l) -eq 1 ]]; then
exit 0
fi
# foxtrot merge detected if here
exit 1
然后用作
预推钩:
#!/bin/sh
remote=""
url=""
z40=0000000000000000000000000000000000000000
while read local_ref local_sha remote_ref remote_sha
do
if [ "$local_sha" = $z40 ]; then
# handle delete, do nothing
:
else
# ex $local_ref as "refs/heads/dev"
branch=$(git rev-parse --abbrev-ref "$local_ref")
~/.git-hooks/helpers/foxtrot-merge-detector "$branch"
# check exit code and exit if needed
exitcode=$?
if [ $exitcode -ne 0 ]; then
echo 1>&2 "fatal: foxtrot merge detected, aborting push"
echo 1>&2 "fatal: branch $branch"
exit $exitcode
fi
fi
done
post-合并:
#!/bin/sh
~/.git-hooks/helpers/foxtrot-merge-detector
# check exit code and exit if needed
exitcode=$?
if [ $exitcode -ne 0 ]; then
echo -e " ${Yellow}WARNING:${None} foxtrot merge detected"
# swallow exit code
fi