Trouver un emtwigment avec Git?

J’ai un repository avec les twigs master et A et beaucoup d’activités de fusion entre les deux. Comment puis-je trouver la validation dans mon référentiel lorsque la twig A a été créée sur la base de master?

Mon référentiel ressemble à ceci:

-- X -- A -- B -- C -- D -- F (master) \ / \ / \ / \ / G -- H -- I -- J (branch A) 

Je cherche la révision A, qui n’est pas ce que git merge-base (--all) trouve.

Je cherchais la même chose et j’ai trouvé cette question. Merci de l’avoir demandé!

Cependant, j’ai trouvé que les réponses que je vois ici ne semblent pas donner la réponse que vous avez demandée (ou que je cherchais) – elles semblent donner le commit G , au lieu du commit A

J’ai donc créé l’arbre suivant (lettres atsortingbuées dans l’ordre chronologique), afin de pouvoir tester les choses:

 A - B - D - F - G <- "master" branch (at G) \ \ / C - E --' <- "topic" branch (still at E) 

Cela semble un peu différent du vôtre, parce que je voulais m'assurer que j'avais (en référence à ce graphique, pas le votre) B, mais pas A (et pas D ou E). Voici les lettres attachées aux préfixes SHA et aux messages de validation (mon repo peut être cloné à partir d' ici , si cela est intéressant pour tout le monde):

 G: a9546a2 merge from topic back to master F: e7c863d commit on master after master was merged to topic E: 648ca35 merging master onto topic D: 37ad159 post-branch commit on master C: 132ee2a first commit on topic branch B: 6aafd7f second commit on master before branching A: 4112403 initial commit on master 

Donc, le but: trouver B. Voici trois manières que j'ai trouvées, après un peu de bricolage:


1. visuellement, avec gitk:

Vous devriez voir visuellement un arbre comme celui-ci (vu du maître):

capture d'écran gitk du maître

ou ici (vu du sujet):

capture d'écran gitk du sujet

dans les deux cas, j'ai sélectionné le commit B dans mon graphique. Une fois que vous cliquez dessus, son SHA complet est présenté dans un champ de saisie de texte juste en dessous du graphique.


2. visuellement, mais depuis le terminal:

git log --graph --oneline --all

(Edit / side-note: l'ajout de --decorate peut également être intéressant; cela ajoute une indication sur les noms des twigs, les balises, etc. Ne l'ajoute pas à la ligne de commande ci-dessus car la sortie ci-dessous ne reflète pas son utilisation.)

qui montre (en supposant git config --global color.ui auto ):

sortie de git log --graph --oneline --all

Ou, en texte clair:

 * a9546a2 fusionne de sujet à maître
 | \  
 |  * 648ca35 fusion du maître sur le sujet
 |  | \  
 |  * |  132ee2a s'engage d'abord sur la twig thématique
 * |  |  e7c863d valide sur le maître après la fusion du maître au sujet
 |  | /  
 | / |   
 * |  37ad159 post-twigment sur master
 | /  
 * 6aafd7f deuxième engagement sur le maître avant la ramification
 * 4112403 premier engagement sur le maître

dans les deux cas, nous voyons le 6aafd7f commettre comme point commun le plus bas, c'est-à-dire B dans mon graphique ou A dans le vôtre.


3. Avec la magie de la coquille:

Vous ne spécifiez pas dans votre question si vous vouliez quelque chose comme ci-dessus, ou une seule commande qui ne vous apporterait qu'une seule révision, et rien d'autre. Eh bien, voici le dernier:

 diff -u <(git rev-list --first-parent topic) \ <(git rev-list --first-parent master) | \ sed -ne 's/^ //p' | head -1 6aafd7ff98017c816033df18395c5c1e7829960d 

Que vous pouvez également mettre dans votre ~ / .gitconfig comme (note: le tiret final est important; merci Brian pour avoir attiré votre attention sur cela) :

 [alias] oldest-ancestor = !zsh -c 'diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' - 

Ce qui pourrait être fait via la ligne de commande suivante (avec des guillemets):

 git config --global alias.oldest-ancestor '!zsh -c '\''diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne "s/^ //p" | head -1'\'' -' 

Note: zsh aurait tout aussi bien pu être bash , mais sh ne fonctionnerait pas - la syntaxe <() n'existe pas dans vanilla sh . (Merci encore, @conny, de m'avoir fait savoir dans un commentaire sur une autre réponse sur cette page!)

