Comment git fusionne-t-il après la sélection de cerises?

190

Imaginons que nous ayons une mastersuccursale.

Ensuite, nous créons un newbranch

git checkout -b newbranch

et faites deux nouveaux commits pour newbranch: commit1 et commit2

Ensuite, nous passons au master et faisons cherry-pick

git checkout master
git cherry-pick hash_of_commit1

En examinant,gitk nous voyons que commit1 et sa version triée sur le volet ont des hachages différents, donc techniquement, ce sont deux commits différents.

Enfin, nous fusionnons newbranchdans master:

git merge newbranch

et voyez que ces deux commits avec des hachages différents ont été fusionnés sans problème bien qu'ils impliquent que les mêmes modifications doivent être appliquées deux fois, donc l'une d'elles devrait échouer.

Est-ce que git fait vraiment une analyse intelligente du contenu du commit lors de la fusion et décide que les changements ne doivent pas être appliqués deux fois ou que ces commits sont marqués en interne comme liés entre eux?

Paul
la source

Réponses:

131

Réponse courte

Ne vous inquiétez pas, Git s'en chargera.

Longue réponse

Contrairement par exemple à SVN 1 , Git ne stocke pas les commits au format delta, mais est basé sur des instantanés 2,3 . Alors que SVN essaierait naïvement d'appliquer chaque commit fusionné en tant que correctif (et échouerait, pour la raison exacte que vous avez décrite), Git est généralement capable de gérer ce scénario.

Lors de la fusion, Git essaiera de combiner les instantanés des deux commits HEAD dans un nouvel instantané. Si une portion de code ou un fichier est identique dans les deux instantanés (c'est-à-dire parce qu'un commit a déjà été sélectionné), Git n'y touchera pas.

Sources

1 Skip-Deltas dans Subversion
2 Bases de Git
3 Le modèle objet Git

Helmbert
la source
40
En fait, je dirais que vous devriez vous soucier de la fusion et que "git le gérera" n'est pas une bonne règle de base.
cregox
4
En fait, la fusion PEUT entraîner dans certains cas un contenu dupliqué. Git le gère parfois, mais parfois non.
donquixote
C'est horriblement faux. Got stocke à peu près les fichiers sous toutes les formes utilisables. Et si je me souviens bien SVN utilisé pour stocker des instantanés.
he_the_great
2
@he_the_great, non. Le format de stockage skip-delta de SVN (! = Snapshots) est bien documenté dans le manuel . Et je ne comprends vraiment pas ce que vous entendez par condevable . Je ne suis pas un locuteur natif, mais je suis presque sûr que ce n'est pas un vrai mot.
helmbert
2
@he_the_great Mais même en tant que packfiles, tout hachage donné pour un fichier donne le fichier complet. Oui, il compresse en utilisant des deltas, mais ce n'est pas un delta pour les changements dans un commit, mais plutôt un delta entre les hachages pour un fichier. En ce qui concerne l'objet de validation, il fait référence à une arborescence qui fait référence au hachage d'un fichier complet. Le fait que sous le capot les données soient compressées n'affecte pas le fonctionnement de git. Git stocke des fichiers complets en ce qui concerne un commit, SVN stocke des deltas pour les commits si je comprends bien.
Peter Olson
44

Après une telle fusion, vous pouvez avoir des commits triés sur le volet dans l'historique deux fois.

Solution pour éviter cela, je cite un article qui recommande pour les branches avec des commits en double (triés sur le volet) d'utiliser le rebase avant la fusion:

git merge après git cherry-pick: éviter les commits en double

Imaginons que nous ayons la branche master et une branche b:

   o---X   <-- master
    \
     b1---b2---b3---b4   <-- b

Maintenant, nous avons besoin d'urgence des commits b1 et b3 dans master, mais pas des commits restants dans b. Donc, ce que nous faisons est de vérifier la branche master et le cherry-pick commits b1 et b3:

$ git checkout master
$ git cherry-pick "b1's SHA"
$ git cherry-pick "b3's SHA"

Le résultat serait:

   o---X---b1'---b3'   <-- master
    \
     b1---b2---b3---b4   <-- b

Disons que nous faisons un autre commit sur master et nous obtenons:

   o---X---b1'---b3'---Y   <-- master
    \
     b1---b2---b3---b4   <-- b

Si nous voulions maintenant fusionner la branche b dans master:

$ git merge b

Nous obtiendrions ce qui suit:

   o---X---b1'---b3'---Y--- M  <-- master
     \                     /
      b1----b2----b3----b4   <-- b

Cela signifie que les changements introduits par b1 et b3 apparaîtront deux fois dans l'historique. Pour éviter cela, nous pouvons rebaser au lieu de fusionner:

$ git rebase master b

Ce qui donnerait:

   o---X---b1'---b3'---Y   <-- master
                        \
                         b2'---b4'   <-- b

Finalement:

$ git checkout master
$ git merge b

nous donne:

   o---X---b1'---b3'---Y---b2'---b4'   <-- master, b

EDIT Corrections supposées par le commentaire de David Lemon

éphémère
la source
1
bon indice sur le rebase! il «ignorera» automatiquement tous les commits sélectionnés.
iTake
2
Honnêtement, cela semble trop beau pour être vrai, je dois le voir avec mes yeux. Rebase modifie également les commits, votre dernière chronologie devrait être---Y---b2'---b4'
David Lemon
Fonctionne parfaitement. Très utile si vous ne voulez pas avoir les commits triés sur le volet deux fois dans l'histoire.
user2350644
1
Ne faut-il pas noter que si rebase est beau, le danger de l'utiliser est que toutes les fourches ou branches créées à partir de l'ancien b seront désynchronisées et que les utilisateurs devront peut-être recourir à des trucs comme git reset --hard et git pousser -f?
JHH
1
@JHH c'est pourquoi nous rebasons la branche locale ici
éphémère