Aller au contenu

Contrôle de Version (Avancé)

Dans cette unité, nous couvrirons les fonctionnalités avancées des systèmes de contrôle de version (VCS) à l'exemple de git. Nous commencerons par un petit récapitulatif des bases apprises précédemment, puis nous nous pencherons sur les branches et sur la manière dont elles peuvent être utilisées au mieux pour collaborer efficacement.

Résumé de la lecture

Les branches permettent de travailler sur plusieurs fonctionnalités logicielles en parallèle. Si elles sont utilisées efficacement, votre logiciel gagne en fonctionnalité sans jamais régresser. En particulier, vous aurez toujours une version stable et présentable de votre produit.

Récapitulatif des bases de Git

Le modèle

Git gère les versions de logiciels en utilisant un modèle graphe.

  • Chaque nœud du graphe est un commit, c'est-à-dire un instantané de la version du code.
  • Ajouter une fonctionnalité correspond à ajouter de nouveaux commits à un nœud feuille, c'est-à-dire à étendre le graphe.
  • Le système de fichiers montre toujours le contenu basé sur un commit donné. Le commit sur lequel vous travaillez actuellement est également appelé HEAD, c'est-à-dire où vous vous trouvez dans le graphe des commits.
  • Pour changer la représentation du système de fichiers vers un autre nœud du graphe, utilisez la commande git checkout.

Les dépôts

Chaque dépôt git contient un graphe git.

  • Un dépôt peut être local, ou sur un serveur distant.
  • Les serveurs git sont utiles pour partager le travail, mais git peut être utilisé sans serveur, par exemple, un lecteur USB commun peut être utilisé pour partager le travail.

Synchroniser les commits

Lors de la synchronisation du travail entre des dépôts, par exemple entre local et distant (serveur), vous essayez effectivement de combiner des modèles graphes.

  • Si les différences dans le modèle graphe sont purement additionnelles, la combinaison du graphe se fait automatiquement.
  • Si des modifications divergentes ont été apportées aux modèles graphes et affectent les mêmes fichiers, il faudra résoudre manuellement le conflit.

Création de branches

Jusqu'à présent, vous avez travaillé avec une seule branche, c'est-à-dire que le graphe était en réalité juste une longue ligne de commits :

---
title: "Single branch graph model"
---
gitGraph:
    commit id: "1"
    commit id: "2"
    commit id: "HEAD" type: HIGHLIGHT

C'est en réalité une pratique dangereuse :

  • Pendant que vous travaillez sur une fonctionnalité, votre code pourrait ne pas du tout être dans un état fonctionnel.
  • Les tests pourraient échouer, ou le programme pourrait même ne pas se compiler.
  • Il serait bon de commettre régulièrement...
  • ... mais d'un autre côté, vous ne voulez pas de commits régressifs (avec un état de logiciel cassé) sur votre branche de production.

C'est ici que les branches interviennent :

  • Les branches vous permettent de bifurquer à partir de n'importe quel commit existant, puis de développer tranquillement sur une fonctionnalité spécifique.
  • Si vous avez des "états cassés" intermédiaires du logiciel et que vous effectuez un commit, c'est acceptable : Les nouveaux commits se trouvent sur une branche séparée et n'interfèrent pas avec ceux qui ont besoin d'une branche main propre.
  • Exemple :
    • Pendant que vous travaillez sur votre contrôleur de halma, vous pourriez aussi travailler sur autre chose, par exemple une interface utilisateur alternative, ou un joueur robot IA.
    • Une branche supplémentaire vous permet de développer une fonctionnalité tout en laissant la ligne main intacte.

Comme pour tout le reste autour de git, il existe une commande pour créer de nouvelles branches :

  • Pour créer et placer le HEAD sur une nouvelle branche : git checkout -b user-interface
  • Décomposons la commande :
    • checkout : aller à un autre commit
    • -b (pour branche) : le commit se trouve en fait sur une nouvelle branche
    • user-interface : le nom de votre nouvelle branche
  • Notez que les nouvelles branches se créent toujours à partir de votre HEAD, c'est-à-dire du commit dans le graphe que vous avez sélectionné le plus récemment.