Note: Autre version de ce qui précède:

Merci à liori pour lui faire remarquer que ce qui précède pourrait tomber en panne lors de la comparaison de twigs identiques, et proposer une autre forme de diff qui supprime la forme sed du mixage, et rend cela plus sûr (c.-à-d. validation la plus récente) même lorsque vous comparez le maître au maître):

En tant que ligne .git-config:

 [alias] oldest-ancestor = !zsh -c 'diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1' - 

De la coquille:

 git config --global alias.oldest-ancestor '!zsh -c '\''diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1'\'' -' 

Donc, dans mon arbre de test (qui était indisponible pendant un moment, désolé, c'est de retour), cela fonctionne maintenant à la fois sur le maître et le sujet (en donnant respectivement les commits G et B). Merci encore, liori, pour le formulaire alternatif.


Donc, c'est ce que j'ai [et Liori] trouvé. Il semble fonctionner pour moi. Il permet également quelques alias supplémentaires qui pourraient s'avérer utiles:

 git config --global alias.branchdiff '!sh -c "git diff `git oldest-ancestor`.."' git config --global alias.branchlog '!sh -c "git log `git oldest-ancestor`.."' 

Bonne git-ing!

Vous recherchez peut-être une git merge-base :

git merge-base trouve le meilleur ancêtre commun entre deux commits à utiliser dans une fusion à trois. Un ancêtre commun est meilleur qu’un autre ancêtre commun si ce dernier est un ancêtre du premier. Un ancêtre commun qui n’a pas de meilleur ancêtre commun est un meilleur ancêtre commun , à savoir une base de fusion . Notez qu’il peut y avoir plus d’une base de fusion pour une paire de validations.

J’ai utilisé git rev-list pour ce genre de choses. Par exemple, (notez les 3 points)

 $ git rev-list --boundary branch-a...master | grep "^-" | cut -c2- 

va recracher le sharepoint twigment. Maintenant, ce n’est pas parfait. Depuis que vous avez fusionné le maître dans la twig A plusieurs fois, cela divisera quelques points de twig possibles (essentiellement le sharepoint twig d’origine, puis chaque point auquel vous avez fusionné le maître dans la twig A). Cependant, il devrait au moins limiter les possibilités.

J’ai ajouté cette commande à mes alias dans ~/.gitconfig tant que:

 [alias] diverges = !sh -c 'git rev-list --boundary $1...$2 | grep "^-" | cut -c2-' 

donc je peux l’appeler comme:

 $ git diverges branch-a master 

Si vous aimez les commandes laconiques,

  git rev-list $ (git rev-list --first-parent ^ nom_base master | tail -n1) ^^! 

Voici une explication.

La commande suivante vous donne la liste de toutes les validations effectuées dans master après la création de branch_name

  git rev-list --first-parent ^ nom_fonction master 

Puisque vous ne vous souciez que de la première de ces validations, vous voulez la dernière ligne de la sortie:

  git rev-list ^ nom_twig --first-parent master |  queue -n1 

Le parent du premier commit qui n’est pas un ancêtre de “branch_name” est, par définition, dans “branch_name” et se trouve dans “master” puisqu’il s’agit d’un ancêtre de quelque chose dans “master”. Vous avez donc le premier engagement dans les deux twigs.

La commande

  git rev-list commit ^^! 

est juste un moyen d’afficher la référence de validation du parent. Vous pourriez utiliser

  git log -1 commit ^ 

ou peu importe.

PS: Je ne suis pas d’accord avec l’argument selon lequel l’ordre des ancêtres n’est pas pertinent. Cela dépend de ce que vous voulez. Par exemple, dans ce cas

 _C1___C2_______ maître
   \ \ _XXXXX_ twig A (les X désignent des croisements arbitraires entre master et A)
    \ _____ / twig B

il est parfaitement logique d’exécuter C2 en tant que validation “branchée”. C’est à ce moment que le développeur est sorti de “master”. Quand il a ramifié, la twig “B” n’a même pas été fusionnée dans sa twig! C’est ce que donne la solution dans cet article.

Si ce que vous voulez est le dernier commit C tel que tous les chemins de l’origine au dernier commit sur la twig “A” passent par C, alors vous voulez ignorer l’ordre de l’ascendance. C’est purement topologique et vous donne une idée de quand vous avez deux versions du code en même temps. C’est à ce moment-là que vous utiliseriez des approches basées sur la fusion et que cela renverrait C1 dans mon exemple.

Étant donné que beaucoup de réponses dans ce fil ne donnent pas la réponse à la question, voici un résumé des résultats de chaque solution, avec le script que j’ai utilisé pour répliquer le référentiel donné dans la question.

Le journal

En créant un repository avec la structure donnée, on obtient le journal de git de:

 $ git --no-pager log --graph --oneline --all --decorate * b80b645 (HEAD, branch_A) J - Work in branch_A branch | * 3bd4054 (master) F - Merge branch_A into branch master | |\ | |/ |/| * | a06711b I - Merge master into branch_A |\ \ * | | bcad6a3 H - Work in branch_A | | * b46632a D - Work in branch master | |/ | * 413851d C - Merge branch_A into branch master | |\ | |/ |/| * | 6e343aa G - Work in branch_A | * 89655bb B - Work in branch master |/ * 74c6405 (tag: branch_A_tag) A - Work in branch master * 7a1c939 X - Work in branch master 

Mon seul ajout est le tag qui le rend explicite sur le point où nous avons créé la twig et donc le commit que nous souhaitons trouver.

La solution qui fonctionne

La seule solution qui fonctionne est celle fournie par lindes renvoie correctement A :

 $ diff -u <(git rev-list --first-parent branch_A) \ <(git rev-list --first-parent master) | \ sed -ne 's/^ //p' | head -1 74c6405d17e319bd0c07c690ed876d65d89618d5 

Comme le souligne Charles Bailey , cette solution est très fragile.

Si vous branch_A dans master , puis fusionnez master dans branch_A sans intervention, la solution de lindes ne vous donne que la première divergence la plus récente .

Cela signifie que pour mon stream de travail, je pense que je vais devoir me contenter de baliser le sharepoint twigment de twigs de longue durée, car je ne peux pas garantir qu’elles puissent être trouvées de manière fiable ultérieurement.

Cela se résume vraiment à l'absence de ce que hg appelle les twigs nommées . Le blogueur jhw appelle ces lignées vs familles dans son article Pourquoi j'aime Mercurial Plus que Git et son article de suivi More On Mercurial vs. Git (avec Graphs!) . Je recommanderais aux gens de les lire pour voir pourquoi certains convertis mercuriels ne manquent pas de ne pas avoir de twigs nommées dans git .

Les solutions qui ne fonctionnent pas

La solution fournie par mipadi renvoie deux réponses, I et C :

 $ git rev-list --boundary branch_A...master | grep ^- | cut -c2- a06711b55cf7275e8c3c843748daaa0aa75aef54 413851dfecab2718a3692a4bba13b50b81e36afc 

La solution proposée par Greg Hewgill I revient

 $ git merge-base master branch_A a06711b55cf7275e8c3c843748daaa0aa75aef54 $ git merge-base --all master branch_A a06711b55cf7275e8c3c843748daaa0aa75aef54 

La solution fournie par Karl renvoie X :

 $ diff -u <(git log --pretty=oneline branch_A) \ <(git log --pretty=oneline master) | \ tail -1 | cut -c 2-42 7a1c939ec325515acfccb79040b2e4e1c3e7bbe5 

Le script

 mkdir $1 cd $1 git init git commit --allow-empty -m "X - Work in branch master" git commit --allow-empty -m "A - Work in branch master" git branch branch_A git tag branch_A_tag -m "Tag branch point of branch_A" git commit --allow-empty -m "B - Work in branch master" git checkout branch_A git commit --allow-empty -m "G - Work in branch_A" git checkout master git merge branch_A -m "C - Merge branch_A into branch master" git checkout branch_A git commit --allow-empty -m "H - Work in branch_A" git merge master -m "I - Merge master into branch_A" git checkout master git commit --allow-empty -m "D - Work in branch master" git merge branch_A -m "F - Merge branch_A into branch master" git checkout branch_A git commit --allow-empty -m "J - Work in branch_A branch" 

Je doute que la version git fasse beaucoup de différence à ce sujet, mais:

 $ git --version git version 1.7.1 

Merci à Charles Bailey pour m'avoir montré un moyen plus compact de scripter le repository d'exemple.

En général, ce n’est pas possible. Dans un historique de twig, une twig et une fusion avant qu’une twig nommée ait été dérivée et une twig intermédiaire de deux twigs nommées se ressemblent.

Dans git, les twigs ne sont que les noms actuels des bouts de sections de l’histoire. Ils n’ont pas vraiment une identité forte.

Ce n’est généralement pas un gros problème car la base de fusion (voir la réponse de Greg Hewgill) de deux commits est généralement beaucoup plus utile, donnant la validation la plus récente partagée par les deux twigs.

Une solution reposant sur l’ordre des parents d’un commit ne fonctionnera évidemment pas dans les situations où une succursale a été complètement intégrée à un moment donné dans l’histoire de la twig.

 git commit --allow-empty -m root # actual branch commit git checkout -b branch_A git commit --allow-empty -m "branch_A commit" git checkout master git commit --allow-empty -m "More work on master" git merge -m "Merge branch_A into master" branch_A # identified as branch point git checkout branch_A git merge --ff-only master git commit --allow-empty -m "More work on branch_A" git checkout master git commit --allow-empty -m "More work on master" 

Cette technique tombe également en panne si une fusion d’intégration a été effectuée avec les parents inversés (par exemple, une twig temporaire a été utilisée pour effectuer une fusion de test dans master et ensuite transférée rapidement dans la twig de fonctions pour être développée).

 git commit --allow-empty -m root # actual branch point git checkout -b branch_A git commit --allow-empty -m "branch_A commit" git checkout master git commit --allow-empty -m "More work on master" git merge -m "Merge branch_A into master" branch_A # identified as branch point git checkout branch_A git commit --allow-empty -m "More work on branch_A" git checkout -b tmp-branch master git merge -m "Merge branch_A into tmp-branch (master copy)" branch_A git checkout branch_A git merge --ff-only tmp-branch git branch -d tmp-branch git checkout master git commit --allow-empty -m "More work on master" 

Que diriez-vous de quelque chose comme

 git log --pretty=oneline master > 1 git log --pretty=oneline branch_A > 2 git rev-parse `diff 1 2 | tail -1 | cut -c 3-42`^ 

J’ai récemment eu besoin de résoudre ce problème et j’ai fini par écrire un script Ruby pour ceci: https://github.com/vaneyckt/git-find-branching-point

il me manque sûrement quelque chose, mais l’OMI, tous les problèmes ci-dessus sont dus au fait que nous essayons toujours de trouver le sharepoint ramification remontant à l’histoire, et cela cause toutes sortes de problèmes en raison des combinaisons de fusion disponibles.

Au lieu de cela, j’ai suivi une approche différente, basée sur le fait que les deux twigs partagent beaucoup d’histoire, tout l’historique avant la création des twigs est 100% identique, donc au lieu de revenir en arrière, ma proposition commit), à la recherche de la 1ère différence dans les deux twigs. Le sharepoint twigment sera simplement le parent de la première différence trouvée.

