Git rebase

Ce document contient une discussion approfondie sur la commande git rebase. La commande rebase a également été abordée sur les pages Créer un dépôt et Réécrire l'historique. Cette page examinera plus en détail la configuration et l'exécution de la commande git rebase. Les cas d'usage et les pièges courants du rebase seront également abordés.

Le rebase, est spécialisé dans l'intégration des changements d'une branche à une autre, est l'un des deux utilitaires Git. L'autre utilitaire d'intégration des changements est git merge. Le merge est toujours un enregistrement progressif des changements. Alternativement, le rebase dispose de puissantes fonctionnalités de réécriture de l'historique. Pour une comparaison détaillée du merge et du rebase, consultez notre guide Comparaison entre un merge et un rebase. Le rebase lui-même compte deux modes principaux : « manuel » et « interactif ». Nous couvrirons les différents modes de rebase plus en détail ci-dessous.

Qu'est-ce que git rebase ?

Le rebase consiste à déplacer du code vers un nouveau commit de base ou à combiner une séquence de commits. Il est particulièrement utile et facilement visible dans le contexte d'un workflow de branche par fonctionnalité. Vous pouvez visualiser le processus général comme suit :

Tutoriel Git : Git rebase

Au niveau du contenu, le rebase consiste à changer la base de votre branche d'un commit vers un autre, donnant l'illusion que vous avez créé votre branche à partir d'un commit différent. En interne, Git réalise cette tâche en créant des commits et en les appliquant à la base spécifiée. Il est important de comprendre que, même si la branche semble identique, elle est composée de commits tout nouveaux.

Utilisation

La principale raison du rebase est de maintenir un historique de projet linéaire. Par exemple, imaginez une situation où la branche principale a évolué depuis que vous avez commencé à travailler sur une branche de fonctionnalité. Vous souhaitez obtenir les dernières mises à jour de la branche principale dans votre branche de fonctionnalité, tout en gardant l'historique de votre branche propre de sorte qu'il apparaisse comme si vous aviez travaillé dans la branche principale la plus récente. Cela permettra par la suite d'effectuer un merge propre de votre branche de fonctionnalité dans la branche principale. Pourquoi souhaitons-nous conserver un « historique propre » ? Les avantages d'un historique propre deviennent tangibles lorsque vous exécutez des opérations Git pour étudier l'introduction d'une régression. Un scénario plus réaliste serait :

  1. Un bug a été identifié dans la branche principale. Une fonctionnalité qui fonctionnait parfaitement rencontre désormais des problèmes.
  2. Un développeur examine l'historique de la branche principale avec git log. Grâce à « l'historique propre », il peut rapidement raisonner sur l'historique du projet.
  3. Le développeur ne peut déterminer quand le bug a été introduit en lançant la commande git log, il exécute donc un git bisect.
  4. Parce que l'historique Git est propre, git bisect renvoie un ensemble de commits affinés à comparer lors de l'examen de la régression. Le développeur trouve rapidement le commit qui a introduit le bug et peut agir en conséquence.

Découvrez-en plus sur git log et git bisect sur les pages dédiées à leur utilisation.

Vous avez deux options pour intégrer votre fonctionnalité dans la branche principale : merger directement ou faire un rebase, puis merger. La première option génère un merge à trois branches et un commit de merge, alors que la deuxième entraîne un fast-forward merge et génère un historique parfaitement linéaire. Le schéma suivant illustre comment le rebase sur la branche principale facilite un fast-forward merge.

Git rebase : Branche sur master

Le rebase est une méthode courante pour intégrer les changements en amont dans votre dépôt local. L'intégration des changements en amont avec git merge génère un commit de merge superflu dès que vous voulez voir comment le projet a évolué. D'un autre côté, faire un rebase revient à dire : « Je veux baser mes changements sur ce que les autres ont fait ».

Ne pas rebaser l'historique public

Comme nous l'avons vu dans Réécrire l'historique, vous ne devez jamais faire un rebase des commits qui ont été pushés vers un dépôt public. Le rebase remplacerait les anciens commits par les nouveaux, et ce serait comme si l'historique de votre projet avait brusquement disparu.

git rebase standard et git rebase interactif

Le rebase interactif Git désigne le moment où le rebase Git accepte un argument -- i. Ce « i » signifie « Interactif ». Sans argument, la commande s'exécute en mode standard. Dans les deux cas, supposons que vous avez créé une branche de fonctionnalité distincte.

