Aller au contenu

Atelier 07

Dans cette session de laboratoire, vous allez pratiquer des fonctionnalités avancées de la ligne de commande de git, notamment des stratégies pour combiner le travail de plusieurs fonctionnalités sur la branche main. Vous n'avez pas besoin de code spécifique pour suivre cette session de laboratoire.

Préparation

Vous travaillerez avec un dépôt de test pour le reste du laboratoire. Avant de commencer les exercices réels, voici quelques étapes préparatoires.

Créer un dépôt de test

  1. Créez un nouveau dossier vide sur votre bureau. (Ou ailleurs selon votre préférence)
  2. Ouvrez un terminal et naviguez à l'intérieur du dossier nouvellement créé.
  3. Initialisez un nouveau dépôt avec git init.

Créer un fichier markdown

  • Vous ne travaillerez pas avec du code réel, le reste de la session de laboratoire nécessite simplement un court poème au format markdown.
    • Suivre les changements du poème exemple vous permettra de simuler le contrôle de version, par exemple en utilisant des branches.
  • À l'intérieur de votre dépôt de test, créez un nouveau fichier texte : "keksli.md".
  • Collez le fichier du poème suivant, puis enregistrez le fichier :

    # Keksli, the Little Piece of Cake
    
    1) Oh Keksli, sweet as frosting’s kiss,
    2) A tiny fluff of gentle bliss.
    3) With eyes that sparkle, soft and bright,
    4) You bring the world pure joy and light.
    
    5) A little piece of cake, they say,
    6) But cuter than the sweetest tray.
    7) With every purr and playful leap,
    8) You melt our hearts and softly keep—
    
    9) That sprinkle of sweetness, warm and true,
    10) A Keksli-sized delight in all you do.
    

  • Ajoutez le fichier à votre dépôt et créez un commit initial :

    git add keksli.md
    git commit -m "Added initial poem" 
    

  • Sauvegardez le dossier de test dans son état actuel, par exemple en en faisant une copie ou en le compressant.

  • Je vous demanderai régulièrement de réinitialiser l'état initial. Dans ce cas, supprimez le dossier original et restaurez-le depuis votre sauvegarde sur le disque.

Création de commits d'exemple

Tout au long du reste du laboratoire, je vous demanderai fréquemment de modifier le poème et d'ajouter la version modifiée dans un nouveau commit.

  • Si je ne précise pas quelle ligne modifier, ajoutez simplement une nouvelle ligne à la fin avec .... Sinon, modifiez la ligne spécifiée (1), 2), ...).
  • Enfin, assurez-vous que la modification de votre poème soit bien enregistrée dans un nouveau commit, avec :
git add keksli.md
git commit -m "Modified poem" 
  • Vous pouvez utiliser un message de commit personnalisé si vous le souhaitez, pour une meilleure traçabilité.

Journalisation des commits (Dog logging)

  • La commande standard git log n'affiche ni l'intégralité du graphe, ni ne visualise proprement la façon dont les commits sont liés.
  • En classe, vous avez vu l'acronyme ADOG, comme aide-mémoire pour la commande plus longue :
    git log --all --decorate --oneline --graph
  • Créez un alias pour l'acronyme ADOG, avec :
    git config --global alias.adog "log --all --decorate --oneline --graph"
  • Testez la nouvelle commande d'alias. Allez dans votre dépôt de test et tapez : git adog

Nouvelles branches

Dans ce premier exercice, vous allez vous entraîner à créer de nouvelles branches et à naviguer entre les branches.

Création d'une nouvelle branche

  • Les branches sont généralement créées pour chaque fonctionnalité. Nous allons maintenant supposer que vous souhaitez créer une version étendue du poème avec quelques lignes supplémentaires.

À vous de jouer

  1. Créez une nouvelle branche extended-poem, en utilisant git checkout -b
    • Remarque : la commande checkout place automatiquement votre HEAD sur la nouvelle branche. Pas besoin de changer manuellement.
  2. Modifiez le poème et créez un nouveau commit.
  3. Inspectez le graphe complet des commits avec git adog
    • Lisez attentivement la sortie. Pourquoi le graphe visualisé est-il toujours une ligne ?

Changer de branche

  • La commande universelle pour naviguer dans le graphe est git checkout
  • git checkout attend soit un hash de commit, soit un nom de branche.
    • Les hashs de commit détachent le HEAD et le placent dans le commit demandé.
    • Les noms de branches attachent le HEAD au dernier commit de la branche spécifiée.
