GIT: Как я могу предотвратить слияние фокстрота в моей "основной" ветке?
Слияние фокстрот - это слияние, при котором "origin / master" сливается как 2-й (или более поздний) родитель, например:
Commit 'D' - это слияние фокстрот, потому что origin / master является его вторым родителем. Обратите внимание, что история первого родителя из 'origin / master' содержит коммит 'B' в данный момент.
Но в моем git-репо мне нужны все слияния с участием origin / master, чтобы сохранить origin / master в качестве первого родителя. К сожалению, git не заботится о порядке родителей, когда он оценивает, подходит ли коммит для ускоренной перемотки вперед. Это приводит к тому, что первая родительская история в моей главной ветке иногда теряет коммиты, которые были там (например, вывод "git log --first-parent").
Вот что происходит, когда выдвигается коммит 'D' из предыдущей диаграммы:
Как я могу предотвратить этот толчок? История первого родителя 'origin / master' больше не содержит коммита 'B' после того, как происходит слияние фокстрот!
Очевидно, что никакие коммиты или работа на самом деле не потеряны, просто в моей среде мне действительно нужно, чтобы git log --first-parent был стабильной накопительной записью коммитов - если хотите, своего рода "Write-Once Read-Many" " (WORM) база данных. У меня есть сценарии и процессы, которые используют "git log --first-parent" для создания журналов изменений и заметок о выпуске, а также для управления переходами билетов в моей системе создания билетов (JIRA). Слияния Фокстрот нарушают мои сценарии!
Есть ли какой-нибудь хук предварительного получения, который я мог бы установить в своих репозиториях git, чтобы предотвратить слияние фокстрот?
ps Графики коммитов в этом вопросе stackru были сгенерированы с использованием http://bit-booster.com/graph.html.
3 ответа
Следующий хук предварительного получения заблокирует их:
#/bin/bash
# Copyright (c) 2016 G. Sylvie Davies. http://bit-booster.com/
# Copyright (c) 2016 torek. http://stackru.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, вам, возможно, придется посмотреть на создание какого-либо вызова в их API состояния фиксации (вот api docs для: bitbucket, github; не уверен, есть ли у gitlab), потому что вы не ' у него нет доступа к хукам предварительного получения, и даже если бы вы это сделали, вам все равно пришлось бы иметь дело с людьми, нажимающими кнопку "объединить" непосредственно в веб-интерфейсе этих продуктов (в этом случае "push"),
С Bitbucket Server вы можете установить созданное мной дополнение.
После установки вы нажимаете "включить" в "Protect First Parent Hook" в настройках "ловушки" данного репозитория:
Он заблокирует слияние фокстрот нажатием и кнопкой "слияние" в пользовательском интерфейсе Bitbucket Server. Он делает это, даже если срок действия лицензии истек, что делает "Protect First-Parent Hook" бесплатным компонентом более крупного дополнения.
Вот пример моей ловушки предварительного получения "Защитить первого родителя" в действии:
$ 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:
Для получения дополнительной информации о слиянии фокстрот я написал сообщение в блоге.
Вот код перехвата, который сделает то, что вы просите:
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='\033[0;31m';
green='\033[0;32m';
yellow='\033[0;33m';
default='\033[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: добавить подцепки для обнаружения слияний foxtrot", 2017-08-05).
~ /.git крючки / хелперы / фокстрот-слияние-детектор:
#!/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
# https://stackru.com/questions/35962754/git-how-can-i-prevent-foxtrot-merges-in-my-master-branch
remoteBranch=$(git rev-parse --abbrev-ref "$1"@{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="$1"
url="$2"
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
после слияния:
#!/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