En pratique:

 #!/bin/bash diff <( git rev-list "${1:-master}" --reverse --topo-order ) \ <( git rev-list "${2:-HEAD}" --reverse --topo-order) \ --unified=1 | sed -ne 's/^ //p' | head -1 

Et ça résout tous mes cas habituels. Bien sûr, il y a des frontières non couvertes mais ... ciao 🙂

Après de nombreuses recherches et discussions, il est clair qu’il n’y a pas de solution miracle qui pourrait fonctionner dans toutes les situations, du moins pas dans la version actuelle de Git.

C’est pourquoi j’ai écrit quelques patchs qui ajoutent le concept d’une twig de tail . Chaque fois qu’une twig est créée, un pointeur vers le point d’origine est créé, la tail ref. Cette référence est mise à jour chaque fois que la twig est rebasée.

Pour trouver le sharepoint twigment de la twig de développement, il suffit d’utiliser devel@{tail} , c’est tout.

https://github.com/felipec/git/commits/fc/tail

La commande suivante révélera le SHA1 de Commit A

git merge-base --fork-point A

Voici une version améliorée de ma réponse précédente réponse précédente . Il s’appuie sur les messages de validation des fusions pour trouver la première création de la twig.

Cela fonctionne sur tous les référentiels mentionnés ici, et j’ai même abordé quelques problèmes difficiles qui ont surgi sur la liste de diffusion . J’ai aussi écrit des tests pour cela.

 find_merge () { local selection extra test "$2" && extra=" into $2" git rev-list --min-parents=2 --grep="Merge branch '$1'$extra" --topo-order ${3:---all} | tail -1 } branch_point () { local first_merge second_merge merge first_merge=$(find_merge $1 "" "$1 $2") second_merge=$(find_merge $2 $1 $first_merge) merge=${second_merge:-$first_merge} if [ "$merge" ]; then git merge-base $merge^1 $merge^2 else git merge-base $1 $2 fi } 