Y a-t-il une différence entre faire un checkout du dernier commit sur une branche et faire un checkout par nom de branche ?

Oui, il y a une différence ! Dans les deux cas, le HEAD se retrouve sur le dernier commit d'une ligne. Mais un checkout par hash de commit ne peut pas toujours aboutir à un HEAD attaché, car les commits peuvent être partagés par plusieurs branches.
Illustration :

*   059ba92 (HEAD, main, feature) fixed merge conflict
|\
| * e0a2244 feature XYZ implemented
* | ecf8311 little bugfix
|/
* 1a31ca2 initial commit
Le commit 059ba92 est partagé par les branches main et feature. Un git checkout 059ba92 ne peut pas être résolu en un nom de branche unique. HEAD sera détaché.
(En fait, un checkout par hash détache toujours HEAD, simplement pour éviter l'ambiguïté ci-dessus !)

  • git switch est un peu plus sécurisé que git checkout, car il s'agit d'une variante de checkout qui n'accepte que les noms de branches.
    • Avec git switch, vous ne pouvez jamais vous retrouver dans un état HEAD détaché.
    • Naviguer entre les branches avec git switch est préférable pour les utilisateurs novices.

À vous de jouer

  1. Revenez à la branche main.
  2. Modifiez le poème et créez un nouveau commit.
  3. Inspectez à nouveau le graphe avec git adog. Qu'est-ce qui a changé ?

Commits avec HEAD détaché

  • Habituellement, vous souhaitez que vos branches divergent d'un HEAD attaché, c'est-à-dire que vous voulez que les branches partent de la fin d'une branche.
  • Cependant, il est possible de créer des branches implicites en commettant tout en étant en HEAD détaché.
    Illustration :
    • Dans une série de deux commits, un checkout du commit initial entraîne un HEAD détaché.
      git adog
       * adce8cb (main) second commit
       * 3af628f (HEAD) initial commit
      
    • Créer un nouveau commit avec un HEAD détaché entrerait en conflit avec le deuxième commit.
    • Git crée donc implicitement une nouvelle branche sans nom. (HEAD ne pointe pas vers un nom de branche !)
      git adog
       * 310e6b7 (HEAD) commit from detached head
       | * adce8cb (main) second commit
       |/
       * 3af628f initial commit
      
  • Les commits depuis un HEAD détaché (comme le commit 310e6b7 ci-dessus) sont dangereux, car ils ne sont associés à aucune branche et sont difficiles à retrouver plus tard. Les travaux effectués avec un HEAD détaché sont facilement perdus.
  • Heureusement, il est possible d'associer ultérieurement les commits à un nom de branche !

À vous de jouer

  1. Réinitialisez le dépôt de test à son état original.
  2. Ajoutez un nouveau commit.
    • Inspectez le graphe du dépôt avec git adog. Vous ne devriez voir que deux commits consécutifs, avec HEAD attaché.
  3. Détachez le HEAD, en effectuant un checkout du commit initial par son ID.
    • Inspectez à nouveau avec git adog, la sortie devrait correspondre au premier des listings ci-dessus.
  4. Avec votre HEAD détaché, créez un nouveau commit.
    • Inspectez à nouveau votre graphe avec git adog. La sortie devrait maintenant correspondre au second des listings ci-dessus.
    • Notez l'ID du commit créé en mode HEAD détaché.
  5. Essayez d'abandonner votre dernier commit, en faisant un checkout de la branche main.
    • Git réattachera votre HEAD.
    • Git émettra également un avertissement vous informant que vous laissez un commit derrière. Git vous expliquera aussi comment associer ce commit laissé derrière à une nouvelle branche.
  6. Revenez au commit laissé derrière, en faisant un checkout (de l'ID que vous avez noté).
  7. Utilisez la commande précédemment proposée par git, pour associer le commit laissé derrière à un nouveau nom de branche, par exemple implicit-branch.
    • Inspectez le graphe du dépôt une fois de plus avec git adog.
Votre HEAD est-il maintenant attaché ?

Non. git adog montre que HEAD est sur le dernier commit de implicit-branch. Mais il n'y a pas de symbole ->. Le HEAD reste détaché tant que vous ne l'attachez pas avec git checkout implicit-branch (ou git switch implicit-branch). Avant d'attacher HEAD :

 * 310e6b7 (HEAD, implicit-branch) commit from detached head
 | * adce8cb (main) second commit
 |/
 * 3af628f initial commit
Après avoir attaché HEAD : (avec git switch implicit-branch)
 * 310e6b7 (HEAD -> implicit-branch) commit from detached head
 | * adce8cb (main) second commit
 |/
 * 3af628f initial commit

Fusion (Merging)

  • Jusqu'à présent, vous avez appris à créer de nouvelles branches.
  • Les branches sont comme des chemins qui se séparent. À chaque branche créée, vous créez une nouvelle bifurcation.
  • Ensuite, vous allez apprendre à les unir à nouveau. Ce processus s'appelle la fusion (merging).

Fusion sans conflit

  • Dans le cas le plus simple, les changements effectués sur les deux branches ne sont pas en conflit, par exemple parce que les changements affectent des fichiers entièrement distincts.
  • La procédure standard pour fusionner est :
    1. Placez votre HEAD sur la branche où vous souhaitez recevoir / intégrer les changements : git checkout receiving-branch
    2. Fusionnez depuis la branche contenant les changements que vous souhaitez intégrer : git merge branch-with-commits-to-integrate

À vous de jouer

  1. Restaurez le projet de test à son état original.
  2. Créez une nouvelle branche feature.
  3. Ajoutez un nouveau commit à la branche feature.
  4. Placez votre HEAD sur main.
  5. Créez un nouveau fichier toto.md avec le contenu # Hello et enregistrez-le dans un nouveau commit sur main.
  6. Fusionnez la branche feature dans main.
  7. Vérifiez que la fusion a été réussie :
    • Vous devriez toujours avoir le fichier toto.md.
    • Vous devriez aussi voir la version étendue du poème.
    • git adog devrait visualiser la fusion.

Fusion avec résolution automatique de conflits

La fusion avec résolution automatique de conflits se produit lorsque deux branches modifient le même fichier, mais de manière non conflictuelle, c'est-à-dire pas sur la même ligne ou le même élément de code.

À vous de jouer

  1. Restaurez le projet de test à son état original.
  2. Créez une nouvelle branche feature.
  3. Ajoutez un nouveau commit à la branche feature, où la première ligne du poème est modifiée en 1) Oh Keksli, sweet as a nutella kiss,
  4. Placez votre HEAD sur main.
  5. Ajoutez un nouveau commit à la branche main, où la dernière ligne du poème est modifiée en 10) Oh Keksli-sized delight, we all love you. <3
  6. Fusionnez la branche feature dans main.
    • À ce stade, vous pourriez voir un éditeur étrange s'ouvrir.
    • Il s'agit de VIM, un éditeur de texte puissant, mais peu intuitif.
    • Pour quitter l'éditeur, tapez cette séquence : ESC : w q ENTER (pour write et quitter l'éditeur).
  7. Vérifiez que la fusion a été réussie :
    • Inspectez le message de sortie de git pour un message de succès.
    • Inspectez le contenu du fichier keksli.md et vérifiez que la première et la dernière ligne du poème ont été modifiées.
    • git adog devrait visualiser la fusion.

