Parfois, c'est effectivement impossible (à quelques exceptions près où vous pourriez avoir la chance d'avoir des données supplémentaires) et les solutions ici ne fonctionneront pas.
Git ne conserve pas l'historique des références (qui inclut les branches). Il stocke uniquement la position actuelle de 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, vous perdez immédiatement la branche d'origine. Une succursale ne fait que:
git checkout branch1 # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1
Vous pouvez supposer que le premier engagé est la branche. Cela a tendance à ê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 en premier 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 à la fois qu'ils deviennent vraiment des branches structurellement.
Alors que dans les diagrammes, nous avons tendance à numéroter les validations de manière conceptuelle, git n'a pas de véritable concept stable de séquence lorsque l'arbre de validation se branche. 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 manière).
C'est ce qu'un humain attend conceptuellement:
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 soit la branche d'origine, mais cela pourrait en fait être simplement une branche morte (quelqu'un a vérifié -b mais ne s'y est jamais engagé). Ce n'est que lorsque vous vous engagez à la fois que vous obtenez une structure de branche légitime dans 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 est venu avant C2 (parce que vous pouvez régler l'heure sur votre poste de travail sur n'importe quoi par exemple). B1 et B2 sont également trompeurs car vous ne pouvez pas savoir quelle branche est arrivée en premier. Dans bien des cas, vous pouvez faire une très bonne estimation, généralement précise. C'est un peu comme une piste de course. Toutes choses étant généralement égales aux voitures, vous pouvez supposer qu'une voiture qui arrive dans un tour derrière a commencé un tour derrière. Nous avons également des conventions qui sont très fiables, par exemple, master 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)
Le vrai ici est également trompeur parce que nous, les humains, le lisons de gauche à droite, de la racine à la feuille (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 ref à root. Les références peuvent être n'importe où mais ont tendance à être des feuilles, au moins pour les branches actives. Une référence pointe vers un commit et les commits ne contiennent qu'un like pour leurs parents, pas pour leurs enfants. Lorsqu'un commit est un commit de fusion, il aura plusieurs parents. Le premier parent est toujours le commit d'origine qui a été fusionné. Les autres parents sont toujours des validations qui ont été fusionnées dans la validation 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 à chaque référence (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 sauvegardez un commit git brut, vous verrez zéro ou plusieurs champs parents. S'il y a zéro, cela signifie qu'il n'y a pas de parent et que la validation est une racine (vous pouvez en fait avoir plusieurs racines). S'il y en a 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 plusieurs, cela signifie que la validation est le résultat d'une fusion et que tous les parents après la première 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 cela, leur chaîne sera entièrement 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 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 vers un commit et une branche n'est rien d'autre 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 d'autre 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 un nouveau commit avec le premier parent comme commit pointé par la branche extraite actuelle et le second parent comme commit à l'extrémité de la branche que vous avez fusionné dans votre branche actuelle. Il doit créer un nouveau commit car les deux branches ont des changements depuis leur ancêtre commun qui doivent être combinés.
/ - B - D (B1)
- A /
\ --- C (B2)
À ce stade, D (B1) a maintenant les deux ensembles de changements 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 à B2 afin qu'elles soient synchronisées, vous pouvez vous attendre à quelque chose qui ressemble à ceci (vous pouvez cependant forcer git merge à le faire comme ceci 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 aura pas de changements dans B2 que B1 n'a pas, les deux branches seront fusionnées. Il fait une avance rapide qui ressemble à un rebase (les rebases mangent ou linéarisent également l'historique), sauf contrairement à un rebase car une seule branche a un ensemble de changements, il n'est pas nécessaire d'appliquer un ensemble de changements d'une branche en plus de celui d'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, alors les choses sont très bien pour préserver l'histoire à long terme. Seul B1 (qui pourrait être maître) avancera généralement, donc l'emplacement de B2 dans l'histoire de B2 représente avec succès le point de fusion avec B1. C'est ce que git attend de vous, pour dériver B de A, alors vous pouvez fusionner A en B autant que vous le souhaitez à mesure que les modifications s'accumulent, mais lors de la fusion de B en A, il n'est pas prévu que vous travailliez sur B et plus loin . Si vous continuez à travailler sur votre branche après l'avoir rapidement fusionnée dans la branche sur laquelle vous travailliez, puis effacez l'historique précédent de B à chaque fois. Vous créez vraiment une nouvelle branche à chaque fois après une avance rapide sur la source, puis sur la branche.
0 1 2 3 4 (B1)
/-\ /-\ /-\ /-\ /
---- - - - -
\-/ \-/ \-/ \-/ \
5 6 7 8 9 (B2)
1 à 3 et 5 à 8 sont des branches structurelles qui s'affichent si vous suivez l'historique pour 4 ou 9. Il n'y a aucun moyen de savoir à laquelle de ces branches structurelles non nommées et non référencées appartiennent les branches nommées et références comme fin de la structure. Vous pourriez supposer à partir de ce dessin que 0 à 4 appartient à B1 et 4 à 9 appartient à B2 mais à part 4 et 9 ne pouvait pas savoir quelle branche appartient à quelle branche, je l'ai simplement dessinée d'une manière qui donne la illusion de cela. 0 pourrait appartenir à B2 et 5 pourrait appartenir à B1. Il y a 16 possibilités différentes dans ce cas dont la branche nommée à laquelle chacune des branches structurelles pourrait appartenir.
Il existe un certain nombre de stratégies Git qui fonctionnent autour de cela. Vous pouvez forcer git merge à ne jamais avancer rapidement et toujours créer une branche de fusion. Une horrible façon 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 vide factice dans la branche dans laquelle vous fusionnez. Une convention très courante consiste à ne pas fusionner dans une branche d'intégration tant que vous ne voulez pas vraiment fermer votre branche. C'est une pratique que les gens devraient essayer d'adhérer, sinon vous travaillez sur le point d'avoir des succursales. 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 '