GIT: Как я могу предотвратить слияние foxtrot в моей "основной" ветке?

Объединение foxtrot - это слияние, где "origin/master" сливается в качестве второго (или более позднего) родителя, например:

Commit 'D' - это объединение фокстрота, потому что 'origin/master' является его вторым родителем.

Commit 'D' - это фокстрот-слияние, потому что "origin/master" является вторым родителем. Обратите внимание, что история первоисточника из "origin/master" содержит фиксацию "B" в данный момент.

Но в моем реестре git мне нужны все слияния с участием "origin/master" , чтобы сохранить "origin/master" в качестве первого родителя. К сожалению, git не заботится о родительском заказе, когда оценивает, имеет ли фиксация право на перемотку вперед. Это приводит к тому, что первая родительская история на моей главной ветке иногда теряет фиксации, которые были там (например, вывод "git log - first-parent" ).

Здесь происходит то, что происходит при фиксации "D" с предыдущей диаграммы:

Как я могу предотвратить это нажатие? Первоначальная история

Как я могу предотвратить это нажатие? Первоначальная история "origin/master" больше не содержит фиксацию "B" после того, как сбрасывается foxtrot merge!

Очевидно, что никакие коммиты или работа на самом деле не потеряны, просто в моей среде мне действительно нужен "git log -first-parent", чтобы быть стабильной накопительной записью коммитов - если хотите, своего рода "Write -Once Read-Many" (WORM). У меня есть сценарии и процессы, которые используют "git log --first-parent" для генерации изменений и примечаний к выпуску, а также для управления переходами билетов в моей системе продажи билетов (JIRA). Слияния Foxtrot нарушают мои скрипты!

Есть ли какой-либо предварительный прием, который я мог бы установить в своих репозиториях git, чтобы предотвратить слияние фокстрота?

<я > p.s. Графы фиксации в этом вопросе stackoverflow генерировались с помощью http://bit-booster.com/graph.html.

Ответ 1

Следующий крюк предварительного приема блокирует их:

#/bin/bash

# Copyright (c) 2016 G. Sylvie Davies. http://bit-booster.com/
# Copyright (c) 2016 torek. http://stackoverflow.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 (здесь api docs for: bitbucket, github, не уверен, есть ли у gitlab один), потому что у вас нет доступа к крючкам предварительной отправки, и даже если вы это сделали, вам все равно придется иметь дело с людьми, которые нажимают кнопку "merge" непосредственно в Интернете ui (в этом случае нет "толчка" ).

С Bitbucket Server вы можете установить надстройку, которую я создал.

После установки вы нажимаете "включить" на "Защитите первый родительский крюк" в настройках "крюка" заданного хранилища:

введите описание изображения здесь

Он блокирует слияния foxtrot через push и через кнопку "merge" в пользовательском интерфейсе Bitbucket. Он делает это, даже если срок действия лицензии истек, что делает "Защитить первый родительский крючок" свободным компонентом большего дополнения.

Вот пример моего Bit-Booster "Защитить первый родитель", чтобы получить доступ к приложению:

$ ​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: 

Для получения дополнительной информации об объединении foxtrot Я написал сообщение в блоге.

Ответ 2

Вот код крюка, который будет делать то, что вы просите:

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;

Ответ 3

Я написал это, чтобы обеспечить обратную связь раньше (pre-receive нужен также крючок). Для этого я использую post-merge и pre-push hooks. Невозможно предотвратить объединение foxtrot в hook-commit-msg, поскольку информация для его обнаружения доступна только после. В hook post-merge я просто предупреждаю. В pre-push hook я бросаю и блокирую push. См. d3f1821 ( "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://stackoverflow.com/info/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 "${[email protected]}" 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