Fusion avec conflit

  1. Restaurez le projet de test à son état original.
  2. Créez une nouvelle branche feature.
  3. Ajoutez un nouveau commit à la branche feature, où la première ligne du poème est modifiée en "1) Oh Keksli, sweet as a nutella kiss,"
  4. Placez votre HEAD sur main.
  5. Ajoutez un nouveau commit à la branche main, où la première ligne du poème est modifiée en "1) Oh Keksli, sweet as a maple syrup kiss,"
  6. Fusionnez la branche feature dans main.
  7. Vérifiez que vous obtenez un message CONFLICT :

    Auto-merging keksli.md
    CONFLICT (content): Merge conflict in keksli.md
    Automatic merge failed; fix conflicts and then commit the result.
    
    8. Inspectez le fichier keksli.md. Vous devriez voir des marqueurs de version :
    # Keksli, the Little Piece of Cake
    
    <<<<<<< HEAD
    1) Oh Keksli, sweet as a maple syrup kiss,
    =======
    1) Oh Keksli, sweet as a nutella kiss,
    >>>>>>> feature
    2) A tiny fluff of gentle bliss.
    3) With eyes that sparkle, soft and bright,
    4) You bring the world pure joy and light.
    ...
    
    9. Résolvez le conflit de fusion en supprimant les marqueurs de version et en décidant quelle version conserver. Ensuite, enregistrez le fichier et créez un commit final "resolve".

