488 votes

Comment trouver le parent le plus proche d'une branche Git ?

Disons que j'ai le dépôt local suivant avec un arbre de commit comme ceci :

master --> a
            \
             \
      develop c --> d
               \
                \
         feature f --> g --> h

master est mon il s'agit du dernier code de la version stable , develop est mon il s'agit du code de la "prochaine" version y feature es une nouvelle fonctionnalité en préparation pour develop .

En utilisant des crochets, je veux être capable de refuser les poussées pour feature à mon référentiel distant, à moins que le commit f est un descendant direct de develop HEAD. C'est à dire que l'arbre de commit ressemble à ceci, parce que la fonctionnalité a été git rebase en d .

master --> a
            \
             \
      develop c --> d
                     \
                      \
               feature f --> g --> h

Alors, est-il possible de :

  • Identifiez la branche mère de feature ?
  • Identifiez le commit de la branche mère qui f est un descendant de ?

A partir de là, je vérifierais quel est le HEAD de la branche mère, et voir si f Le prédécesseur correspond à la branche parent HEAD, pour déterminer si la fonctionnalité doit être rebasée.

0 votes

Cette question devrait être reformulée pour trouver le parent d'un parent.

375voto

Chris Johnsen Points 50064

En supposant que le référentiel distant dispose d'une copie de l'application développer (votre description initiale le décrit dans un dépôt local, mais il semble qu'il existe également dans le dépôt distant), vous devriez être en mesure de réaliser ce que je pense que vous voulez, mais l'approche est un peu différente de ce que vous avez envisagé.

L'histoire de Git est basée sur un DAG de commits. Les branches (et les "refs" en général) ne sont que des étiquettes transitoires qui pointent vers des commits spécifiques dans le DAG des commits qui grandit continuellement. En tant que telle, la relation entre les branches peut varier dans le temps, mais pas la relation entre les commits.

    ---o---1                foo
            \
             2---3---o      bar
                  \
                   4
                    \
                     5---6  baz

On dirait que baz est basé sur (une ancienne version de) bar ? Mais que se passe-t-il si nous supprimons bar ?

    ---o---1                foo
            \
             2---3
                  \
                   4
                    \
                     5---6  baz

Maintenant, ça ressemble à baz est basé sur foo . Mais l'ascendance de baz n'a pas changé. Nous avons juste supprimé une étiquette (et le commit qui en résulte). Et si nous ajoutons une nouvelle étiquette à 4 ?

    ---o---1                foo
            \
             2---3
                  \
                   4        quux
                    \
                     5---6  baz

Maintenant, ça ressemble à baz est basé sur quux . Pourtant, l'ascendance n'a pas changé, seules les étiquettes ont changé.

Si, toutefois, nous demandions "est-ce que le commit 6 un descendant de commit 3 ?" (en supposant que 3 y 6 sont des noms de commit SHA-1 complets), alors la réponse serait "oui", que les bar y quux Les étiquettes sont présentes ou non.

Ainsi, vous pourriez poser des questions comme "est-ce que le commit poussé est un descendant de la pointe actuelle du développer mais vous ne pouvez pas demander de manière fiable "quelle est la branche parente du commit poussé ?

Une question fiable qui semble se rapprocher de ce que vous voulez est la suivante :

Pour tous les ancêtres du commit poussé (à l'exception de la pointe actuelle de développer et ses ancêtres), qui ont la pointe actuelle de développer en tant que parent :

  • existe-t-il au moins un tel engagement ?
  • Est-ce que tous ces commits sont des commits monoparentaux ?

Ce qui pourrait être mis en œuvre comme :

pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
    echo "'$basename' is missing, call for help!"
    exit 1
fi
parents_of_children_of_base="$(
  git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
  grep -F "$baserev"
)"
case ",$parents_of_children_of_base" in
    ,)     echo "must descend from tip of '$basename'"
           exit 1 ;;
    ,*\ *) echo "must not merge tip of '$basename' (rebase instead)"
           exit 1 ;;
    ,*)    exit 0 ;;