Utilisez la convention kebap pour les noms de branches

Les noms de branches doivent être lisibles, mais ne pas contenir d'espaces. Tout le monde semble être d'accord pour utiliser des mots tout en minuscules, séparés par un tiret. Cette notation est également connue sous le nom de notation kebap, en référence à la viande (ou tofu) sur un bâton.

Note : Il existe également l'option de créer une branche sans placer directement le HEAD. D'après mon expérience, ce n'est généralement pas ce que vous voulez, mais pour être complet : git branch user-interface provoque uniquement la création de la branche.

Créer une branche à partir de main

  • Dans la plupart des cas, vous voudrez développer de nouvelles fonctionnalités à partir de la version la plus récente du code.

    • Dans ce cas, la première étape consiste toujours à vérifier que vous êtes sur le commit le plus récent de main. Vous voulez que votre HEAD soit à la fin de la ligne main.
    • Pour en être sûr, vous pouvez commencer par git checkout main.
    • Dans tous les cas, vérifiez votre git status. Vous devriez voir ceci :
        $ git status
        Sur la branche main <--- C'est bon. :)
      
  • Ensuite, vous pouvez créer une nouvelle branche en toute sécurité avec git checkout -b user-interface

---
title: "Creating a branch from main"
---
gitGraph:
    commit id: "1"
    commit id: "2"
    commit id: "3"
    branch "user-interface"
    checkout "user-interface"
    commit id: "4"
    commit id: "HEAD" type: HIGHLIGHT
* Quel est l'effet ? * Vous pouvez développer de nouvelles fonctionnalités, basées sur le commit le plus récent. * Quel que soit le progrès expérimental que vous réalisez sur la branche user-interface, la branche main reste stable. * Si vous vous approchez d'une échéance, au moins avec main vous avez quelque chose à soumettre qui fonctionnera à peu près pour une démo / soumission – même si tout va très mal sur la branche user-interface.

Créer une branche à partir d'un HEAD détaché

  • Parfois, vous voulez ajouter une nouvelle fonctionnalité à partir d'une version antérieure du code.
  • Cela signifie : vous voulez créer une nouvelle branche, mais vous voulez qu'elle soit basée sur un commit antérieur dans le graphe.
  • Mais attention, avoir un HEAD détaché n'est généralement pas ce que vous voulez. Vous travaillez effectivement avec un code obsolète. Cependant, cela peut avoir du sens si...
    • Vous suspectez un bug grave dans le dernier commit de main, et vous êtes assez sûr qu'il sera annulé.
    • Le dernier commit a introduit un refactoring majeur, et vous ne pouvez pas mentalement gérer à la fois le refactoring et l'ajout de nouvelles fonctionnalités. Mais pour une raison quelconque, le travail sur la fonctionnalité ne doit pas être retardé.

Qu'est-ce qu'un HEAD détaché, déjà ?

"HEAD" est simplement une métaphore de l'endroit où vous vous trouvez dans le graphe (sur quel commit vous êtes pointé). "Détaché" signifie que vous ne pointez pas vers le commit le plus récent de la branche.

  • Créer une branche à partir d'un commit antérieur se ferait ainsi :
      $ git checkout cf48b16    <--- Here you intentionally detach the HEAD.
      $ git status
      HEAD detached at cf48b16  <--- This is usually not what you want. THINK TWICE :/
      $ git branch -b user-interface
    

Cela donnerait lieu au graphe de commits suivant :

---
title: "Creating a branch from detached HEAD"
---
gitGraph:
    commit id: "1"
    commit id: "2"
    branch "user-interface"
    checkout "main"
    commit id: "3"
    checkout "user-interface"
    commit id: "4"
    commit id: "HEAD" type: HIGHLIGHT