# Créez une branche de fonctionnalité basée sur la branche principale
git checkout -b feature_branch master
# Modifiez les fichiers
git commit -a -m "Adds new feature"

La commande git rebase en mode standard appliquera automatiquement les commits à votre branche de travail actuelle, puis à la pointe de la branche transférée.

git rebase <base>

 

Cela rebase automatiquement la branche actuelle sur <base>, qui peut être n'importe quel type de référence de commit (par exemple, un ID, un nom de branche, un tag ou une référence relative à HEAD).

Exécuter git rebase avec le flag -i démarre une session de rebase interactif. Au lieu de déplacer aveuglément tous les commits vers la nouvelle base, le rebase interactif vous permet de modifier des commits un à un au cours du processus. Vous pouvez ainsi nettoyer l'historique en supprimant, en séparant et en modifiant une série existante de commits. C'est un peu le git commit --amend sous stéroïdes.

git rebase --interactive <base>

 

Cette action rebase la branche actuelle sur <base>, mais utilise une session de rebase interactif. Elle ouvre un éditeur dans lequel vous pouvez entrer des commandes (décrites ci-dessous) pour chaque commit dont vous souhaitez faire un rebase. Ces commandes définissent comment des commits individuels vont être transférés vers la nouvelle base. Vous pouvez également réorganiser la liste des commits pour modifier l'ordre des commits. Lorsque vous avez spécifié les commandes pour chaque commit dans le rebase, Git commence à relire les commits en appliquant les commandes de rebase. Les commandes d'édition de rebase sont les suivantes :


pick 2231360 some old commit
pick ee2adc2 Adds new feature
# Rebasez 2cf755d..ee2adc2 sur 2cf755d (9 commandes)
#
# Commandes :
# p, pick = utilisez le commit
# r, reword = utilisez le commit, mais modifiez le message de commit
# e, edit = utilisez le commit, mais arrêtez-vous pour apporter des changements
# s, squash = utilisez le commit, mais intégrez-le au commit précédent
# f, fixup = commande similaire à « squash », mais qui permet d'annuler le message de log de ce commit
# x, exec = exécutez la commande (le reste de la ligne) à l'aide de Shell
# d, drop = supprimez le commit

Commandes rebase supplémentaires

Comme détaillé dans la page Réécrire l'historique, le rebase peut être utilisé pour modifier des commits anciens et multiples, des fichiers commités et des messages multiples. Bien qu'il s'agisse là des applications les plus fréquentes, git rebase dispose également d'options de commande supplémentaires utiles dans des applications plus complexes.

  • git rebase -- d signifie que durant la lecture, le commit sera supprimé du bloc de commits combinés final.
  • git rebase -- p laisse le commit en l'état. Il ne modifiera pas le message ou le contenu du commit, et le commit restera un commit individuel dans l'historique des branches.
  • git rebase -- x exécute un script Shell de ligne de commande sur chaque commit marqué lors de la lecture. Un exemple utile consisterait à exécuter la suite de tests de votre base de code sur des commits spécifiques, ce qui pourrait aider à identifier des régressions lors d'un rebase.

Récapitulatif

Le rebase interactif vous donne le contrôle total sur l'apparence de votre historique de projet. Les développeurs ont ainsi une grande liberté pour enregistrer un historique « désordonné » en se concentrant sur la programmation, avant d'y revenir et de le nettoyer ultérieurement.

La plupart des développeurs aiment utiliser un rebase pour affiner une branche de fonctionnalité avant de la merger dans la base de code principale. Ils peuvent ainsi écraser les commits qui ne sont pas importants, supprimer ceux qui sont obsolètes et s'assurer que le reste est en ordre avant de commiter dans l'historique de projet « officiel ». Pour les autres, il semblera que la fonctionnalité a été entièrement développée dans une seule série de commits bien planifiés.

Le vrai pouvoir du rebase interactif peut être constaté dans l'historique de la branche principale obtenue. Pour les autres, vous passerez pour un développeur brillant qui a implémenté la nouvelle fonctionnalité avec le nombre parfait de commits du premier coup. Le rebase interactif permet ainsi de maintenir la propreté et la cohérence de l'historique d'un projet.

Options de configuration

Quelques propriétés de rebase peuvent être définies avec git config. Ces options modifieront l'apparence de la sortie git rebase.

  • rebase.stat : un booléen qui est par défaut défini sur « false ». L'option bascule l'affichage du contenu visuel diffstat qui montre ce qui a changé depuis le dernier rebase.
  • rebase.autoSquash : une valeur booléenne qui modifie le comportement --autosquash.
  • rebase.missingCommitsCheck : peut être défini sur plusieurs valeurs qui changent le comportement de rebase autour des commits manquants.
