web-dev-qa-db-fra.com

GIT: Comment puis-je empêcher les fusions de foxtrot dans ma branche «maître»?

Une fusion foxtrot est une fusion où 'Origin/master' fusionne en tant que 2e parent (ou version ultérieure), comme ceci:

Commit 'D' is a foxtrot merge because 'Origin/master' is its 2nd parent.

Commit 'D' est une fusion foxtrot car 'Origin/master' est son 2ème parent. Remarquez comment l'historique du premier parent de 'Origin/master' contient la validation 'B' en ce moment.

Mais dans mon git repo, j'ai besoin de toutes les fusions impliquant 'Origin/master' pour garder 'Origin/master' comme 1er parent. Malheureusement, git ne se soucie pas de l'ordre des parents lorsqu'il évalue si un commit est éligible à l'avance rapide. Cela provoque parfois la perte des validations du premier historique parent sur ma branche principale (par exemple, la sortie de "git log --first-parent").

Voici ce qui se passe lorsque le commit 'D' du diagramme précédent est poussé:

How can I prevent this push? First-parent history of 'Origin/master' no longer contains commit 'B' after the foxtrot merge is pushed!

Comment puis-je empêcher cette poussée? L'historique du premier parent de "Origin/master" ne contient plus de validation "B" après que la fusion foxtrot a été poussée!

Évidemment, aucun commit ou travail n'est réellement perdu, c'est juste que dans mon environnement j'ai vraiment besoin que "git log --first-parent" soit un enregistrement cumulatif stable des commits - si vous voulez, une sorte de "Write-Once Read-Many" "(WORM) base de données. J'ai des scripts et des processus qui utilisent "git log --first-parent" pour générer des changelogs et des notes de version, ainsi que pour gérer les transitions de tickets dans mon système de ticketing (JIRA). Les fusions de Foxtrot cassent mes scripts!

Existe-t-il une sorte de hook de pré-réception que je pourrais installer dans mes référentiels git pour éviter que les fusions foxtrot ne soient poussées?

p.s. Les graphiques de validation dans cette question de stackoverflow ont été générés à l'aide de http://bit-booster.com/graph.html .

31
G. Sylvie Davies

Le crochet de pré-réception suivant les bloquera:

#/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

Si vous utilisez Github/Gitlab/Bitbucket Cloud, vous devrez peut-être envisager de créer une sorte d'appel dans leur commit status apis (voici les documents api pour: bitbucket , github ; je ne sais pas si gitlab en a un), parce que vous n'avez pas accès aux hooks de pré-réception, et même si vous l'avez fait, vous devrez toujours faire face aux personnes qui cliquent sur "fusionner" "directement dans l'interface utilisateur Web de ces produits (auquel cas il n'y a pas de" Push ").

Avec Bitbucket Server, vous pouvez installer le module complémentaire que j'ai créé .

Une fois qu'il est installé, vous cliquez sur "Activer" sur le "Protéger le premier crochet parent" dans les paramètres de "crochet" d'un référentiel donné:

enter image description here

Il bloquera les fusions foxtrot via Push et via le bouton "fusionner" dans l'interface utilisateur du serveur Bitbucket. Il le fait même si sa licence est expirée, faisant du "Protect First-Parent Hook" un composant gratuit du plus gros module complémentaire.

Voici un exemple de mon Bit-Booster "Protéger le premier parent" crochet de pré-réception en action:

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

Pour plus d'informations sur les fusions foxtrot j'ai écrit un article de blog .

16
G. Sylvie Davies

Voici un code de hook qui fera ce que vous demandez:

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;
7
CodeWizard

J'ai écrit ceci pour fournir des commentaires au début (pre-receive un crochet est également nécessaire). J'utilise post-merge et pre-Push crochets pour cela. Il n'est pas possible d'empêcher une fusion foxtrot dans le hook commit-msg car les informations pour en détecter un ne sont disponibles qu'après. Dans post-merge crochet, je préviens simplement. Dans pre-Push crochet, je lance et bloque le Push. Voir d3f1821 ("foxtrot: Ajouter des sous-crochets pour détecter les fusions foxtrot", 2017-08-05).

~/.git-hooks/helpers/foxtrot-merge-detection:

#!/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/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

Et puis utilisez-le comme

crochet pré-push:

#!/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

post-fusion:

#!/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
3
hIpPy