Créer une branche à partir d'une branche

  • Parfois, vous voulez créer une nouvelle fonctionnalité qui dépend d'une autre fonctionnalité actuellement en développement.
  • Dans ce cas, vous devez créer une branche à partir d'une autre branche.
  • Comme précédemment, l'astuce consiste à d'abord placer le HEAD sur la branche à partir de laquelle vous souhaitez dévier.
    git checkout user-interface
    git checkout -b some-extra-feature
    
  • Le code ci-dessus peut être représenté comme suit :
    ---
    title: "Creating a branch from main"
    ---
    gitGraph:
        commit id: "1"
        commit id: "2"
        commit id: "3"
        branch "user-interface"
        checkout "user-interface"
        commit id: "4"
        commit id: "5"
        branch "some-extra-feature"
        commit id: "HEAD" type: HIGHLIGHT

Journal des branches

Comme vous l'avez déjà appris dans les bases de git, il est toujours bon d'inspecter régulièrement le graphe avant de taper plus de commandes git.

  • Cependant, le git log standard ne montre que les commits de la branche sur laquelle vous vous trouvez actuellement.
    • Lorsqu'on travaille avec des branches, cela donne une représentation incomplète, et parfois trompeuse.
  • Si vous voulez vraiment savoir ce qui se passe, ajoutez quelques paramètres :
    • --all : pour voir tous les commits sur toutes les branches
    • --decorate : pour afficher les informations sur les branches
    • --oneline : pour représenter chaque commit sur une seule ligne, et obtenir une visualisation plus compacte
    • --graph : pour obtenir une visualisation de l'ensemble du graphe
  • Exemple :
# Setup repo
git init

# Create initial commit on main
echo "Keksli is cute" > keksli.txt
git add keksli.txt
git commit -m "initial commit"

# Add two commits to other branch
git checkout -b funny-branch
echo "We <3 Keksli" >> keksli.txt
git add keksli.txt
git commit -m "branch commit 1"
echo "We love him so so much" >> keksli.txt
git add keksli.txt
git commit -m "branch commit 2"

# Add another commit on main
git checkout main
echo "He is so cute" >> keksli.txt
git add keksli.txt
git commit -m "main commit 2"

git log --all --decorate --oneline --graph
Que doit afficher un git log complet ?

Réponse : Il doit afficher une représentation textuelle de l'ensemble du graphe des commits :

* 0f2fa81 (funny-branch) commit de la branche 2
* 2bd16e1 commit de la branche 1
| * 498c055 (HEAD -> main) commit de main 2
|/
* 36415eb commit initial

A dog

  • La commande est en fait facile à retenir ! Il suffit de penser "Un Chien !"

Crédits de l'image : Stackoverflow

Créez un alias