Lorsque l'une des branches est main, fusionnez toujours d'abord dans l'autre branche. Puis fusionnez à nouveau dans main.

  • L'intérêt des branches est généralement de développer une fonctionnalité sans endommager main pendant que le travail est en cours.
  • Lorsque vous êtes prêt à fusionner, fusionnez toujours d'abord main dans la branche feature, la fusion peut échouer et laisser votre dépôt dans un état problématique. Une procédure plus sûre est :
  • Fusionnez main dans feature.
  • Si nécessaire, résolvez les conflits et créez un nouveau commit "résolu".
  • Fusionnez ensuite feature dans main.
    ---
    title: "Merging first main into feature, then merging back"
    ---
    gitGraph:
        commit id: "1"
        commit id: "2"
        branch "feature"
        checkout "feature"
        commit id: "3"
        checkout "main"
        commit id: "4"
        checkout "feature"
        merge "main" type: REVERSE
        commit id: "manual resolve"
        checkout "main"
        merge "feature" type: HIGHLIGHT

Suppression de branches

  • Si vous ne supprimez jamais les branches que vous créez, votre graphe git finira par être encombré avec le temps.
  • Idéalement, les branches doivent être supprimées du graphe une fois qu'elles ne sont plus nécessaires.
  • Pour supprimer définitivement une branche, vous devez la supprimer localement et à distance.

Local

  • Supprimer une branche localement correspond à supprimer une branche du graphe local.
  • Révisez le contenu du cours et cherchez comment supprimer une branche locale.

À vous de jouer

  1. Ajoutez un autre commit à votre branche feature.
  2. Supprimez la dernière branche feature de votre dépôt.
    • Tentez d'abord la commande git branch --delete feature.
    • Pourquoi la commande est-elle rejetée ? Identifiez deux stratégies pour supprimer la branche malgré tout.
  3. Utilisez git adog pour vérifier qu'elle a disparu.
  4. Utilisez git branch -a pour vérifier qu'elle n'est plus listée.

Remote

  • Supprimer une branche à distance correspond à supprimer une branche du graphe distant (serveur).
  • Supprimer une branche distante n'a de sens que s'il y a un dépôt distant associé à votre dépôt local.

À vous de jouer

  1. Allez sur la page Gitlab de l'UQAM et reliez votre dépôt local à un serveur distant :
    • Connectez-vous à Gitlab.
    • Cliquez sur le symbole + en haut à gauche, puis nouveau projet / dépôt.
      1. Sélectionnez : Créer un projet vide.
      2. Sélectionnez un nom pour votre projet, par exemple lab08.
      3. Décochez : Initialiser le dépôt avec un README.
      4. Cliquez sur : Créer un projet.
    • Entrez la commande pour relier votre dépôt local au serveur distant : (remplacez max par votre nom d'utilisateur)
      git remote add origin git@gitlab.info.uqam.ca:max/lab08.git
  2. Utilisez git branch -a, inspectez la liste des branches locales et distantes.
  3. Créez une nouvelle branche locale feature.
  4. Ajoutez au moins un commit à la branche feature.
  5. Poussez la branche vers le dépôt distant, avec :
    git push --set-upstream origin main
  6. Découvrez comment pousser la branche feature vers le dépôt distant.
  7. Vérifiez avec git branch -a que les deux branches existent sur le dépôt distant.
    • Connectez-vous également au site Gitlab de l'UQAM et inspectez la page du projet. Pouvez-vous repérer les deux branches ?
  8. Supprimez la branche distante feature avec :
    git push -d origin feature
  9. Utilisez git branch -a à nouveau pour vérifier que la branche feature a disparu.

Étiquetage (Tagging)

Les étiquettes sont des identifiants supplémentaires lisibles par l'homme pour les commits.

Ajout d'étiquettes

Les étiquettes sont toujours d'abord créées localement, puis poussées vers le serveur.

À vous de jouer

  1. Réinitialisez votre projet de test.
  2. Créez une étiquette pour le commit initial sur la branche principale. Illustration :
    ---
    title: "Tagged initial commit"
    ---
    gitGraph:
    commit id: "1" type: HIGHLIGHT tag: "v1.0"
  3. Poussez l'étiquette vers le serveur.
  4. Trouvez les étiquettes.
    • Inspectez git adog. Pouvez-vous trouver l'étiquette ?
    • Connectez-vous au serveur GitLab de l'UQAM et accédez à votre projet. Pouvez-vous trouver l'étiquette ?

Cherry Picking

Le cherry-picking vous permet de fusionner avec un commit spécifique plutôt que de fusionner avec toute une branche.

Cherry-pick d'une seule ligne du poème

  • Dans cet exercice, vous effectuerez plusieurs modifications sur le poème keksli dans une branche feature.
  • Ensuite, vous fusionnerez exactement l'un de ces changements dans la branche main.
  • Illustration :
    ---
    title: "Cherry picking a single commit from a feature branch"
    ---
    gitGraph:
        commit id: "1"
        branch "feature"
        checkout "feature"
        commit id: "2"
        commit id: "3"
        commit id: "4"
        checkout "main"
        commit id: "5"
        checkout "main"
        cherry-pick id: "3"

À vous de jouer

  1. Réinitialisez votre projet de test à son état original.
  2. Créez une branche feature.
  3. Effectuez trois commits consécutifs sur la branche feature :
    1. Changez la première ligne du poème en 1) Oh Keksli, sweet as a cinnamon kiss,. Faites un commit sur feature.
    2. Changez la deuxième ligne du poème en 2) A tiny fluff of chocolate bliss.. Faites un commit sur feature.
      Notez l'ID du commit du changement de la deuxième ligne.
    3. Changez la troisième ligne du poème en 3) With eyes that glitter, soft and bright,. Faites un commit sur feature.
  4. Placez le HEAD de nouveau sur main. Faites un nouveau commit.
  5. Effectuez un cherry-pick du changement de la deuxième ligne du poème sur main, en utilisant l'ID du commit du changement de la deuxième ligne.
  6. Vous recevrez un conflit de fusion. Inspectez et résolvez le conflit. Faites un commit final pour résoudre le conflit.
  7. Inspectez le graphe avec git adog.
