web-dev-qa-db-fra.com

Trouver un point de branche avec Git?

J'ai un référentiel avec les branches master et A et beaucoup d'activités de fusion entre les deux. Comment trouver le commit dans mon référentiel lorsque la branche A a été créée sur master?

Mon référentiel ressemble en gros à 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.

413
Jochen

Je cherchais la même chose et j'ai trouvé cette question. Merci de le demander!

Cependant, j'ai trouvé que les réponses que je vois ici ne semblent pas assez donner la réponse que vous avez demandée (ou que je cherchais) - elles semblent donner la G commit, au lieu de A commit.

J'ai donc créé l'arbre suivant (lettres attribué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, car je voulais m'assurer que j'avais bien (en vous référant à ce graphique, pas au vôtre) B, mais pas à A (et non à D ou E). Voici les lettres attachées aux préfixes SHA et aux messages de validation (mon référentiel peut être cloné à partir de ici , si cela intéresse 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, l'objectif : trouve B . Voici trois façons 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):

gitk screen capture from master

ou ici (vu du sujet):

gitk screen capture from topic

dans les deux cas, j'ai sélectionné le commit qui est B dans mon graphique. Une fois que vous avez cliqué dessus, son entier SHA 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/note de bas de page: l'ajout de --decorate peut également être intéressant. Il ajoute une indication des noms de branche, des balises, etc. Ne pas l'ajouter à la ligne de commande ci-dessus, car la sortie ci-dessous ne reflète pas son utilisation.)

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

output of git log --graph --oneline --all

Ou, en texte droit:

 * a9546a2 fusionner de sujet en arrière vers maître 
 |\
 | * 648ca35 fusion de maître sur le sujet 
 | |\
 | * | 132ee2a premier engagement sur la branche thématique 
 * | | e7c863d commit sur master après que master ait été fusionné dans la rubrique 
 | |/
 |/| 
 * | 37ad159 validation post-branch sur maître 
 |/
 * 6aafd7f seconde validation sur master avant ramification 
 * 4112403 validation initiale sur master 

dans les deux cas, nous considérons que la validation 6aafd7f est le point commun le plus bas, c’est-à-dire B dans mon graphique ou A dans le vôtre.


3. Avec la magie Shell:

Vous ne précisez pas dans votre question si vous voulez quelque chose comme ce qui précède, ou une seule commande qui vous donnera simplement la 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

Ce que vous pouvez également mettre dans votre ~/.gitconfig sous la forme (note: le tiret final est important; merci Brian pour avoir attiré l'attention sur ce point) :

[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 (compliquée avec des citations):

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'\'' -'

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

Remarque: Version alternative de ce qui précède:

Merci à liori pour soulignant que ce qui précède pourrait tomber lors de la comparaison de branches identiques, et proposer une autre forme de diff qui supprime la forme sed du mélange et rend ce "plus sûr" (c'est-à-dire qu'il retourne un résultat (à savoir le dernier commit) même lorsque vous comparez maître à 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é; il est de retour), cela fonctionne maintenant à la fois dans le maître et dans le sujet (donnant les commits G et B, respectivement). Merci encore, liori, pour la forme alternative.


Donc, c'est ce que je [et liori] sommes venus avec. Il semble fonctionner pour moi. Il permet également un couple d'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!

461
lindes

Vous recherchez peut-être git merge-base :

git merge-base trouve le (s) meilleur (s) ancêtre (s) commun (s) 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 précédent. Un ancêtre commun qui n’a pas de meilleur ancêtre commun est un meilleur ancêtre commun , c’est-à-dire une base de fusion . Notez qu'il peut y avoir plus d'une base de fusion pour une paire de validations.

119
Greg Hewgill

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

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

va cracher le point de la branche. Maintenant, ce n'est pas parfait. depuis que vous avez fusionné le maître dans la branche A plusieurs fois, cela divisera un couple possible points de branche (en gros, le point de branche d'origine puis chaque point où vous avez fusionné le maître dans la branche A ). Cependant, il devrait au moins limiter les possibilités.

J'ai ajouté cette commande à mes alias dans ~/.gitconfig en 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
38
mipadi

Si vous aimez les commandes laconiques,

git rev-list $ (git liste-revis - premier-parent ^ nom de branche master | tail -n1) ^^! 

Voici une explication.

La commande suivante vous donne la liste de tous les commits du maître survenus après la création du nom de branche.

git liste-revisite - premier-parent ^ nom de branche maitre 

Puisque vous ne vous souciez que du premier de ces commits, vous voulez la dernière ligne du résultat:

git liste-revisitée ^ branche_nom - premier maître parent | queue -n1

Le parent du premier commit qui n'est pas un ancêtre de "branch_name" est, par définition, in "branch_name" et est dans "master" puisqu'il s'agit de l'ancêtre de quelque chose dans "master". Donc, vous avez le plus tôt commit qui est dans les deux branches.

La commande

git rev-list commit ^^!

est juste un moyen de montrer la référence de commit parent. Vous pourriez utiliser

git log -1 commit ^

ou peu importe.

PS: Je ne suis pas d'accord avec l'argument voulant que l'ordre des ancêtres ne soit pas pertinent. Cela dépend de ce que vous voulez. Par exemple, dans ce cas

 _ C1___C2_______ maître 
\\ _XXXXX_ branche A (les X désignent des croisements arbitraires entre maître et A) 
\_____/branche B 

il est parfaitement logique de générer C2 en tant que validation "de branchement". C'est à ce moment que le développeur a dérivé de "maître". Quand il a ramifié, la branche "B" n'a même pas été fusionnée dans sa branche! C'est ce que donne la solution dans ce post.

Si ce que vous voulez est le dernier commit C tel que tous les chemins d’origine au dernier commit de la branche "A" passent par C, vous voulez alors ignorer l’ordre d’ascendance. C'est purement topologique et vous donne une idée de ce qui se passe depuis 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 renverra C1 dans mon exemple.

28
Lionel

Étant donné que tant de réponses dans ce fil ne donnent pas la réponse demandée par la question, voici un résumé des résultats de chaque solution, ainsi que le script avec lequel j'ai répliqué le référentiel donné dans la question.

Le journal

En créant un dépôt avec la structure donnée, nous obtenons le journal 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 la balise qui le rend explicite sur le point où nous avons créé la branche 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 Charles Bailey l'indique cependant, cette solution est très fragile.

Si vous branch_A dans master, puis fusionnez master dans branch_A sans intervenir, la solution de lindes ne vous donne que le plus récent divergence .

Cela signifie que pour mon flux de travail, je pense que je vais devoir continuer à étiqueter le point de branche de longues branches en cours d'exécution, car je ne peux pas garantir qu'elles puissent être trouvées de manière fiable plus tard.

Tout cela revient vraiment à gitsans le manque de ce que hg appelle des branches nommées . Le blogueur jhw appelle ces familles contre dans son article - Pourquoi j'aime plus Mercurial que Git et son article de suivi Plus sur Mercurial vs. Git (avec des graphiques!) . Je recommanderais aux gens de les lire pour voir pourquoi certains convertis de Mercurial manquent ne pas avoir des branches 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 fournie par Greg Hewgill return I

$ 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 scénario

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 une grande différence, mais:

$ git --version
git version 1.7.1

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

19
Mark Booth

En général, ce n'est pas possible. Dans un historique de branche, une branche-et-fusion avant qu'une branche nommée ne soit ramifiée et qu'une branche intermédiaire de deux branches nommées se ressemblent.

Dans git, les branches ne sont que les noms actuels des astuces des sections de l’histoire. Ils n'ont pas vraiment d'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, en donnant le dernier commet partagé par les deux branches.

Une solution reposant sur l'ordre des parents d'un commit ne fonctionnera évidemment pas dans les cas où une branche a été complètement intégrée à un moment donné de son histoire.

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 échoue également si une fusion d'intégration a été effectuée avec les parents inversés (par exemple, une branche temporaire a été utilisée pour effectuer un test de fusion en maître, puis transférée rapidement dans la branche de fonctionnalité pour poursuivre la construction).

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"
10
CB Bailey

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`^
5
Karl

sûrement il me manque quelque chose, mais OMI, tous les problèmes ci-dessus sont causés parce que nous essayons toujours de trouver le point de branchement remontant dans l’histoire, ce qui 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 branches partagent beaucoup d’histoire, exactement toute l’histoire avant la création de branches étant à 100% identique. Ainsi, au lieu de revenir en arrière, ma proposition est d'aller de l'avant engager), en recherchant la 1ère différence dans les deux branches. Le point de branchement 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 cela résout tous mes cas habituels. Bien sûr, il y a des frontières non couvertes mais ... ciao :-)

5
stronk7

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

4
Reck

Après de nombreuses recherches et discussions, il est clair qu’aucune solution miracle ne fonctionnerait dans toutes les situations, du moins dans la version actuelle de Git.

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

Pour trouver le point de branche de la branche de développement, tout ce que vous avez à faire est d'utiliser devel@{tail}, c'est tout.

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

3
FelipeC

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 rechercher le lieu où la branche a été créée.

Cela fonctionne sur tous les référentiels mentionnés ici, et j'ai même abordé quelques problèmes difficiles apparaissant 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
}
3
FelipeC

Pour rechercher des commits à partir du point de branchement, vous pouvez utiliser ceci.

git log --ancestry-path master..topicbranch
2
Andor

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

Git ne conserve pas l'historique des références (ce qui inclut les branches). Il ne stocke que la position actuelle pour chaque branche (la tête). Cela signifie que vous pouvez perdre un peu d’historique de branche dans git au fil du temps. Chaque fois que vous branchez, par exemple, il est immédiatement perdu quelle branche était la branche d'origine. Tout ce qu'une branche fait, c'est:

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

Vous pouvez supposer que le premier commité à est la branche. 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 branche après l'opération ci-dessus. De plus, la fiabilité des horodatages git n'est pas garantie. Ce n'est que lorsque vous vous engagez envers les deux qu'ils deviennent véritablement des branches structurelles.

Tandis que dans les diagrammes, nous avons tendance à numéroter conceptuellement les commits, git n'a pas de concept réellement stable de séquence lorsque l'arbre de validation se branche. Dans ce cas, vous pouvez supposer que les nombres (en 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 manière).

C’est ce qu’attend conceptuellement un humain:

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

Voici 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 supposeriez que B1 est la branche d'origine, mais il pourrait s'agir simplement d'une branche morte (quelqu'un a effectué le paiement -b mais ne s'y est jamais engagé). Ce n'est que lorsque vous vous engagez envers les deux que vous obtenez une structure de branche légitime au sein de git:

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

Vous savez toujours que C1 est venu avant C2 et C3, mais vous ne savez jamais de manière fiable si C2 est venu avant C3 ou C3 avant C2 (car vous pouvez régler l'heure sur votre poste de travail par exemple). B1 et B2 sont également trompeurs car vous ne pouvez pas savoir quelle branche est arrivée en premier. Vous pouvez faire une supposition très bonne et généralement exacte dans de nombreux cas. C'est un peu comme une piste de course. Toutes choses étant généralement égales avec les voitures, vous pouvez donc supposer qu'une voiture arrivant dans un tour derrière commence un tour derrière. Nous avons également des conventions très fiables, par exemple, maître représentera presque toujours les branches les plus anciennes, bien que j’ai malheureusement vu des cas où même ce n’est pas le cas.

L'exemple donné ici est un exemple préservant 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 parce que nous, les humains, le lisons de gauche à droite, de la racine aux feuilles (réf). Git ne fait pas ça. Où nous faisons (A-> B) dans nos têtes git fait (A <-B ou B-> A). Il le lit de la référence à la racine. Les arbitres peuvent être n'importe où mais ont tendance à être des feuilles, du moins pour les branches actives. Une référence pointe vers un commit et les commits ne contiennent qu'un semblable à leurs parents, pas à leurs enfants. Lorsqu'un commit est un commit de fusion, il aura plus d'un parent. Le premier parent est toujours le commit original 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 emprunter à partir de chaque ref (B1 et B2).

La mémoire interne de Git ressemble plus à ceci (pas que A apparaisse deux fois comme parent):

    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 ne verrez plus aucun champ parent. S'il existe zéro, cela signifie qu'aucun parent et que la validation est une racine (vous pouvez en réalité avoir plusieurs racines). S'il en existe un, cela signifie qu'il n'y a pas eu de fusion et que ce n'est pas un commit root. 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 commits 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 cela, leur chaîne sera entièrement différente. Le premier commet deux autres commits ont en commun est l'ancêtre commun et d'où ils ont divergé. il pourrait y avoir une certaine confusion ici entre les termes commit, branch et ref. Vous pouvez en fait fusionner un commit. C'est ce que fait vraiment la fusion. Une référence pointe simplement sur un commit et une branche 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 référence est une branche plutôt que quelque chose comme une balise.

Si vous perdez l’histoire, c’est que la fusion fera l’une des deux choses suivantes, selon les circonstances.

Considérer:

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

Dans ce cas, une fusion dans l'un ou l'autre sens créera un nouveau commit avec le premier parent en tant que validation désignée par la branche extraite actuelle et le second parent en tant que commit à l'extrémité de la branche que vous avez fusionnée dans votre branche actuelle. Il doit créer un nouveau commit car les deux branches ont des modifications depuis leur ancêtre commun qui doivent être combinées.

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

À ce stade, D (B1) contient maintenant les deux ensembles de modifications des deux branches (lui-même et B2). Cependant, la deuxième branche n'a pas les changements de B1. Si vous fusionnez les modifications de B1 dans B2 afin qu'elles soient synchronisées, vous pouvez vous attendre à quelque chose qui ressemble à ceci (vous pouvez forcer la fusion avec git à le faire comme ceci, cependant 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 changements dans B2 que B1 n'a pas, les deux branches 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 branche a un ensemble de modifications, il n'est pas nécessaire d'appliquer un jeu de modifications d'une branche à une autre.

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 sont généralement bien pour préserver l’histoire à long terme. Seul B1 (qui peut être maître) avance normalement, de sorte 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 attend de vous, pour séparer B de B, vous pouvez alors fusionner A autant que vous le souhaitez au fur et à mesure que les changements s’accumulent. . Si vous continuez à travailler sur votre branche après l'avoir rapidement fusionnée dans la branche sur laquelle vous travailliez, effacez à chaque fois l'historique précédent de B. Vous créez réellement une nouvelle branche à chaque fois après une validation rapide vers une source, puis une validation pour une branche. Vous vous retrouvez avec une multitude de branches/fusions que vous pouvez voir dans l'historique et la structure, mais sans la possibilité de déterminer le nom de cette branche ou si ce qui ressemble à deux branches distinctes est vraiment la même branche .

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

1 à 3 et 5 à 8 sont des branches structurelles qui apparaissent si vous suivez l’historique de 4 ou 9. Il n’ya aucun moyen de savoir à quelle branche de cette structure appartient, avec les branches nommées et référencées, fin de la structure. Vous pouvez supposer, de ce dessin, que 0 à 4 appartient à B1 et 4 à 9 à B2 mais à part 4 et 9, je ne peux pas savoir quelle branche appartient à quelle branche, je l’ai simplement dessinée de manière à illusion de cela. 0 pourrait appartenir à B2 et 5 pourrait appartenir à B1. Dans ce cas, il existe 16 possibilités différentes auxquelles chaque branche structurelle pourrait appartenir. En supposant qu'aucune de ces branches structurelles ne soit issue d'une branche supprimée ou résultant de la fusion d'une branche en elle-même lors de l'extraction à partir du maître (le même nom de branche sur deux pensions correspond à deux branches, un référentiel distinct revient à ramifier toutes les branches) .

Il existe un certain nombre de stratégies git pour résoudre ce problème. Vous pouvez forcer la fusion à ne jamais effectuer d'avance rapide et à toujours créer une branche de fusion. Une façon horrible de préserver l'historique des branches est d'utiliser des balises et/ou des branches (les balises sont vraiment recommandées) selon une convention de votre choix. Je ne recommanderais vraiment pas un commit factice vide dans la branche dans laquelle vous êtes en train de fusionner. Une convention très courante consiste à ne pas fusionner dans une branche d'intégration tant que vous ne voulez pas réellement fermer votre branche. C'est une pratique à laquelle les gens doivent essayer de se conformer, sinon vous travaillez au point d'avoir des branches. Cependant, dans le monde réel, l'idéal n'est pas toujours pratique, ce qui signifie que faire la bonne chose n'est pas viable dans toutes les situations. Si ce que vous faites sur une branche est isolé, cela peut fonctionner, mais vous risquez sinon de vous retrouver dans une situation où plusieurs développeurs travaillent sur un élément dont ils ont besoin pour partager rapidement leurs modifications (idéalement, vous souhaiterez peut-être vraiment travailler sur une branche mais toutes les situations ne conviennent pas non plus et généralement, deux personnes travaillant dans une branche sont quelque chose que vous souhaitez éviter).

2
jgmjgm

Ce n’est pas tout à fait une solution à la question, mais j’ai pensé que cela valait la peine de noter l’approche que j’utilise lorsque j’ai une branche qui a une longue vie:

En même temps que je crée la branche, je crée aussi une balise du même nom mais avec un suffixe -init, par exemple feature-branch et feature-branch-init.

(C'est un peu bizarre que ce soit une question difficile à répondre!)

2
Stephen Darlington

La commande suivante révélera le SHA1 de la validation A

git merge-base --fork-point A

2
Gayan Pathirage

Je semble avoir de la joie avec

git rev-list branch...master

La dernière ligne que vous obtenez est le premier commit de la branche, il est donc nécessaire d’en obtenir le parent. Alors

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

Cela semble fonctionner pour moi et n'a pas besoin de diffs, etc. (ce qui est utile car nous n'avons pas cette version de diff)

Correction: cela ne fonctionne pas si vous êtes sur la branche principale, mais je le fais dans un script, donc c'est moins un problème

2
Tom Tanner

Ce qui suit implémente l'équivalent git de svn log --stop-on-copy et peut également être utilisé pour trouver l'origine de la branche.

Approche

  1. Obtenez la tête pour toutes les branches
  2. collecter mergeBase pour la branche cible de chaque branche
  3. git.log et itérer
  4. Arrêtez-vous au premier commit qui apparaît dans la liste mergeBase

Comme toutes les rivières qui se jettent dans la mer, toutes les branches se maîtrisent et nous trouvons donc une base de fusion entre des branches apparemment sans lien. En revenant de tête de branche en passant par les ancêtres, nous pouvons nous arrêter à la première base de fusion potentielle car, en théorie, cela devrait être le point d'origine de cette branche.

Notes

  • Je n'ai pas essayé cette approche où des branches frères et cousins ​​se confondaient.
  • Je sais qu'il doit y avoir une meilleure solution.

détails: https://stackoverflow.com/a/35353202/995

0
Peter Kahn

Le problème semble être de trouver la coupe la plus récente (* = commit) à validation unique entre les deux branches d’un côté et le ) le plus ancien ancêtre commun de l'autre (probablement le commit initial du repo). Cela correspond à mon intuition de ce qu'est le point de "ramification".

Ceci dit, ce n’est pas facile à calculer avec les commandes normales de git Shell, car git rev-list - notre outil le plus puissant - ne nous permet pas de restreindre le chemin par qui un commit est atteint. Le plus proche que nous ayons est git rev-list --boundary, ce qui peut nous donner un ensemble de tous les commits qui "bloquaient notre chemin". (Remarque: git rev-list --ancestry-path est intéressant mais je ne sais pas comment le rendre utile ici.)

Voici le script: https://Gist.github.com/abortz/d464c88923c520b79e3d . C'est relativement simple, mais à cause d'une boucle, c'est assez compliqué pour justifier un Gist.

Notez que la plupart des autres solutions proposées ici ne peuvent pas fonctionner dans toutes les situations pour une raison simple: git rev-list --first-parent n'est pas fiable dans l'historique de linéarisation car il peut y avoir des fusions avec l'un ou l'autre des ordres.

git rev-list --topo-order, d’autre part, est très utile - pour les commits dans l’ordre topographique - mais les différences sont fragiles: il y en a plusieurs ordres topographiques possibles pour un graphique donné, de sorte que vous dépendez d'une certaine stabilité des ordres. Cela dit, la solution de strongk7 fonctionne probablement très bien la plupart du temps. Cependant, il est plus lent que le mien car il doit parcourir l’ensemble de l’histoire de la prise en pension ... deux fois. :-)

0
Andrew Bortz