Si vous trouvez cette ligne trop longue à mémoriser, créez un alias avec : `git config --global alias.adog "log --all --decorate --oneline --graph"

git log --oneline
    02dd18a (HEAD -> main, origin/main, origin/HEAD) most recent commit
    c764ed0 some commit before
    cd1062f some commit even earlier before
    c5434da the first commit
git checkout c764ed0
  • La commande checkout vous permet en réalité de naviguer vers n'importe quel commit dans le graphe, il n'est pas nécessaire que ce soit un commit sur la branche main.
  • Ainsi, dans l'exemple suivant, vous pourriez déplacer votre HEAD vers n'importe quel commit de n'importe quelle branche, en utilisant la commande checkout :
    ---
    title: "Moving to another commit"
    ---
    gitGraph:
        commit id: "1"
        commit id: "HEAD" type: HIGHLIGHT
        commit id: "3"
        branch "user-interface"
        checkout "user-interface"
        commit id: "4"
        commit id: "5"
    • git checkout 4
      gitGraph:
          commit id: "1"
          commit id: "2"
          commit id: "3"
          branch "user-interface"
          checkout "user-interface"
          commit id: "HEAD" type: HIGHLIGHT
          commit id: "5"

Garder le HEAD sur vos épaules

  • La plupart du temps, vous voudrez déplacer votre HEAD vers la fin d'une branche.
    • Les noms de branches sont plus faciles à retenir que les numéros de commits. Mais changer pour un nom de branche vous garantit également de garder votre HEAD attaché.
    • Vous pouvez simplement fournir le nom de la branche :
      git checkout user-interface
    • Mais pour éviter toute confusion avec le changement vers un commit spécifique, il existe aussi la commande switch :
      git switch user-interface
      (Certains préfèrent cela. Je n'ai pas de préférence. Utilisez ce que vous préférez.)
      ---
      title: "Moving to the \"end\" of a branch"
      ---
      gitGraph:
          commit id: "1"
          commit id: "2"
          commit id: "3"
          branch "user-interface"
          checkout "user-interface"
          commit id: "4"
          commit id: "HEAD" type: HIGHLIGHT

Étendre les branches

Les commits sont toujours ajoutés au commit sur lequel vous vous trouvez actuellement.

  • Idéalement, votre HEAD est attaché à la fin d'une branche.
    • Dans ce cas, vous étendez simplement la branche sur laquelle vous avez effectué le dernier checkout.
    • Exemple :

# Make sure to place HEAD at end of the user-interface branch
git checkout user-interface
echo "Some new file content" > some-new-file
git add some-new-file
git commit -m "Added some new file"
# Here we've added a new commit to the end of the user-interface branch.
* Si vous n'êtes pas à la fin d'une branche, par exemple parce que vous avez placé le HEAD sur un commit précédent, vous créez implicitement une nouvelle branche anonyme. * Évitez cela ! Créez une nouvelle branche si vous en avez besoin, mais ne créez pas d'extensions sans nom. * Vous pouvez toujours les transformer en "branches réelles", mais l'état intermédiaire est confus. * Exemple :
# Initial state: two commits, and HEAD is detached
git adog
  * a99d339 (main) added whiskers
  * 6a66966 (HEAD) initial commit
echo "..." >> keksli
git add keksli
git commit -m "added more ..."
  [detached HEAD 767f84c] added more ...
   1 file changed, 1 insertion(+)
git adog
  * 767f84c (HEAD) added more ...
  | * a99d339 (main) added whiskers
  |/
  * 6a66966 initial commit

Fusionner les branches

  • Jusqu'à présent, vous avez vu comment créer de nouvelles branches, c'est-à-dire de nouvelles lignes de commits.
  • Mais peu importe la nouvelle fonctionnalité que vous implémentez, à la fin de la journée, vous voulez la fusionner avec la branche main, pas sur une branche de fonctionnalité isolée. C'est-à-dire :
    • Une fois que vous avez atteint un état stable, vous voulez fusionner votre nouveau code (les nouveaux commits sur votre branche de fonctionnalité) avec la branche main.
    • Surtout, vous ne voulez pas fusionner du code tant qu'il est en cours de développement. Le code doit être dans un état stable, et tout votre nouveau code doit être compilé, correctement testé et commenté.
  • Ce processus qui consiste à ramener une fonctionnalité d'une branche à une autre s'appelle la fusion.
  • Plus précisément : La fusion est l'effort de combiner le travail de différentes branches locales.
    • La syntaxe de base est :
      # Go to the branch that wants to receive the commits:
      git checkout main
      # Merge from the branch that has the commits to receive:
      git merge user-interface
      
      • Illustration
        ---
        title: "Merging branch into main"
        ---
        gitGraph:
            commit id: "1"
            commit id: "2"
            commit id: "3"
            branch "user-interface"
            checkout "user-interface"
            commit id: "4"
            commit id: "5"
            checkout "main"
            merge "user-interface"
            commit id: "HEAD" type: HIGHLIGHT

Scénarios de fusion

Selon la structure du graphe, la fusion peut être simple et entièrement automatisée, ou nécessiter une intervention manuelle :

  • Si seule la branche à partir de laquelle vous effectuez la fusion a de nouveaux commits : La fusion est simple, un nouveau commit est créé, combinant le résultat de tous les commits fusionnés.
  • Si les deux branches ont de nouveaux commits...
    • si elles n'affectent pas les mêmes fichiers : Il n'y a aucun conflit, un nouveau commit est créé, combinant le résultat de tous les commits fusionnés.
    • si elles affectent les mêmes fichiers, mais pas les mêmes lignes : Il n'y a aucun conflit, un nouveau commit est créé, combinant le résultat de tous les commits fusionnés.
    • si elles affectent les mêmes fichiers et les mêmes lignes : Il y a un conflit de fusion. Vous devez manuellement indiquer quelle version de ligne doit l'emporter. Ensuite, un nouveau commit est créé.

C'est pourquoi vous voulez un dépôt sans encombrement

Les conflits de fusion sont gérables pour le code, mais extrêmement fastidieux pour les fichiers binaires ou générés, car il n'existe pas de moyen significatif de les résoudre manuellement au niveau des lignes. C'est pourquoi vous ne voulez pas d'encombrement dans votre dépôt. Gardez les fichiers non pertinents hors de votre dépôt, vous ne voulez pas perdre votre temps avec des conflits de fusion absurdes !

Fusion fréquente de main dans la branche de fonctionnalité

  • Plus vous attendez pour fusionner des branches, plus cela devient complexe.
    • Plus vous attendez, plus il est probable qu'il y ait de nouveaux commits sur les deux branches, plus il y a de chances que des changements affectent les mêmes fichiers et les mêmes lignes.
  • Si le développement de votre fonctionnalité prend un certain temps, vous pouvez régulièrement fusionner main dans votre branche, avant de finalement fusionner avec main.
  • Des fusions multiples et plus petites sont plus faciles que des fusions massives et rares !
  • Voici une illustration de la fusion de main dans une branche de fonctionnalité :
---
title: "Merging branch into main"
---
gitGraph:
    commit id: "1"
    commit id: "2"
    commit id: "3"
    branch "user-interface"
    checkout "user-interface"
    commit id: "4"
    checkout "main"
    commit id: "5"
    checkout "user-interface"
    merge "main"
    checkout "main"
    commit id: "6"
    checkout "user-interface"
    commit id: "7"
    checkout "main"
    merge "user-interface"
    commit id: "HEAD" type: HIGHLIGHT

La fusion fréquente ne contredit pas les commits fréquents !

Bien que vous souhaitiez attendre que votre code soit dans un bon état avant de fusionner, vous devez garder des commits fréquents sur la branche de fonctionnalité. Évitez les méga-commits, ce sera un vrai casse-tête à fusionner.

Synchroniser les branches entre les dépôts

  • Contrairement à Dropbox, iCloud ou Google Drive, les dépôts Git ne se synchronisent pas automatiquement.
    • (La synchronisation automatique serait horrible, vous auriez constamment peur des mises à jour en cours de développement cassées de vos pairs !)
  • Dans la leçon de bases de VCS, vous avez déjà appris que les commits doivent être activement poussés ou tirés vers et depuis un serveur distant.
    • git commit crée seulement un commit local.
    • Le commit n'est pas encore sur le serveur. Il reste strictement sur votre machine jusqu'à ce que vous push le commit sur le serveur.
  • Les branches fonctionnent de la même manière !
    • git checkout -b ma-nouvelle-branche crée une nouvelle branche locale.
    • La branche n'est pas encore sur le serveur. Elle reste strictement sur votre machine jusqu'à ce que vous push la branche sur le serveur.

Envoyer des branches vers le serveur distant

  • Pousser une nouvelle branche vers le serveur est simplement une commande push plus longue :
    git push --set-upstream origin ma-nouvelle-branche

Vous n'avez pas besoin de vous souvenir de cela

Il n'est pas nécessaire de vous souvenir de cette commande push spéciale. Si vous tentez un push normal, Git vous rappellera la syntaxe correcte.

Tirer des branches vers votre machine locale

  • Il est fréquent que vous souhaitiez travailler sur une branche qui se trouve sur le dépôt distant, mais qui n'est pas encore présente sur votre dépôt local.
    • Cela se produit, par exemple, lorsqu'un collègue a créé une nouvelle branche sur sa machine et l'a ensuite poussée sur le serveur distant.
  • Avant de pouvoir utiliser leur branche et y ajouter de nouveaux commits, vous devez obtenir une copie de cette branche dans votre dépôt.
  • Exemple :
    • Vous commencez par vérifier vos branches locales
      # Look up local branches:
      git branch -a 
        * main
      
    • Vous savez que votre collègue a commencé à travailler sur une branche user-interface, mais elle n'est pas présente dans votre dépôt.
    • Vous ne voulez PAS créer une nouvelle branche vous-même, vous souhaitez récupérer la branche de votre collègue !
  • Il existe deux principales façons de récupérer des branches depuis le serveur, et elles sont étroitement liées mais présentent une différence subtile.

Récupérer (Fetch)

  • Dans le cas le plus simple, vous voulez simplement récupérer les nouvelles branches du serveur distant.
  • Vous pouvez le faire avec git fetch.
  • Exemple :
    • Vous commencez par vérifier vos branches locales
      # Look up local branches:
      git branch -a 
        * main
      
    • La branche que vous recherchez n'est pas présente, donc vous utilisez git fetch pour récupérer les nouveaux commits depuis le serveur :
         # Get new commits from remote and add to local graph:
         git fetch
         # Then you look up local branches again:
         git branch -a 
           * main
           * user-interface
         # Great the new branch is there, let's switch:
         git checkout user-interface
      

Fetch ne fait pas de fusion automatique !

Un détail important à propos de fetch est qu'il ne tente pas de fusionner, mais récupère uniquement les nouveaux commits.

  • Les commits récupérés par fetch sont toujours stockés sur des branches supplémentaires, c'est-à-dire que les nouveaux commits ne sont pas combinés avec les branches que vous avez déjà.
  • Dans l'exemple précédent, ce n'était pas un problème, car vous vouliez récupérer une branche entièrement nouvelle.
  • Mais la plupart du temps, vous récupérez de nouveaux commits ou branches, non pas pour les laisser sur des branches locales inutilisées, mais parce que vous souhaitez aussi les intégrer avec vos propres nouveaux commits.
  • À moins que vous ne soyez intéressé par une branche entièrement nouvelle, fetch est toujours suivi par un merge, afin que vous puissiez réellement intégrer les nouveaux commits avec vos propres progrès.

Pull

  • Dans le premier cours et atelier sur le VCS, vous avez déjà vu brièvement la commande pull.
  • pull n'est en réalité pas nouveau, mais c'est juste la combinaison de fetch + merge.
  • Je n'utilise que rarement fetch. Les nouveaux commits ne servent à rien à moins d'être également fusionnés avec vos propres progrès.

Pull vs Fetch

Pull et fetch récupèrent tous les deux les commits du dépôt distant, mais pull tente implicitement aussi de fusionner les nouveaux commits (il essaie de combiner et de déplacer votre HEAD dans la branche actuelle), tandis que fetch ne récupère que le commit sans déplacer votre HEAD. Dans la plupart des cas, vous voulez faire un pull.

Suppression des branches

  • À un moment donné, les branches ont rempli leur objectif.
  • Par exemple, si une fonctionnalité est finalisée et que la branche a été fusionnée dans la branche principale, vous n'avez plus besoin de cette branche.
  • C'est une bonne pratique de supprimer également la branche dans ce cas, afin qu'elle ne vous distrait pas lorsque vous inspectez votre dépôt avec git branch -a.

L'un n'implique pas l'autre

Supprimer une branche locale n'implique pas la suppression de la branche distante.
De même, supprimer une branche distante n'implique pas la suppression de la branche locale.

Local

  • Pour supprimer une branche locale, utilisez simplement l'une des commandes suivantes :

    • git branch --delete user-interface
    • git branch -d user-interface
  • Pour vous éviter de supprimer accidentellement du travail non sauvegardé, git ne vous laissera pas supprimer des branches non fusionnées. Vous pouvez forcer la suppression avec l'une des commandes suivantes :

    • git branch --delete --force user-interface
    • git branch -D user-interface

Remote

  • Supprimer une branche d'un dépôt distant nécessite de pousser vos modifications du graphe.
  • La commande correspondante est : git push -d origin user-interface

Rebase

  • En général, vous voulez que les développeurs fassent des commits régulièrement et de manière fine-grainée.
    • Cela vous offre plus de liberté pour revenir en arrière en cas de problème.
  • Cependant, lorsque plusieurs personnes travaillent sur le même graphe git et poussent vers plusieurs branches en même temps, cela conduit à des "diamants" dans l'historique du graphe :

La raison est que la suppression d'une branch ne supprime pas ses commits, seulement le nom de la branch associé avec. Tout branch qui a été merge laisse un diamant, peu import si supprimé ou pas.

  • Il serait plus agréable d'avoir des branches qui reprennent un commit spécifique sur main et qui, immédiatement après, poussent leur progrès (finalisé) vers main.
    • Mais cela est presque impossible, car développer de nouvelles fonctionnalités prend du temps.
    • Plus l'équipe est grande, plus de nouveaux contenus seront constamment ajoutés à main.
  • Illustration de l'évolution de main, au fur et à mesure du développement de user-interface :

    ---
    title: main evolving in parallel to user-interface
    ---
    gitGraph
        commit id: "1"
        commit id: "2"
        branch user-interface
        checkout user-interface
        commit id: "3"
        commit id: "4"
        commit id: "5" type: HIGHLIGHT
        checkout main
        commit id: "6"

  • Le rebase est une astuce qui permet de modifier rétroactivement l'historique des commits, de sorte à ce que les commits sur une branche apparaissent comme s'ils s'étaient produits de manière séquentielle, plutôt que parallèlement.

  • Dans l'exemple ci-dessus, le graphe est modifié pour ressembler à ceci :
    ---
    title: Commit graph after rebase
    ---
    gitGraph
        commit id: "1"
        commit id: "2"
        commit id: "6"
        branch user-interface
        checkout user-interface
        commit id: "3R"
        commit id: "4R"
        commit id: "5R" type: HIGHLIGHT
        checkout main

Syntaxe

  • Pour faire en sorte que les commits d'une branche de fonctionnalité soient réorganisés (rebase) à partir du dernier commit de la branche principale (voir illustration ci-dessus) :
    # place the HEAD on the user-interface branch
    git checkout user-interface
    # make the commits on user-interface branch rebase from the most recent main commit
    git rebase main
  • Il est fort probable que vous souhaitiez ensuite ramener vos changements sur main (sans les diamants !)
  • Vous venez de faire un rebase, vous êtes donc assuré de ne pas avoir de conflits de fusion.
    # go back to the main branch
    git checkout main
    # merge the rebased feature branch into main
    git merge user-interface

Fonctionnement interne du rebase

Comment le rebase fonctionne-t-il réellement ?

  • L'idée de déplacer des commits est quelque peu artificielle, car l'idée même d'un commit est de définir une certaine position dans le graphe des commits.
  • Le rebase ne déplace pas réellement les commits, mais plutôt :
    • Cache les commits originaux d'une certaine série, par exemple une branche.
    • Rejoue chacun des commits originaux, c'est-à-dire :
      1. Créer une copie ailleurs dans le graphe
      2. Imiter exactement les mêmes modifications de fichiers que dans le commit original
      3. Créer un nouveau conflit de fusion pour chaque problème rencontré
    • Optionnel : Supprimer les commits cachés.

Cherry-pick

  • La fusion tente de combiner le travail de tous les commits d'une branche différente jusqu'à un commit spécifique.
  • Dans certaines situations, cela peut être trop grossier :
    • Imaginez qu'un collègue travaille sur une branche de fonctionnalité et qu'il a également corrigé un petit bug pertinent pour vous.
    • Dans ce cas, vous ne voulez pas nécessairement fusionner tout leur travail, juste pour récupérer cette petite correction de bug.
  • La commande cherry-pick vous permet de combiner les travaux de manière plus sélective.
  • Plus précisément, le cherry-pick (un autre commit étant la cerise sur le gâteau) vous permet de fusionner avec un commit spécifique, indépendamment de ce qui s'est passé avant sur cette branche.
  • Exemple :
# trace the commits of all branches
git adog
  * 9ff78e3 (HEAD -> main) some-recent-commit
  | * 89afd4f (some-other-branch) commit-to-cherrypick
  | * c081b9e some-non-relevant-commit
  |/
  * 151c4c6 first-commit
# just making sure the HEAD is attached to main
git checkout main
# go get that commit from another branch!
git cherry-pick 89afd4f

Tags

  • Les tags vous permettent d'étiqueter un commit spécifique, afin de pouvoir le retrouver plus facilement plus tard.
  • Les tags sont souvent utilisés pour les versions majeures, par exemple Halma-1.4.0.

Tags "soft"

  • Pour taguer le commit le plus récent (là où votre HEAD pointe), utilisez : git tag Halma-1.4.0
  • Vous pouvez également fournir une description plus longue avec :
    git tag -m "Une nouvelle version de Halma, présentant quelques joueurs bots IA." Halma-1.5.0

Afficher les tags

  • Dès que vous avez des tags, vous pouvez inspecter la liste avec : git tag

Publier les tags

  • Comme tout le reste de votre dépôt local, les tags restent locaux jusqu'à ce que vous les partagiez activement, en les poussant vers le dépôt :
    git push origin Halma-1.5.0

Pensées conclusives

  • Q : J'ai un bouton git dans mon IDE, pourquoi devrais-je m'embêter à apprendre les commandes ?
  • R : Tout d'abord, sentez-vous libre d'utiliser l'outil qui fonctionne le mieux pour vous. Mais voici mon avis sur les outils graphiques pour les débutants :

"git est complexe, et vous voulez réfléchir à deux fois avant toute action. Les outils graphiques accélèrent les choses, mais mènent facilement à une approche du "essayer et échouer", un peu comme : "Je vais juste cliquer sur ceci et cela, et espérer que ça marche" (Sans vraiment comprendre ce que "ça marche" veut dire ou ce qu'on peut en attendre exactement). Ce n'est pas ainsi qu'on devient compétent avec une technologie complexe, et ce n'est pas ce qu'on attend de vous dans une entreprise. On n'apprend pas en copiant-collant depuis ChatGPT, et personne ne paiera un ingénieur qui ne sait que copier-coller depuis ChatGPT. Par contre, comprendre réellement est précieux. Mais apprendre prend du temps et de la pratique. Si vous tapez vous-même les commandes git, vous avez au moins plus de temps pour RÉFLÉCHIR à ce qu'elles servent à faire. Quand vous vous sentirez à l'aise avec les commandes, bien sûr, allez-y et essayez de nouveaux outils."

Littérature

Inspiration et lectures supplémentaires pour les esprits curieux :