Pour trouver des commits à partir du sharepoint twigment, vous pouvez utiliser ceci.

 git log --ancestry-path master..topicbranch 

Parfois, il est effectivement impossible (à quelques exceptions près où vous pourriez avoir de la chance d’avoir des données supplémentaires) et les solutions proposées ici ne fonctionneront pas.

Git ne conserve pas l’historique des références (qui inclut les twigs). Il ne stocke que la position actuelle pour chaque twig (la tête). Cela signifie que vous pouvez perdre certains antécédents de twig avec le temps. Chaque fois que vous twigz par exemple, vous perdez immédiatement la twig d’origine. Tout ce que fait une succursale est:

 git checkout branch1 # refs/branch1 -> commit1 git checkout -b branch2 # branch2 -> commit1 

Vous pouvez supposer que le premier engagé est la twig. Cela tend à être le cas mais ce n’est pas toujours le cas. Rien ne vous empêche de vous engager dans l’une ou l’autre twig après l’opération ci-dessus. De plus, les horodatages git ne sont pas garantis pour être fiables. Ce n’est que lorsque vous vous engagez à les deux qu’ils deviennent vraiment des twigs structurelles.

Alors que dans les diagrammes nous avons tendance à numéroter les commits de manière conceptuelle, git n’a pas de concept de séquence vraiment stable lorsque l’arbre de validation est branché. Dans ce cas, vous pouvez supposer que les nombres (indiquant l’ordre) sont déterminés par l’horodatage (il peut être amusant de voir comment une interface utilisateur git gère les choses lorsque vous définissez tous les horodatages de la même façon).