esac

Cela couvrira une partie de ce que vous voulez restreindre, mais peut-être pas tout.

À titre de référence, voici un exemple d'historique étendu :

    A                                   master
     \
      \                    o-----J
       \                  /       \
        \                | o---K---L
         \               |/
          C--------------D              develop
           \             |\
            F---G---H    | F'--G'--H'
                    |    |\
                    |    | o---o---o---N
                     \   \      \       \
                      \   \      o---o---P
                       \   \
                        R---S

Le code ci-dessus pourrait être utilisé pour rejeter H y S tout en acceptant H' , J , K o N mais il accepterait également L y P (ils impliquent des fusions, mais ils ne fusionnent pas l'extrémité des développer ).

Rejeter également L y P vous pouvez changer la question et demander

Pour tous les ancêtres du commit poussé (à l'exception de la pointe actuelle de développer et ses ancêtres) :

  • Y a-t-il des engagés avec deux parents ?
  • Si ce n'est pas le cas, est-ce qu'au moins un de ces commit a le tip actuel de développer son (seul) parent ?
pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
    echo "'$basename' is missing, call for help!"
    exit 1
fi
parents_of_commits_beyond_base="$(
  git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
  grep -v '^commit '
)"
case "$parents_of_commits_beyond_base" in
    *\ *)          echo "must not push merge commits (rebase instead)"
                   exit 1 ;;
    *"$baserev"*)  exit 0 ;;
    *)             echo "must descend from tip of '$basename'"
                   exit 1 ;;
esac

0 votes

J'obtiens ceci : git : fatal : ambiguous argument '...' : both revision and filename. what is the triple dots intention ?

1 votes

@Schneider Je suis presque sûr que le '...' est destiné à être un espace dans cet exemple : si vous le remplacez par le SHA du commit sur lequel vous essayez d'effectuer cette vérification (disons, le HEAD de la branche sur laquelle vous êtes actuellement), tout fonctionne parfaitement.

0 votes

Merci pour cette réponse élaborée ! C'est super utile. Je veux faire un hook similaire, mais je ne veux pas coder en dur le nom de la branche de développement. En d'autres termes, je veux un hook pour empêcher le rebasement sur une branche autre que la branche parente. Si je comprends bien votre réponse (je suis nouveau à bash et tout ça), ceci n'est pas couvert dans votre réponse, n'est-ce pas ? Existe-t-il un moyen de faire cela ?

293voto

JoeChrysler Points 151

Un rephrasé

Une autre façon de formuler la question est "Quel est le commit le plus proche qui réside sur une branche autre que la branche actuelle, et de quelle branche s'agit-il ?".

Une solution

Vous pouvez le trouver avec un peu de magie en ligne de commande

git show-branch \
| sed "s/].*//" \
| grep "\*" \
| grep -v "$(git rev-parse --abbrev-ref HEAD)" \
| head -n1 \
| sed "s/^.*\[//"

Avec AWK :

git show-branch -a \
| grep '\*' \
| grep -v `git rev-parse --abbrev-ref HEAD` \
| head -n1 \
| sed 's/[^\[]*//' \
| awk 'match($0, /\[[a-zA-Z0-9\/-]+\]/) { print substr( $0, RSTART+1, RLENGTH-2 )}'

Voici comment cela fonctionne :

  1. Affiche un historique textuel de tous les commits, y compris les branches distantes.
  2. Les ancêtres de la livraison actuelle sont indiqués par une étoile. Filtrez tout le reste.
  3. Ignore tous les commits de la branche courante.
  4. Le premier résultat sera la branche de l'ancêtre le plus proche. Ignorez les autres résultats.
  5. Les noms des branches sont affichés [entre parenthèses]. Ignorez tout ce qui se trouve en dehors des crochets, ainsi que les crochets.
  6. Parfois, le nom de la branche comprendra un ~# o ^# pour indiquer combien de commits se trouvent entre le commit référencé et le bout de branche. Nous ne nous en soucions pas. Ignorez-les.

