Git Guide Partie numéro 2: la règle d'or et autres bases de rebase

Voyons ce qui se passe lorsque vous exécutez git rebase et pourquoi vous devez être prudent. 

Ceci est la deuxième et la troisième partie du guide Git du blog Pierre de Wulf traduit par l'équipe Mail.ru Cloud Solutions . La première partie peut être lue ici .

L'essence du rebase


Comment se produit exactement le rebasage:


Vous pouvez dire que le rebase consiste à détacher la branche que vous souhaitez déplacer et à la connecter à une autre branche. Cette définition est vraie, mais essayez de regarder un peu plus en profondeur. Si vous regardez la documentation , voici ce qu'elle dit à propos du rebase: "Appliquer les validations à une autre branche (Réappliquer les validations au-dessus d'une autre astuce de base)".

Le mot principal ici est d'appliquer, car rebaser n'est pas seulement copier-coller des branches dans une autre branche. Rebase prend séquentiellement toutes les validations de la branche sélectionnée et les réapplique à la nouvelle branche.

Ce comportement conduit à deux points:

  1. En réappliquant les validations, Git crée de nouvelles validations. Même s'ils contiennent les mêmes modifications, Git est considéré comme de nouveaux commits indépendants.
  2. Git rebase remplace les validations et ne supprime pas les anciennes. Cela signifie qu'une fois le rebase terminé, vos anciennes validations continueront d'être stockées dans le sous-dossier / gjects du dossier .git. Si vous ne comprenez pas bien comment Git stocke et considère les commits, lisez la première partie de cet article.

Voici une interprétation plus correcte de ce qui se passe lors du rebasage:


Comme vous pouvez le voir, la branche de fonctionnalité contient des commits complètement nouveaux. Comme mentionné précédemment, le même ensemble de changements, mais des objets complètement nouveaux du point de vue de Git. 

Cela signifie également que les anciens commits ne sont pas détruits. Ils deviennent simplement inaccessibles directement. Si vous vous en souvenez, une branche n'est qu'un lien vers un commit. Ainsi, si ni une branche ni une balise ne fait référence à une validation, elle n'est pas accessible à l'aide de Git, bien qu'elle continue d'être présente sur le disque.

Voyons maintenant la règle d'or.

Règle d'or de rebase


La règle d'or de rebase est: « NE JAMAIS rebaser une branche partagée! ". Une branche partagée fait référence à une branche qui existe dans le référentiel réseau et avec laquelle d'autres personnes, sauf vous, peuvent travailler.

Souvent, cette règle est appliquée sans une bonne compréhension, par conséquent, nous analyserons pourquoi elle est apparue, d'autant plus qu'elle aidera à mieux comprendre le travail de Git.

Regardons une situation où un développeur enfreint la règle d'or et ce qui se passe dans ce cas.

Supposons que Bob et Anna travaillent ensemble sur un projet. Voici à quoi ressemblent les référentiels Bob et Anna et le référentiel d'origine sur GitHub:


Tous les utilisateurs ont des référentiels synchronisés avec GitHub.

Maintenant, Bob, enfreignant la règle d'or, effectue un rebase, et en même temps, Anna, travaillant dans la branche de fonctionnalité, crée un nouveau commit:


Voyez-vous ce qui va se passer?

Bob essaie d'exécuter un commit push; il obtient un refus de quelque chose comme ceci:


Git n'a pas réussi car Git ne savait pas comment combiner la branche de fonctionnalités de Bob avec la branche de fonctionnalités de GitHub.

La seule solution qui permet à Bob de pousser consiste à utiliser la clé force, qui indique au référentiel GitHub de supprimer la branche de fonctionnalité et d'accepter celle que Bob pousse pour cette branche. Après cela, nous obtenons la situation suivante:


Maintenant, Anna veut lancer ses changements, et voici ce qui se passera:


C'est normal, Git a dit à Anna qu'elle n'avait pas de version synchronisée de la branche de fonctionnalité, c'est-à-dire que sa version de la branche et la version de la branche dans GitHub étaient différentes. Anna doit terminer la traction. De la même manière que Git fusionne une branche locale avec une branche dans le référentiel lorsque vous poussez, Git essaie de fusionner la branche dans le référentiel avec une branche locale lorsque vous tirez.

Avant de faire des pull commits dans les branches locales et GitHub, cela ressemble à ceci:

A--B--C--D'   origin/feature // GitHub
A--B--D--E    feature        // Anna

Lorsque vous tirez, Git fusionne pour éliminer la différence dans les référentiels. Et donc, qu'est-ce que cela mène à:


Le commit M est un commit de fusion. Enfin, les branches de fonctionnalités d'Anna et de GitHub sont entièrement fusionnées. Anna a poussé un soupir de soulagement, tous les conflits ont été résolus, elle peut effectuer une poussée. 

Bob tire, maintenant tout est synchronisé:


En regardant le gâchis qui en résulte, vous devez vous assurer de l'importance de la règle d'or. Gardez également à l'esprit qu'un tel désordre a été créé par un seul développeur et sur une branche partagée entre deux personnes. Imaginez être dans une équipe de dix personnes. 

L'un des nombreux avantages de Git est que vous pouvez revenir en arrière sans aucun problème à tout moment. Mais plus il y a d'erreurs, telles que décrites, plus il est difficile de le faire.