C’est ce qu’un humain attend conceptuellement:

 After branch: C1 (B1) / - \ C1 (B2) After first commit: C1 (B1) / - \ C1 - C2 (B2) 

C’est ce que vous obtenez réellement:

 After branch: - C1 (B1) (B2) After first commit (human): - C1 (B1) \ C2 (B2) After first commit (real): - C1 (B1) - C2 (B2) 

Vous supposez que B1 est la twig d’origine, mais il pourrait en fait s’agir simplement d’une twig morte (quelqu’un a effectué une vérification -b mais ne s’y est jamais engagé). Ce n’est que lorsque vous vous engagez à la fois que vous obtenez une structure de twig légitime dans git:

 Either: / - C2 (B1) -- C1 \ - C3 (B2) Or: / - C3 (B1) -- C1 \ - C2 (B2) 

Vous savez toujours que C1 est arrivé avant C2 et C3, mais vous ne savez jamais si C2 est arrivé avant C3 ou C3 avant C2 (parce que vous pouvez par exemple définir l’heure sur votre poste de travail). B1 et B2 sont également trompeurs car vous ne pouvez pas savoir quelle twig est arrivée en premier. Vous pouvez faire une très bonne estimation, généralement exacte, dans de nombreux cas. C’est un peu comme une piste de course. Toutes les choses étant généralement égales avec les voitures, vous pouvez supposer qu’une voiture qui arrive au tour arrière a commencé un tour derrière. Nous avons également des conventions très fiables, par exemple master représentera presque toujours les twigs les plus longues, mais malheureusement, j’ai vu des cas où ce n’était même pas le cas.

