web-dev-qa-db-fra.com

Existe-t-il une différence entre git rebase et git merge --ff-only

D'après ce que j'ai lu, les deux nous aident à obtenir une histoire linéaire.

D'après ce que j'ai expérimenté, le rebase fonctionne tout le temps. Mais la fusion --ff-only ne fonctionne que dans les scénarios où elle peut être transmise rapidement.

J'ai également remarqué que git merge crée un commit de fusion, mais si nous utilisons --ff-only, il donne un historique linéaire qui est essentiellement égal à git rebasing. Donc --ff-only tue le but de la fusion git, non?

Quelle est donc la véritable différence entre eux?

43
phoenix

Notez que git rebase a un travail différent que git merge (avec ou sans --ff-only). Ce que rebase fait est de prendre les validations existantes et de les copier . Supposons, par exemple, que vous êtes sur branch1 et ont effectué deux commits A et B:

...-o--o--A--B   <-- HEAD=branch1
        \
         o--C    <-- branch2

et vous décidez que vous préférez que ces deux validations soient sur branch2 au lieu. Vous pouvez:

  • obtenir une liste des modifications que vous avez apportées dans A (diff A par rapport à son parent)
  • obtenir une liste des modifications que vous avez apportées dans B (diff B contre A)
  • basculer vers branch2
  • apportez les mêmes modifications que vous avez apportées dans A et validez-les, en copiant votre message de validation depuis A; appelons ce commit A'
  • puis apportez les mêmes modifications que vous avez apportées dans B et validez-les, en copiant votre message de validation depuis B; appelons cela B'.

Il y a une commande git qui fait ce diff-puis-copie-et-commit pour vous: git cherry-pick. Alors:

git checkout branch2      # switch HEAD to branch2 (commit C)
git cherry-pick branch1^  # this copies A to A'
git cherry-pick branch1   # and this copies B

Vous avez maintenant ceci:

...-o--o--A--B         <-- branch1
        \
         o--C--A'-B'   <-- HEAD=branch2

Vous pouvez maintenant revenir à branch1 et supprimez vos A et B originaux, en utilisant git reset (Je vais utiliser --hard ici, c'est plus pratique de cette façon car il nettoie aussi l'arbre de travail):

git checkout branch1
git reset --hard HEAD~2

Cela supprime les A et B d'origine,1 maintenant vous avez:

...-o--o               <-- HEAD=branch1
        \
         o--C--A'-B'   <-- branch2

Il ne vous reste plus qu'à re-check-out branch2 pour continuer à y travailler.

C'est quoi git rebase fait: il "déplace" les validations (mais pas en les déplaçant réellement, car il ne le peut pas: dans git, une validation ne peut jamais être modifiée, donc même le simple changement de l'ID parent nécessite de le copier dans une nouvelle validation légèrement différente ).

En d'autres termes, tandis que git cherry-pick est un diff-and-redo automatisé de un commit, git rebase est un processus automatisé de refaire plusieurs validations, plus, à la fin, de déplacer les étiquettes pour "oublier" ou cacher les originaux.

L'illustration ci-dessus illustre le déplacement des validations à partir d'une branche locale branch1 vers une autre succursale locale branch2, mais git utilise exactement le même processus pour déplacer les validations lorsque vous avez une branche de suivi à distance qui acquiert de nouvelles validations lorsque vous effectuez une git fetch (y compris le fetch qui est la première étape de git pull). Vous pourriez commencer par travailler sur la branche feature, qui a en amont de Origin/feature, et faites vous-même quelques commits:

...-o        <-- Origin/feature
     \
      A--B   <-- HEAD=feature

Mais vous décidez ensuite de voir ce qui s'est passé en amont, vous exécutez donc git fetch,2 et, aha, quelqu'un en amont a écrit un commit C:

...-o--C     <-- Origin/feature
     \
      A--B   <-- HEAD=feature

À ce stade, vous pouvez simplement rebaser vos featureA et B sur C, donnant:

...-o--C     <-- Origin/feature
        \
         A'-B'  <-- HEAD=feature

Ce sont des copies de vos A et B originaux, les originaux étant jetés (mais voir référence 1) une fois les copies terminées.


Parfois, il n'y a rien à rebaser, c'est-à-dire aucun travail que vous avez fait vous-même. Autrement dit, le graphique avant le fetch ressemble à ceci:

...-o      <-- Origin/feature
           `-- HEAD=feature

Si vous alors git fetch et commit C entre, cependant, vous vous retrouvez avec votre feature branche pointant vers l'ancien valider, tandis que Origin/feature a progressé:

...-o--C   <-- Origin/feature
     `---- <-- HEAD=feature

C'est ici que git merge --ff-only entre en jeu: si vous demandez de fusionner votre branche actuelle feature avec Origin/feature, git voit qu'il est possible de faire glisser la flèche vers l'avant, pour ainsi dire, de sorte que feature pointe directement pour valider C. Aucune fusion réelle n'est requise.

Si vous aviez vos propres validations A et B, et que vous demandiez de fusionner celles-ci avec C, git ferait une véritable fusion, créant une nouvelle validation de fusion M:

...-o--C        <-- Origin/feature
     \   `-_
      A--B--M   <-- feature

Ici, --ff-only s'arrêtera et vous donnera une erreur. Rebase, d'autre part, peut copier A et B vers A' et B', puis masquez les A et B originaux.

Donc, en bref (ok, trop tard :-)), ils font simplement des choses différentes. Parfois, le résultat est le même et parfois non. Si vous pouvez copier A et B, vous pouvez utiliser git rebase; mais s'il y a une bonne raison de ne pas les copier, vous pouvez utiliser git merge, peut-être avec --ff-only, pour fusionner ou échouer selon le cas.


1Git conserve les originaux pendant un certain temps - normalement un mois dans ce cas - mais il les cache. Le moyen le plus simple de les trouver est avec les "reflogs" de git, qui gardent un historique de l'endroit où chaque branche pointait, et où HEAD pointait, avant chaque changement qui mettait à jour la branche et/ou HEAD.

Finalement, les entrées d'historique de reflog expirent, auquel cas ces validations deviennent éligibles pour garbage collection .

2Ou, encore une fois, vous pouvez utiliser git pull, qui est un script pratique qui commence par exécuter git fetch. Une fois l'extraction terminée, le script de commodité exécute soit git merge ou git rebase, selon la façon dont vous le configurez et l'exécutez.

86
torek

Oui, il y a une différence. git merge --ff-only s'interrompra s'il ne peut pas avancer rapidement et prend un commit (normalement une branche) pour fusionner. Il ne créera un commit de fusion que s'il ne peut pas avancer rapidement (c'est-à-dire qu'il ne le fera jamais avec --ff-only).

git rebase réécrit l'historique sur la branche actuelle, ou peut être utilisé pour rebaser une branche existante sur une branche existante. Dans ce cas, il ne créera pas de validation de fusion car il s'agit de rebaser plutôt que de fusionner.

5
abligh

Oui, --ff-only échouera toujours là où un simple git merge échouerait et pourrait échouer là où un simple git merge réussirait. C'est le point - si vous essayez de conserver un historique linéaire, et que la fusion ne peut pas être effectuée de cette façon, vous voulez qu'il échoue.

Une option qui ajoute des cas d'échec à une commande n'est pas inutile; c'est un moyen de valider une condition préalable, donc si l'état actuel du système n'est pas celui que vous attendez, vous n'aggravez pas le problème.

3
user2404501