-
1. Démarrage rapide
-
2. Les bases de Git
-
3. Les branches avec Git
-
4. Git sur le serveur
- 4.1 Protocoles
- 4.2 Installation de Git sur un serveur
- 4.3 Génération des clés publiques SSH
- 4.4 Mise en place du serveur
- 4.5 Démon (Daemon) Git
- 4.6 HTTP intelligent
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Git hébergé
- 4.10 Résumé
-
5. Git distribué
-
6. GitHub
-
7. Utilitaires Git
- 7.1 Sélection des versions
- 7.2 Indexation interactive
- 7.3 Remisage et nettoyage
- 7.4 Signer votre travail
- 7.5 Recherche
- 7.6 Réécrire l’historique
- 7.7 Reset démystifié
- 7.8 Fusion avancée
- 7.9 Rerere
- 7.10 Déboguer avec Git
- 7.11 Sous-modules
- 7.12 Empaquetage (bundling)
- 7.13 Replace
- 7.14 Stockage des identifiants
- 7.15 Résumé
-
8. Personnalisation de Git
- 8.1 Configuration de Git
- 8.2 Attributs Git
- 8.3 Crochets Git
- 8.4 Exemple de politique gérée par Git
- 8.5 Résumé
-
9. Git et les autres systèmes
- 9.1 Git comme client
- 9.2 Migration vers Git
- 9.3 Résumé
-
10. Les tripes de Git
- 10.1 Plomberie et porcelaine
- 10.2 Les objets de Git
- 10.3 Références Git
- 10.4 Fichiers groupés
- 10.5 La refspec
- 10.6 Les protocoles de transfert
- 10.7 Maintenance et récupération de données
- 10.8 Les variables d’environnement
- 10.9 Résumé
-
A1. Annexe A: Git dans d’autres environnements
- A1.1 Interfaces graphiques
- A1.2 Git dans Visual Studio
- A1.3 Git dans Visual Studio Code
- A1.4 Git dans IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.5 Git dans Sublime Text
- A1.6 Git dans Bash
- A1.7 Git dans Zsh
- A1.8 Git dans PowerShell
- A1.9 Résumé
-
A2. Annexe B: Embarquer Git dans vos applications
- A2.1 Git en ligne de commande
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Commandes Git
- A3.1 Installation et configuration
- A3.2 Obtention et création des projets
- A3.3 Capture d’instantané basique
- A3.4 Création de branches et fusion
- A3.5 Partage et mise à jour de projets
- A3.6 Inspection et comparaison
- A3.7 Débogage
- A3.8 Patchs
- A3.9 Courriel
- A3.10 Systèmes externes
- A3.11 Administration
- A3.12 Commandes de plomberie
3.6 Les branches avec Git - Rebaser (Rebasing)
Rebaser (Rebasing)
Dans Git, il y a deux façons d’intégrer les modifications d’une branche dans une autre : en fusionnant (merge
) et en rebasant (rebase
).
Dans ce chapitre, vous apprendrez la signification de rebaser, comment le faire, pourquoi c’est un outil incroyable et dans quels cas il est déconseillé de l’utiliser.
Les bases
Si vous revenez à un exemple précédent du chapitre Fusions (Merges), vous remarquerez que votre travail a divergé et que vous avez ajouté des commits sur deux branches différentes.
![Historique divergeant simple](png/basic-rebase-1.png)
Comme nous l’avons déjà expliqué, le moyen le plus simple pour intégrer ces branches est la fusion via la commande merge
.
Cette commande réalise une fusion à trois branches entre les deux derniers instantanés (snapshots) de chaque branche (C3 et C4) et l’ancêtre commun le plus récent (C2), créant un nouvel instantané (et un commit).
![Fusion pour intégrer des travaux aux historiques divergeants](png/basic-rebase-2.png)
Cependant, il existe un autre moyen : vous pouvez prendre le patch de la modification introduite en C4
et le réappliquer sur C3
.
Dans Git, cette action est appelée "rebaser" (rebasing).
Avec la commande rebase
, vous pouvez prendre toutes les modifications qui ont été validées sur une branche et les rejouer sur une autre.
Dans cet exemple, vous lanceriez les commandes suivantes :
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
Cela fonctionne en cherchant l’ancêtre commun le plus récent des deux branches (celle sur laquelle vous vous trouvez et celle sur laquelle vous rebasez), en récupérant toutes les différences introduites par chaque commit de la branche courante, en les sauvant dans des fichiers temporaires, en réinitialisant la branche courante sur le même commit que la branche de destination et en appliquant finalement chaque modification dans le même ordre.
![Rebasage des modifications introduites par `C4` sur `C3`](png/basic-rebase-3.png)
C4
sur C3
À ce moment, vous pouvez retourner sur la branche master
et réaliser une fusion en avance rapide (fast-forward merge).
$ git checkout master
$ git merge experiment
![Avance rapide de la branche `master`](png/basic-rebase-4.png)
master
À présent, l’instantané pointé par C4'
est exactement le même que celui pointé par C5
dans l’exemple de fusion.
Il n’y a pas de différence entre les résultats des deux types d’intégration, mais rebaser rend l’historique plus clair.
Si vous examinez le journal de la branche rebasée, elle est devenue linéaire : toutes les modifications apparaissent en série même si elles ont eu lieu en parallèle.
Vous aurez souvent à faire cela pour vous assurer que vos commits s’appliquent proprement sur une branche distante — par exemple, sur un projet où vous souhaitez contribuer mais que vous ne maintenez pas.
Dans ce cas, vous réaliseriez votre travail dans une branche puis vous rebaseriez votre travail sur origin/master
quand vous êtes prêt à soumettre vos patchs au projet principal.
De cette manière, le mainteneur n’a pas à réaliser de travail d’intégration — juste une avance rapide ou simplement une application propre.
Il faut noter que l’instantané pointé par le commit final, qu’il soit le dernier des commits d’une opération de rebasage ou le commit final issu d’une fusion, sont en fait le même instantané — c’est juste que l’historique est différent. Rebaser rejoue les modifications d’une ligne de commits sur une autre dans l’ordre d’apparition, alors que la fusion joint et fusionne les deux têtes.
Rebases plus intéressants
Vous pouvez aussi faire rejouer votre rebasage sur autre chose qu’une branche.
Prenez un historique tel que Un historique avec deux branches thématiques qui sortent l’une de l’autre par exemple.
Vous avez créé une branche thématique (server
) pour ajouter des fonctionnalités côté serveur à votre projet et avez réalisé un commit.
Ensuite, vous avez créé une branche pour ajouter des modifications côté client (client
) et avez validé plusieurs fois.
Finalement, vous avez rebasculé sur la branche server
et avez réalisé quelques commits supplémentaires.
![Un historique avec deux branches thématiques qui sortent l’une de l’autre](png/interesting-rebase-1.png)
Supposons que vous décidez que vous souhaitez fusionner vos modifications du côté client dans votre ligne principale pour une publication (release) mais vous souhaitez retenir les modifications de la partie serveur jusqu’à ce qu’elles soient un peu mieux testées.
Vous pouvez récupérer les modifications du côté client qui ne sont pas sur le serveur (C8
et C9
) et les rejouer sur la branche master
en utilisant l’option --onto
de git rebase
:
$ git rebase --onto master server client
Cela signifie en substance "Extraire la branche client, déterminer les patchs depuis l’ancêtre commun des branches client
et server
puis les rejouer sur master
".
C’est assez complexe, mais le résultat est assez impressionnant.
![Rebaser deux branches thématiques l’une sur l’autre](png/interesting-rebase-2.png)
Maintenant, vous pouvez faire une avance rapide sur votre branche master
(cf. Avance rapide sur votre branche master
pour inclure les modifications de la branche client):
$ git checkout master
$ git merge client
![Avance rapide sur votre branche `master` pour inclure les modifications de la branche client](png/interesting-rebase-3.png)
master
pour inclure les modifications de la branche clientSupposons que vous décidiez de tirer (pull) votre branche server
aussi.
Vous pouvez rebaser la branche server
sur la branche master
sans avoir à l’extraire avant en utilisant git rebase [branchedebase] [branchethematique]
— qui extrait la branche thématique (dans notre cas, server
) pour vous et la rejoue sur la branche de base (master
) :
$ git rebase master server
Cette commande rejoue les modifications de server
sur le sommet de la branche master
, comme indiqué dans Rebasage de la branche server sur le sommet de la branche master
.
![Rebasage de la branche server sur le sommet de la branche `master`](png/interesting-rebase-4.png)
master
Vous pouvez ensuite faire une avance rapide sur la branche de base (master
) :
$ git checkout master
$ git merge server
Vous pouvez effacer les branches client
et server
une fois que tout le travail est intégré et que vous n’en avez plus besoin, éliminant tout l’historique de ce processus, comme visible sur Historique final des commits :
$ git branch -d client
$ git branch -d server
![Historique final des _commits_](png/interesting-rebase-5.png)
Les dangers du rebasage
Ah… mais les joies de rebaser ne viennent pas sans leurs contreparties, qui peuvent être résumées en une ligne :
Ne rebasez jamais des commits qui ont déjà été poussés sur un dépôt public.
Si vous suivez ce conseil, tout ira bien. Sinon, de nombreuses personnes vont vous haïr et vous serez méprisé par vos amis et votre famille.
Quand vous rebasez des données, vous abandonnez les commits existants et vous en créez de nouveaux qui sont similaires mais différents.
Si vous poussez des commits quelque part, que d’autres les tirent et se basent dessus pour travailler, et qu’après coup, vous réécrivez ces commits à l’aide de git rebase
et les poussez à nouveau, vos collaborateurs devront re-fusionner leur travail et les choses peuvent rapidement devenir très désordonnées quand vous essaierez de tirer leur travail dans votre dépôt.
Examinons un exemple expliquant comment rebaser un travail déjà publié sur un dépôt public peut générer des gros problèmes. Supposons que vous clonez un dépôt depuis un serveur central et réalisez quelques travaux dessus. Votre historique de commits ressemble à ceci :
![Cloner un dépôt et baser du travail dessus.](png/perils-of-rebasing-1.png)
À présent, une autre personne travaille et inclut une fusion, puis elle pousse ce travail sur le serveur central. Vous le récupérez et vous fusionnez la nouvelle branche distante dans votre copie, ce qui donne l’historique suivant :
![Récupération de _commits_ et fusion dans votre copie.](png/perils-of-rebasing-2.png)
Ensuite, la personne qui a poussé le travail que vous venez de fusionner décide de faire marche arrière et de rebaser son travail.
Elle lance un git push --force
pour forcer l’écrasement de l’historique sur le serveur.
Vous récupérez alors les données du serveur, qui vous amènent les nouveaux commits.
![Quelqu’un pousse des _commits_ rebasés, en abandonnant les _commits_ sur lesquels vous avez fondé votre travail.](png/perils-of-rebasing-3.png)
Vous êtes désormais tous les deux dans le pétrin.
Si vous faites un git pull
, vous allez créer un commit de fusion incluant les deux historiques et votre dépôt ressemblera à ça :
![Vous fusionnez le même travail une nouvelle fois dans un nouveau _commit_ de fusion](png/perils-of-rebasing-4.png)
Si vous lancez git log
lorsque votre historique ressemble à ceci, vous verrez deux commits qui ont la même date d’auteur et les mêmes messages, ce qui est déroutant.
De plus, si vous poussez cet historique sur le serveur, vous réintroduirez tous ces commits rebasés sur le serveur central, ce qui va encore plus dérouter les autres développeurs.
C’est plutôt logique de présumer que l’autre développeur ne souhaite pas voir apparaître C4
et C6
dans l’historique.
C’est la raison pour laquelle il avait effectué un rebasage initialement.
Rebaser quand vous rebasez
Si vous vous retrouvez effectivement dans une situation telle que celle-ci, Git dispose d’autres fonctions magiques qui peuvent vous aider. Si quelqu’un de votre équipe pousse de force des changements qui écrasent des travaux sur lesquels vous vous êtes basés, votre défi est de déterminer ce qui est à vous et ce qui a été réécrit.
Il se trouve qu’en plus de l’empreinte SHA du commit, Git calcule aussi une empreinte qui est uniquement basée sur le patch introduit avec le commit. Ceci est appelé un "identifiant de patch" (patch-id).
Si vous tirez des travaux qui ont été réécrits et les rebasez au-dessus des nouveaux commits de votre collègue, Git peut souvent déterminer ceux qui sont uniquement les vôtres et les réappliquer au sommet de votre nouvelle branche.
Par exemple, dans le scénario précédent, si au lieu de fusionner quand nous étions à l’étape Quelqu’un pousse des commits rebasés, en abandonnant les commits sur lesquels vous avez fondé votre travail nous exécutons la commande git rebase teamone/master
, Git va :
-
Déterminer quels travaux sont uniques à notre branche (C2, C3, C4, C6, C7)
-
Déterminer ceux qui ne sont pas des commits de fusion (C2, C3, C4)
-
Déterminer ceux qui n’ont pas été réécrits dans la branche de destination (uniquement C2 et C3 puisque C4 est le même patch que C4')
-
Appliquer ces commits au sommet de
teamone/master
Ainsi, au lieu du résultat que nous avons observé au chapitre Vous fusionnez le même travail une nouvelle fois dans un nouveau commit de fusion, nous aurions pu finir avec quelque chose qui ressemblerait davantage à Rebaser au-dessus de travaux rebasés puis que l’on a poussé en forçant.
![Rebaser au-dessus de travaux rebasés puis que l’on a poussé en forçant](png/perils-of-rebasing-5.png)
Cela fonctionne seulement si les commits C4 et C4' de votre collègue correspondent presque exactement aux mêmes modifications. Autrement, le rebasage ne sera pas capable de déterminer qu’il s’agit d’un doublon et va ajouter un autre patch similaire à C4 (ce qui échouera probablement puisque les changements sont au moins partiellement déjà présents).
Vous pouvez également simplifier tout cela en lançant un git pull --rebase
au lieu d’un git pull
normal.
Vous pouvez encore le faire manuellement à l’aide d’un git fetch
suivi d’un git rebase team1/master
dans le cas présent.
Si vous utilisez git pull
et voulez faire de --rebase
le traitement par défaut, vous pouvez changer la valeur du paramètre de configuration pull.rebase
par git config --global pull.rebase true
.
Si vous considérez le fait de rebaser comme un moyen de nettoyer et réarranger des commits avant de les pousser et si vous vous en tenez à ne rebaser que des commits qui n’ont jamais été publiés, tout ira bien. Si vous tentez de rebaser des commits déjà publiés sur lesquels les gens ont déjà basé leur travail, vous allez au devant de gros problèmes et votre équipe vous en tiendra rigueur.
Si vous ou l’un de vos collègues y trouve cependant une quelconque nécessité, assurez-vous que tout le monde sache lancer un git pull --rebase
pour essayer de rendre les choses un peu plus faciles.
Rebaser ou Fusionner
Maintenant que vous avez vu concrètement ce que signifient rebaser et fusionner, vous devez vous demander ce qu’il est préférable d’utiliser. Avant de pouvoir répondre à cela, revenons quelque peu en arrière et parlons un peu de ce que signifie un historique.
On peut voir l’historique des commits de votre dépôt comme un enregistrement de ce qu’il s’est réellement passé. Il s’agit d’un document historique qui a une valeur en tant que tel et ne doit pas être altéré. Sous cet angle, modifier l’historique des commits est presque blasphématoire puisque vous mentez sur ce qu’il s’est réellement passé. Dans ce cas, que faire dans le cas d’une série de commits de fusions désordonnés ? Cela reflète ce qu’il s’est passé et le dépôt devrait le conserver pour la postérité.
Le point de vue inverse consiste à considérer que l’historique des commits est le reflet de la façon dont votre projet a été construit. Vous ne publieriez jamais le premier brouillon d’un livre et le manuel de maintenance de votre projet mérite une révision attentive. Ceci constitue le camp de ceux qui utilisent des outils tels que le rebasage et les branches filtrées pour raconter une histoire de la meilleure des manières pour les futurs lecteurs.
Désormais, nous espérons que vous comprenez qu’il n’est pas si simple de répondre à la question portant sur le meilleur outil entre fusion et rebasage. Git est un outil puissant et vous permet beaucoup de manipulations sur et avec votre historique mais chaque équipe et chaque projet sont différents. Maintenant que vous savez comment fonctionnent ces deux outils, c’est à vous de décider lequel correspond le mieux à votre situation en particulier.
De manière générale, la manière de profiter au mieux des deux mondes consiste à rebaser des modifications locales que vous avez effectuées mais qui n’ont pas encore été partagées avant de les pousser de manière à obtenir un historique propre mais sans jamais rebaser quoi que ce soit que vous ayez déjà poussé quelque part.