Git cherry pick vs rebase

120

J'ai récemment commencé à travailler avec Git.

En parcourant le livre Git en ligne, j'ai trouvé ce qui suit dans la section "Git Rebase":

Avec la commande rebase, vous pouvez prendre toutes les modifications qui ont été validées sur une branche et les rejouer sur une autre.

(Extrait de: http://git-scm.com/book/en/Git-Branching-Rebasing )

Je pensais que c'était la définition exacte de git cherry-pick (réappliquer un commit ou un ensemble d'objets de commit sur la branche actuellement extraite).

Quelle est la différence entre les deux ?

acide lysergique
la source

Réponses:

166

Depuis que le temps a git cherry-pickappris à pouvoir appliquer plusieurs commits, la distinction est en effet devenue quelque peu théorique, mais c'est quelque chose qu'on appelle une évolution convergente ;-)

La véritable distinction réside dans l'intention initiale de créer les deux outils:

  • git rebaseLa tâche du développeur est de transférer une série de changements qu'un développeur a dans son référentiel privé, créés contre la version X d'une branche amont, vers la version Y de cette même branche (Y> X). Cela change effectivement la base de cette série de commits, d'où le «rebasage».

    (Cela permet également au développeur de transplanter une série de commits sur n'importe quel commit arbitraire, mais c'est d'une utilité moins évidente.)

  • git cherry-pickest pour amener un commit intéressant d'une ligne de développement à une autre. Un exemple classique est le rétroportage d'un correctif de sécurité effectué sur une branche de développement instable vers une branche stable (maintenance), où a mergen'a aucun sens, car cela entraînerait de nombreux changements indésirables.

    Depuis sa première apparition, git cherry-picka pu choisir plusieurs commits à la fois, un par un.

Par conséquent, la différence la plus frappante entre ces deux commandes est peut-être la manière dont elles traitent la branche sur laquelle elles travaillent: git cherry-pickapporte généralement un commit d'un autre endroit et l'applique au-dessus de votre branche actuelle, enregistrant un nouveau commit, tandis que git rebaseprend votre branche actuelle et réécrit une série de son propre tip s'engage d'une manière ou d'une autre. Oui, c'est une description très stupide de ce qui git rebasepeut faire, mais c'est intentionnel, d'essayer de faire pénétrer l'idée générale.

Mettre à jour pour expliquer plus en détail un exemple d'utilisation en git rebasecours de discussion.

Compte tenu de cette situation,
un état du repo avant le rebasage
The Book déclare:

Cependant, il existe un autre moyen: vous pouvez prendre le patch du changement qui a été introduit dans C3 et le réappliquer par-dessus C4. Dans Git, cela s'appelle le rebasage. Avec la commande rebase, vous pouvez prendre toutes les modifications qui ont été validées sur une branche et les appliquer sur une autre.

Dans cet exemple, vous exécuteriez ce qui suit:

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

"Le hic" ici est que dans cet exemple, la branche "expérimentation" (le sujet du rebasage) a été à l'origine bifurquée de la branche "maître", et par conséquent, elle partage les commits C0 à C2 avec elle - effectivement, "expérience" est " master "jusqu'à, et y compris, C2 plus engager C3 par-dessus. (C'est le cas le plus simple possible; bien sûr, "experiment" pourrait contenir plusieurs dizaines de commits en plus de sa base d'origine.)

Il git rebaseest maintenant dit de rebaser "experiment" sur la pointe actuelle de "master", et git rebaseva comme ceci:

  1. S'exécute git merge-basepour voir quel est le dernier commit partagé à la fois par "experiment" et "master" (quel est le point de diversion, en d'autres termes). C'est C2.
  2. Enregistre tous les commits effectués depuis le point de diversion; dans notre exemple de jouet, c'est juste C3.
  3. Rembobine le HEAD (qui pointe vers le commit tip de "experiment" avant que l'opération ne commence à s'exécuter) pour pointer vers le bout de "master" - nous rebasons dessus.
  4. Tente d'appliquer chacun des commits enregistrés (comme avec git apply) dans l'ordre. Dans notre exemple de jouet, il ne s'agit que d'un seul commit, C3. Disons que son application produira un commit C3 '.
  5. Si tout s'est bien passé, la référence "expérience" est mise à jour pour pointer vers le commit résultant de l'application du dernier commit enregistré (C3 'dans notre cas).

Revenons maintenant à votre question. Comme vous pouvez le voir, ici, techniquement, en git rebase effet, transplante une série de commits de "experiment" à la pointe de "master", donc vous pouvez à juste titre dire qu'il y a effectivement "une autre branche" dans le processus. Mais l'essentiel est que le commit tip de "experiment" a fini par être le nouveau commit tip dans "experiment", il a juste changé sa base:
état après la fusion

Encore une fois, techniquement, vous pouvez dire git rebasequ'ici incorporé certains commits de "master", et c'est tout à fait correct.

Kostix
la source
2
Merci. Je n'ai toujours pas bien compris ce que vous entendez ici. Dans le livre, l'exemple est donné que rebase applique une série de tip commits d'une autre branche, alors que vous dites que c'est de "la même branche". Ou peut-être y a-t-il quelques cas de comment cela fonctionne?
acide lysergique
1
J'ai essayé d'expliquer le problème en mettant à jour ma réponse.
kostix
98

Avec cherry-pick, les commits / branches d'origine restent et de nouveaux commits sont créés. Avec rebase, la branche entière est déplacée avec la branche pointant vers les commits rejoués.

Disons que vous avez commencé avec:

      A---B---C topic
     /
D---E---F---G master

Rebase:

$ git rebase master topic

Vous obtenez:

              A'--B'--C' topic
             /
D---E---F---G master

Cueillette de cerises:

$ git checkout master -b topic_new
$ git cherry-pick A^..C

Vous obtenez:

      A---B---C topic
     /
D---E---F---G master
             \
              A'--B'--C' topic_new

pour plus d'informations sur git, ce livre en a la plupart (http://git-scm.com/book)

Kenny Ho
la source
3
Bien répondu. Il est également courant que vous souhaitiez sélectionner uniquement les validations A et B, mais laisser C en suspens dans ces cas, vous souhaiterez peut-être conserver la branche et sélectionner simplement les modifications que vos collègues pourraient avoir besoin de voir. Git est conçu pour fonctionner avec les gens, donc si vous ne voyez pas les avantages de quelque chose lorsque vous travaillez seul, il est souvent plus couramment utilisé lorsque vous travaillez dans des groupes plus importants.
Pablo Jomer
Si un rebase interactif était effectué à la place, en omettant un ou plusieurs commits, quelles branches auriez-vous à la fin? s'il était seulement topicrebasé par-dessus master, il ne contient pas les commits laissés de côté, alors de quelle branche feront-ils partie?
Anthony
Encore une chose que je veux ajouter: si vous git checkout topicet ensuite git reset --hard C'après la cueillette des cerises, vous obtenez le même résultat qu'après le rebasage. Je me suis sauvé de beaucoup de conflits de fusion en utilisant la sélection de cerises plutôt que le rebasage, car l'ancêtre commun était très ancien.
sorrymissjackson
@anthony - stackoverflow.com/questions/11835948/… : pour autant que je sache, ils sont perdus. Je ne suis pas git-guru mais ceci rebase/ cherry-pickest sur tous les détails avec gitque j'avais un problème de compréhension.
thoni56
1
Vos graphiques font plus de mal que de bien, car ils sont fonctionnellement identiques. La seule différence est la branche créée par git checkout -b, qui n'a rien à voir avec git cherry-pick. Une meilleure façon d'expliquer ce que vous essayez de dire serait «vous courez git rebasesur la topicbranche et la transmettez master; vous exécutez git cherry-picksur la masterbranche et la transmettez (commits à partir de) topic. »
Rory O'Kane
14

La sélection des cerises fonctionne pour les commits individuels .

Lorsque vous rebasez, il applique toutes les validations de l'historique au HEAD de la branche qui y manque.

iltempo
la source
Merci. Savez-vous si ceux-ci fonctionnent de la même manière sous les couvertures? (stocker leurs sorties intermédiaires dans des fichiers «patch», etc.).
acide lysergique
Afaik oui. Il applique tous les correctifs un par un. C'est la raison pour laquelle vous devez parfois résoudre des conflits de fusion au milieu d'un rebase avant de continuer.
iltempo
6
@iltempo, cela fonctionnait pour les commits individuels uniquement dans les anciennes versions de Git; à l'heure actuelle, vous pouvez faire quelque chose comme git cherry-pick foo~3..fooet obtenir les commits de la cime de l'arbre de "foo" choisis un par un.
kostix
1
git-rebase utilise la même api que le cherry-picking dans la base de code, iirc
alternative
Je ne pense pas qu'ils fonctionnent réellement de la même manière sous les couvertures. J'ai essayé de rebaser des milliers de commits et je pense que git crée un énorme fichier de boîte aux lettres, puis s'exécute git amdessus. Alors qu'un choix cerise applique la validation par validation (éventuellement en créant une boîte aux lettres à message unique pour chaque patch). Mon rebase échouait car le fichier de boîte aux lettres qu'il créait manquait d'espace sur le lecteur, mais la sélection avec la même plage de révision a réussi (et semble fonctionner plus rapidement).
seulement
11

Une réponse courte:

  • git cherry-pick est plus "bas niveau"
  • En tant que tel, il peut émuler git rebase

Les réponses données ci-dessus sont bonnes, je voulais juste donner un exemple pour tenter de démontrer leur interrelation.

Il n'est pas recommandé de remplacer "git rebase" par cette séquence d'actions, c'est juste "une preuve de concept" qui, je l'espère, aide à comprendre comment les choses fonctionnent.

Compte tenu du référentiel de jouets suivant:

$ git log --graph --decorate --all --oneline
* 558be99 (test_branch_1) Test commit #7
* 21883bb Test commit #6
| * 7254931 (HEAD -> master) Test commit #5
| * 79fd6cb Test commit #4
| * 48c9b78 Test commit #3
| * da8a50f Test commit #2
|/
* f2fa606 Test commit #1

Disons que nous avons des changements très importants (commits # 2 à # 5) dans master que nous voulons inclure dans notre test_branch_1. Habituellement, nous passons simplement à une branche et faisons "git rebase master". Mais comme nous prétendons être équipés uniquement de "git cherry-pick", nous faisons:

$ git checkout 7254931                # Switch to master (7254931 <-- master <-- HEAD)
$ git cherry-pick 21883bb^..558be99   # Apply a range of commits (first commit is included, hence "^")    

Après toutes ces opérations, notre graphe de validation ressemblera à ceci:

* dd0d3b4 (HEAD) Test commit #7
* 8ccc132 Test commit #6
* 7254931 (master) Test commit #5
* 79fd6cb Test commit #4
* 48c9b78 Test commit #3
* da8a50f Test commit #2
| * 558be99 (test_branch_1) Test commit #7
| * 21883bb Test commit #6
|/
* f2fa606 Test commit #1

Comme nous pouvons le voir, les commits # 6 et # 7 ont été appliqués à 7254931 (un commit tip de master). HEAD a été déplacé et pointe un commit qui est, essentiellement, une pointe d'une branche rebasée. Il ne nous reste plus qu'à supprimer un ancien pointeur de branche et en créer un nouveau:

$ git branch -D test_branch_1
$ git checkout -b test_branch_1 dd0d3b4

test_branch_1 est maintenant enraciné à partir de la dernière position de maître. Terminé!

raiks
la source
Mais rebase peut également simuler git cherry-pick?
Numéro945 le
Depuis cherry-pickest capable d'appliquer une gamme de commits, je pense que oui. Bien que ce soit une manière un peu étrange de faire les choses, rien ne vous empêche de sélectionner tous les commits dans votre branche de fonctionnalité par-dessus master, puis de supprimer la branche de fonctionnalité et de la recréer de manière à ce qu'elle pointe vers la pointe de master. Vous pouvez penser git rebaseà une séquence de git cherry-pick feature_branch, git branch -d feature_branchet git branch feature_branch master.
raiks
7

Ce sont toutes les deux des commandes pour réécrire les commits d'une branche sur une autre: la différence est dans quelle branche - "la vôtre" (actuellement extraite HEAD) ou "la leur" (la branche passée en argument à la commande) - est la base de cette réécriture.

git rebaseprend un commit de départ et rejoue vos commits comme venant après le leur (le commit de départ).

git cherry-pickprend un ensemble de commits et rejoue leurs commits comme venant après le vôtre (votre HEAD).

En d'autres termes, les deux commandes sont, dans leur comportement de base (ignorant leurs caractéristiques de performance divergentes, leurs conventions d'appel et leurs options d'amélioration), symétriques : l'extraction de la branche baret l'exécution git rebase foodéfinit la barbranche sur le même historique que l'extraction de la branche fooet l'exécution git cherry-pick ..bardéfinirait fooà (les changements de foo, suivis des changements de bar).

En ce qui concerne le nom, la différence entre les deux commandes peut être mémorisée en ce que chacune décrit ce qu'elle fait à la branche actuelle : rebasefait de l'autre tête la nouvelle base de vos modifications, tandis que cherry-pickchoisit les modifications de l'autre branche et les place au-dessus de votreHEAD (comme des cerises sur un sundae).

Stuart P. Bentley
la source
1
Je n'ai compris aucune des réponses sauf la vôtre! C'est concis et parfaitement logique sans formulation superflue.
neoxic
4

Les deux font des choses très similaires; la principale différence conceptuelle est (en termes simplifiés) que:

  • rebase déplace les commits de la branche actuelle vers une autre branche .

  • des copies de sélection cerise commits d' une autre branche à la branche actuelle .

En utilisant des diagrammes similaires à la réponse de @Kenny Ho :

Compte tenu de cet état initial:

A---B---C---D master
     \
      E---F---G topic

... et en supposant que vous voulez que les commits de la topicbranche soient rejoués au-dessus de la masterbranche actuelle , vous avez deux options:

  1. Utilisation de rebase: vous devez d'abord aller topicen faisant git checkout topic, puis déplacer la branche en exécutant git rebase master, en produisant:

    A---B---C---D master
                 \
                  E'---F'---G' topic
    

    Résultat: votre branche actuelle a topicété rebasée (déplacée) sur master.
    La topicsuccursale a été mise à jour, tandis que la mastersuccursale est restée en place.

  2. Utilisation de cherry-pick : vous devez d'abord aller masteren faisant git checkout master, puis copier la branche en exécutant git cherry-pick topic~3..topic(ou, de manière équivalente, git cherry-pick B..G), en produisant:

    A---B---C---D---E'---F'---G' master
         \
          E---F---G topic
    

    Résultat: les commits de topicont été copiés dans master.
    La mastersuccursale a été mise à jour, tandis que la topicsuccursale est restée en place.


Bien sûr, ici, vous deviez explicitement dire à cherry-pick de choisir une séquence de commits , en utilisant la notation de plage foo..bar . Si vous aviez simplement passé le nom de la branche, comme dans git cherry-pick topic, il n'aurait ramassé que le commit à la pointe de la branche, ce qui aurait donné:

A---B---C---D---G' master
     \
      E---F---G topic
waldyrious
la source