Notez également que les validations en double apparaissent dans le référentiel réseau. Dans notre cas, D et D ', contenant les mêmes données. En fait, le nombre de validations en double peut être aussi important que le nombre de validations dans votre branche rebasée.

Si vous n'êtes toujours pas convaincu, présentons Emma, ​​le troisième développeur. Elle travaille dans la branche des fonctionnalités avant que Bob ne fasse son erreur et ne souhaite pousser. Supposons qu'au moment où elle pousse notre petit script précédent soit déjà terminé. Voici ce qui sort:


Oh, ce Bob !!!!

Ce texte peut vous faire penser que le rebasage n'est utilisé que pour déplacer une branche vers le haut d'une autre branche. Ceci est facultatif - vous pouvez rebaser sur la même branche.

Beauty pull rebase


Comme vous l'avez vu ci-dessus, les problèmes d'Anna auraient pu être évités si elle avait utilisé le rebasage par traction. Examinons cette question plus en détail.

Disons que Bob travaille dans une branche qui s'écarte du maître, alors son histoire peut ressembler à ceci:




Bob décide qu'il est temps de tirer, ce qui, comme vous l'avez déjà compris, entraînera une certaine confusion. Puisque le référentiel de Bob s'éloignait de GitHub, Git vous demandera si la fusion est effectuée, et le résultat sera comme ceci:


Cette solution fonctionne et fonctionne bien, cependant, il peut être utile pour vous de savoir qu'il existe d'autres solutions au problème. L'un d'eux est pull-rebase.

Lorsque vous effectuez un pull-rebase, Git essaie de déterminer quelles validations se trouvent uniquement dans votre branche et lesquelles se trouvent dans le référentiel réseau. Git combine ensuite les validations du référentiel réseau avec la dernière validation présente dans les référentiels local et réseau. Rebase ensuite tes commits locaux jusqu'à la fin de la branche. 

Cela semble compliqué, nous illustrons donc:

  1. Git ne fait attention qu'aux commits qui se trouvent à la fois dans votre référentiel et dans le référentiel réseau:

    Il ressemble à un clone local du référentiel GitHub.
  2. Git rebase les commits locaux:


Comme vous vous en souvenez, lorsque rebase Git applique les commits un par un, c'est-à-dire dans ce cas, il applique le commit maître E, puis F. à la fin de la branche. Le résultat est rebasé sur lui-même. Cela semble bon, mais la question se pose - pourquoi faire cela?

À mon avis, le plus gros problème avec la fusion des succursales est que l'histoire des commits est polluée. Par conséquent, le pull-rebase est une solution plus élégante. J'irais même plus loin et dirais que lorsque vous devez télécharger les dernières modifications de votre branche, vous devez toujours utiliser pull-rebase. Mais vous devez vous rappeler: puisque rebase applique toutes les validations tour à tour, lorsque vous rebasez 20 validations, vous devrez peut-être résoudre 20 conflits l'un après l'autre. 

En règle générale, vous pouvez utiliser l'approche suivante: une grande modification apportée il y a longtemps est la fusion, deux petites modifications récemment apportées à un pull-rebase.

Renforcer la force sur


Supposons que votre historique de commit ressemble à ceci:




Vous souhaitez donc rebaser la branche de la fonction 2 à la branche principale. Si vous effectuez un rebase régulier sur la branche principale, obtenez ceci:


Il est illogique que la validation D existe dans les deux branches: dans la fonctionnalité 1 et la fonctionnalité 2. Si vous déplacez la branche de la fonctionnalité 1 à la fin de la branche principale, il s'avère que la validation D sera appliquée deux fois.

Supposons que vous ayez besoin d'obtenir un résultat différent:


Pour implémenter un tel scénario, git rebase sur est exactement ce qui est prévu.

Tout d'abord, lisez la documentation:

SYNOPSIS
       git rebase [-i | --interactive] [<options>] [--exec <cmd>]
               [--onto <newbase> | --keep-base] [<upstream> [<branch>]]
       git rebase [-i | --interactive] [<options>] [--exec <cmd>] 
[--onto <newbase>]
               --root [<branch>]
       git rebase (--continue | --skip | --abort | --quit | --edit-todo 
| --show-current-patch)


Cela nous intéresse:

OPTIONS
       --onto <newbase>
          Starting point at which to create the new commits. If the 
--onto option is not specified, the starting point is <upstream>. May be 
any valid commit, and not just an existing branch name.


Utilisez cette option pour indiquer à quel moment créer de nouveaux commits.

Si cette option n'est pas spécifiée, l'amont deviendra le point de départ.

Pour comprendre, je vais vous donner une autre image:

A--B--C        master
    \
     D--E      feature1
         \
          F--G feature2

Here we want to rebase feature2 to master beginning from feature1
                           |                                |
                        newbase                         upstream


Autrement dit, la branche principale est newbase et la branche caractéristique 1 est en amont.

Ainsi, si vous souhaitez obtenir le résultat comme dans la dernière figure, vous devez exécuter git rebase --onto master feature1 dans la branche feature2.

Bonne chance

Traduit avec le support de Mail.ru Cloud Solutions .

Quoi d'autre à lire sur le sujet :

  1. La première partie du guide Git.
  2. Ma deuxième année en tant que développeur indépendant .
  3. Notre chaîne télégramme sur la transformation numérique


All Articles