warn Affiche un sortie d'avertissement en mode interactif qui prévient de la suppression de commits.

error

Arrête le rebase et imprime des messages d'avertissement de suppression de commits.

ignore

Défini par défaut, il ignore tout avertissement lié à l'absence de commit.
  • rebase.instructionFormat : une chaîne de format git log qui sera utilisée pour formater l'affichage du rebase interactif.

Application de rebase avancé

L'argument de ligne de commande --onto peut être transmis à git rebase. Dans le mode git rebase --onto, la commande se développe en :

 git rebase --onto <newbase> <oldbase>

La commande --onto active une forme de rebase plus puissante permettant de transmettre des réfs spécifiques qui deviendront des pointes de rebase.
Supposons que nous ayons un dépôt avec des branches comme :


   o---o---o---o---o master
        \
         o---o---o---o---o featureA
              \
               o---o---o featureB

 

La branche featureB est basée sur la branche featureA. Toutefois, nous réalisons que featureB ne dépend pas des changements dans featureA et pourrait être dérivée de la branche principale (master).

 git rebase --onto master featureA featureB

featureA équivaut à <oldbase>. master devient <newbase>, et featureB est référencé pour ce vers quoi l'élément HEAD de <newbase> pointe. Les résultats sont alors les suivants :


                      o---o---o featureB
                     /
    o---o---o---o---o master
     \
      o---o---o---o---o featureA

Comprendre les dangers du rebase

Lorsque vous travaillez avec git rebase, soyez prudent : les conflits de merge peuvent se multiplier durant un workflow de rebase. Cela se produit si vous disposez d'une branche ancienne qui s'est éloignée de la branche principale (master). En fin de compte, vous souhaiterez effectuer un rebase avec la branche principale (master), et à ce moment-là, elle peut contenir beaucoup de nouveaux commits avec lesquels les changements de votre branche peuvent entrer en conflit. Une solution facile consiste à faire fréquemment un rebase de votre branche par rapport à la branche principale (master) et à commiter plus souvent. Les arguments de ligne de commande --continue et --abort peuvent être transmis à git rebase pour faire avancer ou réinitialiser le rebase en cas de conflits.

La perte de commits lors de la réécriture interactive de l'historique est un point plus important à prendre en compte avec la commande rebase. L'exécution du rebase en mode interactif et de sous-commandes comme squash ou drop supprimera les commits du log immédiat de votre branche. À première vue, les commits peuvent sembler définitivement supprimés. Cependant, il est possible de les restaurer et d'annuler l'ensemble du rebase avec git reflog. Pour en savoir plus sur l'utilisation de git reflog afin de retrouver des commits perdus, consultez notre page de documentation git reflog.

La commande git rebase en elle-même n'est pas si dangereuse. Les dangers réels surviennent lors de l'exécution de rebases interactifs de réécriture d'historique et de pushs forcés des résultats vers une branche distante partagée par d'autres utilisateurs. C'est un modèle qui devrait être évité, car il est susceptible d'écraser le travail des autres utilisateurs distants lorsqu'ils font un pull.

Récupération du rebase en amont

Si un autre utilisateur a fait un rebase, puis un push en force vers la branche sur laquelle vous commitez, une opération git pull écrase tous les commits que vous avez basés sur cette branche précédente avec la pointe qui a été pushée en force. Heureusement, en utilisant git reflog, vous pouvez obtenir le reflog de la branche distante. Sur celui-ci, vous pouvez trouver une réf avant qu'elle ne soit rebasée. Vous pouvez ensuite faire un rebase de votre branche par rapport à cette réf distante à l'aide de l'option --onto comme indiqué ci-dessus dans la section Application avancée de rebase.

Résumé

Dans cet article, nous avons couvert l'utilisation de git rebase. Nous avons discuté de cas d'usage de base et avancés, ainsi que d'exemples plus poussés. Voici quelques-uns des principaux points de discussion :

  • Comparaison des modes standard et interactif de git rebase
  • Options de configuration de la commande git rebase
  • git rebase --onto
  • Commits perdus de la commande git rebase

Nous avons examiné l'utilisation de git rebase avec d'autres outils comme git reflog, git fetch et git push. Consultez leurs pages respectives pour en savoir plus.

Prêt à découvrir Git ?

Essayez ce tutoriel interactif.

Démarrez maintenant