Et le résultat

En exécutant le code ci-dessus sur

 A---B---D <-master
      \
       \
        C---E---I <-develop
             \
              \
               F---G---H <-topic

Vous donnera develop si vous l'exécutez à partir de H et master si vous l'exécutez à partir de moi.

Le code est disponible sous forme de gist .

26 votes

Suppression d'un backtick de fin qui causait une erreur. Cependant, lorsque j'exécute cette commande, j'obtiens un grand nombre d'avertissements, se plaignant de chaque branche qui dit cannot handle more than 25 refs

1 votes

@JoeChrysler pensez-vous pouvoir le faire en une seule ligne au lieu de 2, et éventuellement le faire fonctionner sur Mac comme ack n'est pas disponible sur Mac (quelqu'un a suggéré de remplacer ack con grep )

0 votes

Ça sonne comme une idée. Fonctionne dans les cas les plus simples. Voici la version avec grep : git show-branch | grep '*' | grep -v '$branch' | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//'

140voto

NIKHIL C M Points 713

Git parent

Vous pouvez simplement exécuter la commande

git parent

pour trouver le parent de la branche, si vous ajoutez l'option La réponse de Joe Chrysler en tant que Alias Git . Cela simplifiera l'utilisation.

Ouvrez le gitconfig situé à l'adresse "~/.gitconfig" en utilisant n'importe quel éditeur de texte (pour Linux). Et pour Windows, le chemin ".gitconfig" se trouve généralement à l'adresse suivante C:\users\your-user\.gitconfig .

vim  ~/.gitconfig

Ajoutez la commande alias suivante dans le fichier :

[alias]
    parent = "!git show-branch | grep '*' | grep -v \"$(git rev-parse --abbrev-ref HEAD)\" | head -n1 | sed 's/.*\\[\\(.*\\)\\].*/\\1/' | sed 's/[\\^~].*//' #"

Sauvegardez et quittez l'éditeur.

Exécutez la commande git parent .

C'est ça !

6 votes

C'est une excellente solution. Il serait utile d'ajouter également quelques exemples de résultats pour s'assurer des résultats attendus. Lorsque je l'ai exécuté, j'ai reçu quelques avertissements avant la toute dernière ligne qui, je crois, était le nom de la branche parente.

4 votes

Fonctionne comme un charme ! Pour les utilisateurs de Windows, le fichier .gitconfig est généralement situé à l'emplacement c : \users\your -utilisateur \.gitconfig

21 votes

Obtenir cannot handle more than 25 refs exception.

131voto

rcde0 Points 1590

Vous pouvez également essayer :

git log --graph --decorate

9 votes

git log --graph --decorate --simplify-by-decoration donde --graph est facultatif.

6 votes

git log --graph --decorate --simplify-by-decoration --oneline

0 votes

Git log --decorate=full --simplify-by-decoration --oneline --format="%D"

55voto

Daniel Stutzbach Points 20026

J'ai une solution à votre problème global (déterminer si feature descend de la pointe de develop ), mais cela ne fonctionne pas avec la méthode que vous avez décrite.

Vous pouvez utiliser git branch --contains pour lister toutes les branches descendant de la pointe de develop puis utiliser grep pour s'assurer feature est parmi eux.

git branch --contains develop | grep "^ *feature$"

S'il est parmi eux, il imprimera " feature" sur la sortie standard et aura un code de retour de 0. Sinon, il n'imprimera rien et aura un code de retour de 1.

1 votes

Cela fonctionne mais il faut noter que cela peut prendre beaucoup de temps sur un référentiel qui a beaucoup de refs. Cela la rend un peu moins qu'idéale pour être exécutée, par exemple, dans un hook de pré-réception.

0 votes

Je cherchais la branche, on va l'appeler <branch> où je me suis exécuté : git checkout -b <branch-2> de... C'est la réponse ! Pas besoin de grep, vraiment. git branch --contains <branch>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X