Trouver un commit de fusion qui inclut un commit spécifique


190

Imaginez l'histoire suivante:

       c---e---g--- feature
      /         \
-a---b---d---f---h--- master

Comment puis-je trouver quand le commit "c" a été fusionné dans master (c'est-à-dire, trouver le commit de fusion "h")?

Réponses:


162

Votre exemple montre que la branche featureest toujours disponible.

Dans ce cas, hle dernier résultat de:

git log master ^feature --ancestry-path

Si la branche featuren'est plus disponible, vous pouvez afficher les validations de fusion dans la ligne d'historique entre cet master:

git log <SHA-1_for_c>..master --ancestry-path --merges

Cela montrera cependant également toutes les fusions qui se sont produites après h, et entre eet gsur feature.


Comparaison du résultat des commandes suivantes:

git rev-list <SHA-1_for_c>..master --ancestry-path

git rev-list <SHA-1_for_c>..master --first-parent

vous donnera le SHA-1 de hcomme dernière ligne en commun.

Si vous l'avez disponible, vous pouvez l'utiliser comm -1 -2sur ces résultats. Si vous êtes sur msysgit, vous pouvez utiliser le code Perl suivant pour comparer:

perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2

(code perl de http://www.cyberciti.biz/faq/command-to-display-lines-common-in-files/ , qui l'a emprunté à "quelqu'un du groupe de nouvelles comp.unix.shell").

Voir la substitution de processus si vous voulez en faire un one-liner.


4
Même si vous avez comm, vous ne pouvez pas l'utiliser car la sortie de la commande "git rev-list" n'est pas triée lexicographiquement. Vous pouvez bien sûr trier la sortie de chaque commande avant de rechercher des lignes communes, mais alors le commit souhaité ne serait pas nécessairement le dernier. Donc je pense que quelque chose comme la commande perl (bien qu'obscur) est nécessaire.
mhagger

13
Je viens d'écrire un script git-when-merged qui implémente cette suggestion (avec pas mal d'autres fonctionnalités). Voir github.com/mhagger/git-when-merged
mhagger

2
Supposons qu'à un moment donné, a masterété fusionné dans feature, puis featureest immédiatement fusionné en mastertant qu'avance rapide (pointe des featureremplacements master). Cela provoquerait-il --first-parentle retour du mauvais parent?
Kelvin

4
J'ai essayé comm -1 -2mais ça n'a pas marché. commne fonctionne que sur des lignes triées. (Le perl one-liner fonctionne, bien que je ne puisse pas le lire.)
Domon

2
C'est un peu tard mais l'ajout pour que les gens puissent le trouver utile car git ne le fournit pas nativement. Il y a quelques cas de coins que je pense que cette réponse ne gère pas (voir ma réponse ci-dessous stackoverflow.com/a/43716029/58678 qui gère la plupart des cas de coin). Voici les cas du coin: git find-merge h master(ne renvoie rien mais doit renvoyer h), git find-merge d master(renvoie f mais doit renvoyer d), git find-merge c feature(renvoie e mais doit retourner g).
hIpPy

137

Ajoutez ceci à votre ~/.gitconfig:

[alias]
    find-merge = "!sh -c 'commit=$0 && branch=${1:-HEAD} && (git rev-list $commit..$branch --ancestry-path | cat -n; git rev-list $commit..$branch --first-parent | cat -n) | sort -k2 -s | uniq -f1 -d | sort -n | tail -1 | cut -f2'"
    show-merge = "!sh -c 'merge=$(git find-merge $0 $1) && [ -n \"$merge\" ] && git show $merge'"

Ensuite, vous pouvez utiliser les alias comme ceci:

# current branch
git find-merge <SHA-1>
# specify master
git find-merge <SHA-1> master

Pour voir le message de la validation de fusion et d'autres détails, utilisez git show-mergeavec les mêmes arguments.

(Basé sur la réponse de Gauthier . Merci à Rosen Matev et javabrett d' avoir corrigé un problème avec sort.)


6
Magnifique! C'est la meilleure solution sans rendez-vous.
N Jones

1
Attention à sort -k2 | uniq -f1 -d | sort -n | tail -1 | cut -f2ne pas trouver correctement la dernière ligne en commun. Voici un exemple d 'échec.
Rosen Matev

1
@RosenMatev Il trouve 16db9fef5c581ab0c56137d04ef08ef1bf82b0b7ici lorsque je l'exécute sur votre pâte, n'est-ce pas prévu? Sur quel OS êtes-vous?
robinst

1
@robinst, la vôtre a raison. J'ai "(GNU coreutils) 8.4" et pour moi il trouve29c40c3a3b33196d4e79793bd8e503a03753bad1
Rosen Matev

2
@powlo: Rien, c'est juste une commande au lieu de deux pour plus de commodité.
robinst

28

git-get-merge localisera et affichera le commit de fusion que vous recherchez:

pip install git-get-merge
git get-merge <SHA-1>

La commande suit les enfants du commit donné jusqu'à ce qu'une fusion dans une autre branche (probablement maître) soit trouvée.


22

Autrement dit, pour résumer le post de Gauthier:

perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' <(git rev-list --ancestry-path <SHA-1_for_c>..master) <(git rev-list --first-parent <SHA-1_for_c>..master) | tail -n 1

EDIT: parce que cela utilise la substitution de processus " <()", il n'est pas compatible POSIX, et il peut ne pas fonctionner avec votre shell. Cela fonctionne avec bashou zshbien.


4
Je ne copie / colle pas souvent du code à partir d'Internet, mais quand je le fais, cela fonctionne parfaitement! Merci Totor de m'avoir permis de ne pas réfléchir.
Ben

2
@ TheodoreR.Smith malheureusement, la syntaxe <()n'est pas compatible POSIX. Vous devez utiliser bash, zshou un shell prenant en charge la substitution de processus . J'ai modifié ma réponse en conséquence.
Totor

Cela n'a pas fonctionné pour moi. Je pense que ma branche de fonctionnalité a d'abord été fusionnée avec une autre branche avant d'être fusionnée avec master. Cela pourrait expliquer pourquoi vos commandes aboutissent à "Fusionner la branche 'release_branch'" plutôt que "Fusionner la branche 'feature_branch'".
Tim Kuipers

14

J'avais besoin de le faire et j'ai trouvé git-when-merged(qui fait référence à cette question SO, mais Michael Haggerty n'a jamais ajouté de référence à son très beau script Python ici). Alors maintenant je l'ai.


Je suis en fait venu à cette question de sa page.
Flavius

12

En nous appuyant sur la bonne réponse de Gauthier, nous n'avons pas besoin d'utiliser commpour comparer les listes. Puisque nous recherchons le dernier résultat dans --ancestry-pathlequel est également in --first-parent, nous pouvons simplement grep pour ce dernier dans la sortie du premier:

git rev-list <SHA>..master --ancestry-path | grep -f <(git rev-list <SHA>..master --first-parent) | tail -1

Ou pour quelque chose de vif et réutilisable, voici une fonction dans laquelle entrer .bashrc:

function git-find-merge() {
  git rev-list $1..master --ancestry-path | grep -f <(git rev-list $1..master --first-parent) | tail -1
}

Cela a fonctionné pour moi. commne fonctionnait pas lorsque les entrées n'étaient pas triées.
hIpPy

5

Pour la foule Ruby, il y a git-d'où . Très facile.

$ gem install git-whence
$ git whence 1234567
234557 Merge pull request #203 from branch/pathway

4

J'utilise ci-dessous le script bash que je place au chemin ~/bin/git-find-merge. Il est basé sur la réponse de Gauthier et la réponse de evilstreak avec quelques modifications pour traiter les cas d'angle. commjette lorsque les entrées ne sont pas triées. grep -ffonctionne parfaitement.

Cas d'angle:

  • Si commit est un commit de fusion sur le chemin du premier parent de la branche, alors retournez commit.
  • Si commit est un commit NON-merge sur le chemin du premier parent de la branche, alors retournez branch. C'est soit une fusion ff, soit un commit est uniquement sur la branche et il n'y a pas de bon moyen de trouver le bon commit.
  • Si commit et branch sont identiques, retournez commit.

~/bin/git-find-merge scénario:

#!/bin/bash

commit=$1
if [ -z $commit ]; then
    echo 1>&2 "fatal: commit is required"
    exit 1
fi
commit=$(git rev-parse $commit)
branch=${2-@}

# if branch points to commit (both are same), then return commit
if [ $commit == $(git rev-parse $branch) ]; then
    git log -1 $commit
    exit
fi

# if commit is a merge commit on first-parent path of branch,
# then return commit
# if commit is a NON-merge commit on first-parent path of branch,
# then return branch as it's either a ff merge or commit is only on branch
# and there is not a good way to figure out the right commit
if [[ $(git log --first-parent --pretty='%P' $commit..$branch | \
    cut -d' ' -f1 | \
    grep $commit | wc -l) -eq 1 ]]; then
    if [ $(git show -s --format="%P" $commit | wc -w) -gt 1 ]; then
        # if commit is a merge commit
        git log -1 $commit
    else
        # if commit is a NON-merge commit
        echo 1>&2 ""
        echo 1>&2 "error: returning the branch commit (ff merge or commit only on branch)"
        echo 1>&2 ""
        git log -1 $branch
    fi
    exit
fi

# 1st common commit from bottom of first-parent and ancestry-path
merge=$(grep -f \
    <(git rev-list --first-parent  $commit..$branch) \
    <(git rev-list --ancestry-path $commit..$branch) \
        | tail -1)
if [ ! -z $merge ]; then
    git log -1 $merge
    exit
fi

# merge commit not found
echo 1>&2 "fatal: no merge commit found"
exit 1

Ce qui me permet de faire ceci:

(master)
$ git find-merge <commit>    # to find when commit merged to current branch
$ git find-merge <branch>    # to find when branch merged to current branch
$ git find-merge <commit> pu # to find when commit merged to pu branch

Ce script est également disponible sur mon github .


2
git log --topo-order

Recherchez ensuite la première fusion avant la validation.


1

Ma version ruby ​​de l'idée de @ robinst fonctionne deux fois plus vite (ce qui est important lors de la recherche de très vieux commit).

find-commit.rb

commit = ARGV[0]
master = ARGV[1] || 'origin/master'

unless commit
  puts "Usage: find-commit.rb commit [master-branch]"
  puts "Will show commit that merged <commit> into <master-branch>"
  exit 1
end

parents = `git rev-list #{commit}..#{master} --reverse --first-parent --merges`.split("\n")
ancestry = `git rev-list #{commit}..#{master} --reverse --ancestry-path --merges`.split("\n")
merge = (parents & ancestry)[0]

if merge
  system "git show #{merge}"
else
  puts "#{master} doesn't include #{commit}"
  exit 2
end

Vous pouvez simplement l'utiliser comme ceci:

ruby find-commit.rb SHA master

0

Vous pouvez essayer quelque chose comme ça. L'idée est de parcourir tous les commit de fusion et de voir si le commit "c" est accessible à partir de l'un d'eux:

$ git log --merges --format='%h' master | while read mergecommit; do
  if git log --format='%h' $mergecommit|grep -q $c; then
    echo $mergecommit;
    break
  fi
done

C'est la même chose que:git rev-list <SHA-1_for_c>..master --ancestry-path --merges
Eugen Konkov

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.