Est-il une différence entre git rebase et git merge --ff-seulement

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é, rebase fonctionne tout le temps. Mais la fusion -- ff-ne fonctionne que dans les scénarios où elle peut être transmise rapidement.

j'ai aussi remarqué que git merge crée une commit merge, mais si on utilise --ff-only, cela donne une histoire linéaire qui est essentiellement égale à git rebasing. Donc ... ça ne tue que le but de la fusion de git, pas vrai?

alors quelle est la différence réelle entre eux?

22
demandé sur phoenix 2015-01-25 22:20:15

3 réponses

Notez que git rebase a unemploigit merge (avec ou sans --ff-only). rebase n'est à prendre s'engage et copier. Supposons, par exemple, que vous êtes sur branch1 et ont fait deux commits A et B:

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

et que vous décidez que vous préférez avoir ces deux commits sur branch2 à la place. Vous pouvez:

  • obtenir une liste des modifications que vous avez apportées dans A (diff A contre son parent)
  • obtenir une liste des modifications que vous avez apportées dans B(diff B contre A)
  • passez à branch2
  • faites les mêmes changements que vous avez fait dans A et valider, la copie de votre message de validation de A; appelons ce commit A'
  • et ensuite faire les mêmes modifications que vous avez apportées dans B et valider, la copie de votre message de validation de B; appelons cela B'.

Il y a une commande git que cette diff-et-puis-copie-et-de s'engager pour vous: git cherry-pick. Donc:

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

Maintenant, vous avez ceci:

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

Maintenant, vous pouvez revenir à branch1 et supprimez votre original A et B, en utilisant git reset (je vais utiliser --hard ici, c'est plus pratique que comme il nettoie le travail-arbre):

git checkout branch1
git reset --hard HEAD~2

ceci supprime l'original A et B,1 alors maintenant, vous avez:

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

Maintenant, vous avez juste besoin de re-check-out branch2 pour continuer à y travailler.

c'est Ce que git rebase fait: il" déplace " les commits (mais pas en les déplaçant, car il ne peut pas: en git, une commit ne peut jamais être changée, donc même changer le parent-ID nécessite de le copier en une nouvelle commit légèrement différente).

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

ci-dessus illustre le déplacement s'engage à partir d'une branche locale branch1 pour une autre branche locale branch2, mais git utilise le exactement le même processus pour aller s'engage lorsque vous avez une distance de suivi de branche qui acquiert des nouveaux commits, quand tu fais un git fetch (y compris les fetch c'est la première étape de git pull). Vous pourriez commencer par travailler sur la branchefeature, qui a un amont de origin/feature, et de faire une couple de validations de votre propre:

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

mais ensuite vous décidez que vous devriez voir ce qui s'est passé en amont, donc vous courez git fetch,2 et, aha, quelqu'un en amont a écrit un commit C:

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

a ce point vous pouvez simplement rebaser votre featureA et B sur C, donner:

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

ce sont des copies de votre original A et B, les originaux étant jetés (mais Voir note 1 de bas de page) après que les copies sont complètes.


parfois, il n'y a rien à reformater, c.-à-d. pas de travail que vous avez fait vous-même. Qui est, le graphe avant le fetch ressembler à ceci:

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

Si vous git fetch et commit C entre, cependant, il ne te reste que feature branche pointant vers le vieux commit, alors que origin/feature a progressé:

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

C'est là git merge --ff-only: si vous demandez à fusionner votre branche courante featureorigin/feature, git voit qu'il est possible de faire glisser la flèche vers l'avant, pour ainsi dire, de sorte que feature points directement à commettre C. Pas de fusion est nécessaire.

Si vous aviez votre propre commet A et B, cependant, et vous a demandé de faire fusionner ces C, git ferait un réel, de fusion, d'en faire un nouveau commit de fusion M:

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

Ici --ff-only arrêter et vous donnera une erreur. Rebase, d'un autre côté, peut copier A et BA' et B' et puis cacher le original A et B.

donc, en bref (ok, trop tard : -)), ils font simplement des choses différentes. Parfois, le résultat est le même, et parfois il ne l'est pas. Si c'est OK pour copier A et B, vous pouvez utiliser git rebase; mais si il y a quelques bonnes raisons copier, vous pouvez utiliser git merge peut-être --ff-only, de fusionner ou de l'échec tant que de besoin.


1git conserve les originaux pendant un certain temps-normalement un mois dans ce cas-ci-mais les Cache. La façon la plus facile de les trouver est avec les "reflogs" de git, qui gardent une histoire d'où chaque branche pointait, et où HEAD pointed, avant chaque changement qui a mis à jour la branche et / ou HEAD.

éventuellement les entrées de l'historique reflog expirent, à quel moment ces propagations deviennent éligibles pour collecte des ordures.

2Ou, encore, vous pouvez utiliser git pull, qui est un script de commodité qui commence pargit fetch. Une fois que le fetch est fait, le script de commodité tourne soit git merge ou git rebase, dépend de la façon dont vous le configurez et l'exécutez.

54
répondu torek 2015-01-25 23:35:21

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

git rebase réécrit l'histoire de la branche courante, ou peut être utilisé pour rebaser une branche sur une branche. Dans ce cas, il ne créera pas de commit de fusion parce qu'il rebase, plutôt que de fusionner.

3
répondu abligh 2015-01-25 19:24:51

Oui, --ff-only échouera toujours où une plaine git merge échouerait, et pourrait échouer où une simple git merge serait un succès. C'est le point - si vous essayez de garder une histoire linéaire, et la fusion ne peut pas être fait de cette façon, vous voulez échec.

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

2
répondu Wumpus Q. Wumbley 2015-01-25 19:28:39