Merge vs Rebase¶
Pour aller plus loin dans la compréhension de Git, il faut examiner de façon plus approfondie le mécanisme de merge et de rebase.
Scénario Merge et Rebase¶
Voyons un petit scénario avec 5 commits pour observer concrètement la différence entre Merge et Rebase
Merge¶
On crée quelques commits, puis une branche avec un commit, tandis que le main reçoit un commit supplémentaire, puis fusion de la branche et ajout d’un commit supplémentaire.
echo 1 > Readme.md
git add Readme.md
git commit -m "un"
echo 2 > Readme.md
git commit -am "deux"
git checkout -b feature
echo 3 > feature.md
git add feature.md
git commit -m "trois"
git checkout main
echo 4 > Readme.md
git commit -am "quatre"
git merge feature --no-edit
echo 5 > Readme.md
git commit -am "cinq"
git log --graph --pretty='format:%h %an (%ar) %s' --abbrev-commit
On garde la trace de la branche dans l’historique :
* 8161013 Gerard (il y a 2 jours) cinq
* a6deee3 Gerard (il y a 2 jours) Merge branch 'feature'
|\
| * 6c0bcf0 Gerard (il y a 2 jours) trois
* | 881c431 Gerard (il y a 2 jours) quatre
|/
* 88d5081 Gerard (il y a 2 jours) deux
* d761e62 Gerard (il y a 2 jours) un
Rebase¶
On crée quelques commits, puis une branche avec un commit, tandis que le main reçoit un commit supplémentaire, puis rebase de la branche à partir du main et ajout d’un commit supplémentaire. Le scénario est le suivant :
echo 1 > Readme.md
git add Readme.md
git commit -m "un"
echo 2 > Readme.md
git commit -am "deux"
git checkout -b feature
echo 3 > feature.md
git add feature.md
git commit -m "trois"
git checkout main
echo 4 > Readme.md
git commit -am "quatre"
git rebase feature
git log --graph --pretty=oneline --abbrev-commit
echo 5 > Readme.md
git commit -am "cinq"
git log --graph --pretty='format:%h %an (%ar) %s' --abbrev-commit
On obtient cette fois un historique linéaire :
* 824e964 cinq
* 5164ca2 quatre
* 4905a5f trois
* f0b3d04 deux
* 2d84cee un
Utilisation de Merge¶
utilisation standard¶
L’utilisation la plus courante est :
git checkout main
git merge feature
Si tout se passe sans encombre, parfait ! Sinon on est dans les ennuis :
Auto-merging Document
CONFLICT (content): Merge conflict in mon-prog.py
Automatic merge failed; fix conflicts and then commit the result.
régler un conflit de fusion¶
On peut alors editer le fichier et régler le conflit à la main ou avec un outil comme kdiff3 ou vscode.
Sinon, si on sait que la version correcte est dans main :
git checkout --ours mon-prog.py
Si au contraire, on veut garder la version de la branche feature :
git checkout --theirs mon-prog.py
puis dans tous les cas :
git add mon-prog.py
git merge --continue
Le conflit est résolu !
Utilisation de Rebase¶
Le merge est souvent le plus utilisé (Merge request, etc.) mais il y a quelques utilisations importantes du rebase dont les 2 suivantes :
se mettre à jour sur un projet sur lequel on collabore¶
Si Bob a réalisé une feature dans une branche new_feature dans un projet auquel il collabore et que le projet évolue sensiblement pendant la réalisation de cette feature, Bob va devoir se remettre à jour des dernières évolutions du projet.Pour cela il fera usage du rebase comme ceci par exemple :
git checkout main
git pull depot-reference main
git checkout new_feature
git rebase main
revisiter un historique défaillant : rebase interactif¶
Les historiques Git sont rarement parfaits du premier coup ! Alors il y a parfois nécessité de revisiter le passé et de faire un nettoyage. La machine à remonter le temps en Git s’appelle le rebase interactif On choisit le commit à partir duquel on veut opérer des changements. On note l’id du commit précédent celui qu’on souhaite modifier et on demande à notre machine à remonter le temps de nous conduire à ce commit :
git rebase -i d5ea32
Un écran s’ouvre alors vous permettant d’éditer, supprimer, déplacer ou
rejouer tel quel un commit. Si on demande à éditer le commit, il faudra
généralement le défaire avec un git reset HEAD^
, procéder aux
modifications puis relancer le processus avec un
git rebase --continue
détail d’un rebasage interactif¶
git rebase -i COMMIT
vous permet de rejouer (en les modifiants) tous les commits plus récents que COMMIT. Git vous présente alors un petit menu vous permettant d’éditer, pour chaque commit, l’action à lui appliquer :
pick : pour le rejouer tel quel.
squash : pour le rejouer tel quel ET le fusionner avec le commit précédent.
reword : pour le rejouer tel quel MAIS avoir la possibilité de modifier le message de log.
edit : pour le rejouer tel quel ET temporairement s’arrêter pour permettre à l’utilisateur d’effectuer des modifications (voir Editer un commit ci-dessous). Une fois effectuées et commitées les modifications, le rebase interrompu peut se poursuivre en faisant
git rebase --continue
.On peut déplacer la position d’un commit dans le menu (mais il se peut que cela cause un conflit, lorsque les commits interviennent sur les mêmes fichiers, qu’il faudra alors gérer manuellement).
On peut supprimer une ligne du menu pour se débarasser du commit correspondant.
git rebase --continue
permet de poursuivre un rebase temporairement
interrompu pour permettre à l’utilisateur d’éditer un commit.
Pour nettoyer un historique il faut typiquement procéder à plusieurs petits rebases successifs, chacun ne faisant qu’un petit morceau de nettoyage.
Editer un commit¶
Lorsqu’on choisit l’action edit pour un commit d’un rebase, le rebase va s’interrompre après avoir rejoué ce commit pour permettre à l’utilisateur de faire des modifications. Il faut alors défaire le commit tout en préservant ses contributions au répertoire de travail :
$ git reset HEAD^
On peut alors lister les contributions à retravailler :
$ git status
fin du rebasage¶
La technique est alors de réaliser un ou plusieurs commits jusqu’à ce que le status soit clean. On peut alors invoquer :
$ git rebase -- continue
pour continuer le rebase en cours, ou bien :
$ git rebase -- abort
pour annuler le rebase en cours.
ajout de modifications partielles dans un commit¶
Pour réaliser un ou plusieurs commits, il faut pour chacun d’entre eux
faire les git add
que l’on désire. Si on ne veut pas commiter toutes
les modifications faites à un fichier, mais seulement certaines d’entre
elles, on peut utiliser :
$ git add -e FICHIER
qui nous donne la possibilité d’éditer un diff des modifs du fichier :
on peut alors enlever certaines lignes ou les modifier. Dans le cas où
le fichier n’a pas déjà été ajouté au projet, il faut d’abord ajouter
une version vide du fichier avec git add -N
puis faire le
git add -e
:
$ git add -N FICHIER
$ git add -e FICHIER
Voir la doc pour plus de détails sur rebase.
Attention : Choix de la stratégie de pull¶
Si vous voyez ce message apparaitre lorsque vous faites un git pull
:
─ git pull
warning: Tirer sans spécifier comment réconcilier les branches divergentes
est découragé. Vous pouvez éliminer ce message en lançant une des
commandes suivantes avant votre prochain tirage :
git config pull.rebase false # fusion (stratégie par défaut)
git config pull.rebase true # rebasage
git config pull.ff only # avance rapide seulement
Vous pouvez remplacer "git config" par "git config --global" pour que
ce soit l\'option par défaut pour tous les dépôts. Vous pouvez aussi
passer --rebase, --no-rebase ou --ff-only sur la ligne de commande pour
remplacer à l\'invocation la valeur par défaut configurée.
Le premier choix sera souvent privilégié, le second uniquement si vous voulez procéder par rebase, cf ci-dessous, et le troisième va notamment refuser de merger si le local et le distant ont divergé, ce qui peut-être sécurisant pour un débutant Git, et éviter de faire un commit de merge. Ceci présentera toutefois une difficulté si vous souhaitez annuler le merge ultérieurement.
On pourra donc généralement choisir :
git config --global pull.rebase false
ou
git config --global pull.ff only
sauf si on sait ce qu’on fait !