L’exemple donné ici est un exemple de préservation de l’historique:

 Human: - X - A - B - C - D - F (B1) \ / \ / G - H ----- I - J (B2) Real: B ----- C - D - F (B1) / / \ / - X - A / \ / \ / \ / G - H ----- I - J (B2) 

Réel ici est également trompeur car nous, humains, le lisons de gauche à droite, racine à feuille (ref). Git ne fait pas ça. Où nous en sums (A-> B) dans nos têtes git (A <-B ou B-> A). Il le lit de ref à root. Les références peuvent être n’importe où mais ont tendance à être des feuilles, au moins pour les twigs actives. Une référence pointe vers un commit et les commits contiennent uniquement un like à leurs parents / s, pas à leurs enfants. Quand une validation est une validation de fusion, elle aura plusieurs parents. Le premier parent est toujours le commit d’origine qui a été fusionné. Les autres parents sont toujours des commits qui ont été fusionnés dans le commit d’origine.

 Paths: F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X))))) J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X))))) 

Ce n’est pas une représentation très efficace, mais plutôt une expression de tous les chemins que git peut prendre à partir de chaque ref (B1 et B2).

Le stockage interne de Git ressemble plus à ceci (pas que A en tant que parent apparaisse deux fois):

  F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A 

Si vous videz un commit git brut, vous verrez zéro ou plusieurs champs parents. S’il y a zéro, cela signifie qu’aucun parent et le commit est une racine (vous pouvez réellement avoir plusieurs racines). S’il y en a un, cela signifie qu’il n’y a pas eu de fusion et ce n’est pas un commit racine. S’il y en a plus d’un, cela signifie que le commit est le résultat d’une fusion et que tous les parents après le premier sont des validations de fusion.

 Paths simplified: F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X Paths first parents only: F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X J->(I->(H->(G->(A->X))) | J->I->H->G->A->X Or: F->D->C | J->I | I->H | C->B->A | H->G->A | A->X Paths first parents only simplified: F->D->C->B->A | J->I->->G->A | A->X Topological: - X - A - B - C - D - F (B1) \ G - H - I - J (B2) 

Lorsque les deux frappent A, leur chaîne sera la même avant que leur chaîne ne soit complètement différente. Le premier commit que deux autres commits ont en commun est l’ancêtre commun et d’où ils ont divergé. il pourrait y avoir une certaine confusion entre les termes commit, branch et ref. Vous pouvez en effet fusionner un commit. C’est ce que fait vraiment la fusion. Une ref pointe simplement vers un commit et une twig n’est rien de plus qu’une référence dans le dossier .git / refs / heads, l’emplacement du dossier est ce qui détermine qu’une ref est une twig plutôt que quelque chose comme une balise.

Là où vous perdez l’histoire, c’est que la fusion fera l’une des deux choses selon les circonstances.

Considérer:

  / - B (B1) - A \ - C (B2) 

Dans ce cas, une fusion dans l’une ou l’autre direction créera une nouvelle validation avec le premier parent en tant que la validation pointée par la twig extraite en cours et le deuxième parent en tant que validation à l’extrémité de la twig fusionnée dans votre twig actuelle. Il doit créer un nouveau commit car les deux twigs ont des modifications depuis leur ancêtre commun qui doivent être combinées.

  / - B - D (B1) - A / \ --- C (B2) 

A ce stade, D (B1) a maintenant les deux jeux de modifications des deux twigs (lui-même et B2). Cependant, la deuxième twig n’a pas les modifications de B1. Si vous fusionnez les modifications de B1 à B2 pour qu’elles soient synchronisées, vous pouvez vous attendre à quelque chose qui ressemble à ceci (vous pouvez forcer git merge à le faire comme ça avec –no-ff):

 Expected: / - B - D (B1) - A / \ \ --- C - E (B2) Reality: / - B - D (B1) (B2) - A / \ --- C 

Vous obtiendrez cela même si B1 a des commits supplémentaires. Tant qu’il n’y a pas de changement dans B2 que B1 n’a pas, les deux twigs seront fusionnées. Il effectue une avance rapide qui ressemble à une rebase (les rebases mangent ou linéarisent également l’historique), sauf que contrairement à une rebase car une seule twig a un ensemble de modifications, elle n’a pas besoin d’appliquer une modification.

 From: / - B - D - E (B1) - A / \ --- C (B2) To: / - B - D - E (B1) (B2) - A / \ --- C 

Si vous cessez de travailler sur B1, les choses vont bien pour préserver l’histoire à long terme. Seul B1 (qui pourrait être maître) avancera généralement, si bien que l’emplacement de B2 dans l’historique de B2 représente le point où il a été fusionné dans B1. C’est ce que git s’attend à ce que vous fassiez, à la twig B de A, puis vous pouvez fusionner A dans B autant que vous le souhaitez alors que les changements s’accumulent, mais lorsque vous fusionnez B dans A, . If you carry on working on your branch after fast forward merging it back into the branch you were working on then your erasing B’s previous history each time. You’re really creating a new branch each time after fast forward commit to source then commit to branch. You end up with when you fast forward commit is lots of twigs/merges that you can see in the history and structure but without the ability to determine what the name of that branch was or if what looks like two separate twigs is really the same branch.

  0 1 2 3 4 (B1) /-\ /-\ /-\ /-\ / ---- - - - - \-/ \-/ \-/ \-/ \ 5 6 7 8 9 (B2) 

1 to 3 and 5 to 8 are structural twigs that show up if you follow the history for either 4 or 9. There’s no way in git to know which of this unnamed and unreferenced structural twigs belong to with of the named and references twigs as the end of the structure. You might assume from this drawing that 0 to 4 belongs to B1 and 4 to 9 belongs to B2 but apart from 4 and 9 was can’t know which branch belongs to which branch, I’ve simply drawn it in a way that gives the illusion of that. 0 might belong to B2 and 5 might belong to B1. There are 16 different possibilies in this case of which named branch each of the structural twigs could belong to. This is assuming that none of these structural twigs came from a deleted branch or as a result of merging a branch into itself when pulling from master (the same branch name on two repos is infact two twigs, a separate repository is like branching all twigs).

There are a number of git strategies that work around this. You can force git merge to never fast forward and always create a merge branch. A horrible way to preserve branch history is with tags and/or twigs (tags are really recommended) according to some convention of your choosing. I realy wouldn’t recommend a dummy empty commit in the branch you’re merging into. A very common convention is to not merge into an integration branch until you want to genuinely close your branch. This is a practice that people should attempt to adhere to as otherwise you’re working around the point of having twigs. However in the real world the ideal is not always practical meaning doing the right thing is not viable for every situation. If what you’re doing on a branch is isolated that can work but otherwise you might be in a situation where when multiple developers are working one something they need to share their changes quickly (ideally you might really want to be working on one branch but not all situations suit that either and generally two people working on a branch is something you want to avoid).

I seem to be getting some joy with

 git rev-list branch...master 

The last line you get is the first commit on the branch, so then it’s a matter of getting the parent of that. Alors

 git rev-list -1 `git rev-list branch...master | tail -1`^ 

Seems to work for me and doesn’t need diffs and so on (which is helpful as we don’t have that version of diff)

Correction: This doesn’t work if you are on the master branch, but I’m doing this in a script so that’s less of an issue

The problem appears to be to find the most recent, single-commit cut between both twigs on one side, and the earliest common ancestor on the other (probably the initial commit of the repo). This matches my intuition of what the “branching off” point is.

That in mind, this is not at all easy to compute with normal git shell commands, since git rev-list — our most powerful tool — doesn’t let us ressortingct the path by which a commit is reached. The closest we have is git rev-list --boundary , which can give us a set of all the commits that “blocked our way”. (Note: git rev-list --ancestry-path is interesting but I don’t how to make it useful here.)

Here is the script: https://gist.github.com/abortz/d464c88923c520b79e3d . It’s relatively simple, but due to a loop it’s complicated enough to warrant a gist.

Note that most other solutions proposed here can’t possibly work in all situations for a simple reason: git rev-list --first-parent isn’t reliable in linearizing history because there can be merges with either ordering.

git rev-list --topo-order , on the other hand, is very useful — for walking commits in topographic order — but doing diffs is brittle: there are multiple possible topographic orderings for a given graph, so you are depending on a certain stability of the orderings. That said, strongk7’s solution probably works damn well most of the time. However it’s slower that mine as a result of having to walk the entire history of the repo… twice. 🙂

The following implements git equivalent of svn log –stop-on-copy and can also be used to find branch origin.

Approach

  1. Get head for all twigs
  2. collect mergeBase for target branch each other branch
  3. git.log and iterate
  4. Stop at first commit that appears in the mergeBase list

Like all rivers run to the sea, all twigs run to master and therefore we find merge-base between seemingly unrelated twigs. As we walk back from branch head through ancestors, we can stop at the first potential merge base since in theory it should be origin point of this branch.

Remarques

  • I haven’t sortinged this approach where sibling and cousin twigs merged between each other.
  • I know there must be a better solution.

details: https://stackoverflow.com/a/35353202/9950

Not quite a solution to the question but I thought it was worth noting the the approach I use when I have a long-living branch:

At the same time I create the branch, I also create a tag with the same name but with an -init suffix, for example feature-branch and feature-branch-init .

(It is kind of bizarre that this is such a hard question to answer!)

You could use the following command to return the oldest commit in branch_a, which is not reachable from master:

 git rev-list branch_a ^master | tail -1 

Perhaps with an additional sanity check that the parent of that commit is actually reachable from master…

You can examine the reflog of branch A to find from which commit it was created, as well as the full history of which commits that branch pointed to. Reflogs are in .git/logs .

I believe I’ve found a way that deals with all the corner-cases mentioned here:

 branch=branch_A merge=$(git rev-list --min-parents=2 --grep="Merge.*$branch" --all | tail -1) git merge-base $merge^1 $merge^2 

Charles Bailey is quite right that solutions based on the order of ancestors have only limited value; at the end of the day you need some sort of record of “this commit came from branch X”, but such record already exists; by default ‘git merge’ would use a commit message such as “Merge branch ‘branch_A’ into master”, this tells you that all the commits from the second parent (commit^2) came from ‘branch_A’ and was merged to the first parent (commit^1), which is ‘master’.

Armed with this information you can find the first merge of ‘branch_A’ (which is when ‘branch_A’ really came into existence), and find the merge-base, which would be the branch point 🙂

I’ve sortinged with the repositories of Mark Booth and Charles Bailey and the solution works; how couldn’t it? The only way this wouldn’t work is if you have manually changed the default commit message for merges so that the branch information is truly lost.

For usefulness:

 [alias] branch-point = !sh -c 'merge=$(git rev-list --min-parents=2 --grep="Merge.*$1" --all | tail -1) && git merge-base $merge^1 $merge^2' 

Then you can do ‘ git branch-point branch_A ‘.

Enjoy 😉