Y a-t-il quelque chose de particulier dans la sortie de git adog ?

Oui ! La représentation du graphe ne montre pas le cherry-pick ! En effet, git log ne montre jamais les cherry-picks, seulement les fusions de branches complètes.

Rebase

Le rebase permet de redresser le graphe des commits git a posteriori.

Rebase d'une branche feature

  • En général, le rebase est appliqué avant de fusionner une branche feature dans main.
  • En faisant d'abord un rebase de main dans feature, il n'y a aucun risque d'endommager la branche main (tout comme lorsque vous fusionnez main dans feature).
  • Le rebase crée une répétition de chaque commit sur feature, basée sur le commit le plus récent de main.
  • Cela transforme le graphe, de sorte que les commits de main et feature ne se produisent plus de manière concurrente, mais séquentielle.
  • La fusion finale est toujours garantie de fonctionner, car tous les conflits ont déjà été résolus pendant le rebase.

À vous de jouer

  1. Réinitialisez votre projet de test.
  2. Créez une branche feature.
  3. Effectuez trois commits consécutifs sur la branche feature.
  4. Attachez votre HEAD à main.
  5. Changez la ligne du titre du poème en #Keksli, the Rebased piece of Cake.
  6. Attachez votre HEAD à feature à nouveau. Votre graphe des commits devrait maintenant ressembler à ceci :
    ---
    title: "Feature branch lacks recent main commit"
    ---
    gitGraph:
        commit id: "1"
        branch "feature"
        checkout "feature"
        commit id: "2"
        commit id: "3"
        commit id: "4"  type: HIGHLIGHT
        checkout "main"
        commit id: "5"
  7. Effectuez un rebase de la branche feature, pour qu'elle parte du commit le plus récent de main, en utilisant git rebase main.
  8. Fusionnez feature dans main.
  9. Inspectez la structure du graphe avec git adog. Vérifiez qu'elle correspond à :
    ---
    title: "Feature branch rebased and merged to main"
    ---
    gitGraph:
        commit id: "1"
        commit id: "5"
        branch "feature"
        checkout "feature"
        commit id: "2R"
        commit id: "3R"
        commit id: "4R"
        checkout "main"
        merge "feature"  type: HIGHLIGHT

Rebase au lieu de fusionner

Prenez l'habitude de faire un rebase plutôt que de fusionner. Un graphe qui ne montre pas de progrès concurrent est plus facile à maintenir et à naviguer. Le rebase transforme efficacement le graphe pour ressembler à une histoire où chaque fonctionnalité a été développée instantanément, sans concurrence avec la branche principale.