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
- Créez un nouveau dossier vide sur votre bureau. (Ou ailleurs selon votre préférence)
- Ouvrez un terminal et naviguez à l'intérieur du dossier nouvellement créé.
- 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 :
-
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 :
- 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
- Créez une nouvelle branche
extended-poem
, en utilisantgit checkout -b
- Remarque : la commande
checkout
place automatiquement votre HEAD sur la nouvelle branche. Pas besoin de changer manuellement.
- Remarque : la commande
- Modifiez le poème et créez un nouveau commit.
- 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
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é quegit checkout
, car il s'agit d'une variante decheckout
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.
- Avec
À vous de jouer
- Revenez à la branche
main
. - Modifiez le poème et créez un nouveau commit.
- 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é.
- 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 !)
- 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
- Réinitialisez le dépôt de test à son état original.
- 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é.
- Inspectez le graphe du dépôt avec
- 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.
- Inspectez à nouveau avec
- 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é.
- Inspectez à nouveau votre graphe avec
- 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.
- Revenez au commit laissé derrière, en faisant un
checkout
(de l'ID que vous avez noté). - 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
.
- Inspectez le graphe du dépôt une fois de plus avec
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
git switch implicit-branch
)
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 :
- Placez votre HEAD sur la branche où vous souhaitez recevoir / intégrer les changements :
git checkout receiving-branch
- Fusionnez depuis la branche contenant les changements que vous souhaitez intégrer :
git merge branch-with-commits-to-integrate
- Placez votre HEAD sur la branche où vous souhaitez recevoir / intégrer les changements :
À vous de jouer
- Restaurez le projet de test à son état original.
- Créez une nouvelle branche
feature
. - Ajoutez un nouveau commit à la branche
feature
. - Placez votre HEAD sur
main
. - Créez un nouveau fichier
toto.md
avec le contenu# Hello
et enregistrez-le dans un nouveau commit surmain
. - Fusionnez la branche
feature
dansmain
. - 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.
- Vous devriez toujours avoir le fichier
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
- Restaurez le projet de test à son état original.
- Créez une nouvelle branche
feature
. - Ajoutez un nouveau commit à la branche
feature
, où la première ligne du poème est modifiée en1) Oh Keksli, sweet as a nutella kiss,
- Placez votre HEAD sur
main
. - Ajoutez un nouveau commit à la branche
main
, où la dernière ligne du poème est modifiée en10) Oh Keksli-sized delight, we all love you. <3
- Fusionnez la branche
feature
dansmain
.- À 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).
- 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.
- Inspectez le message de sortie de
Fusion avec conflit
- Restaurez le projet de test à son état original.
- Créez une nouvelle branche
feature
. - 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," - Placez votre HEAD sur
main
. - 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," - Fusionnez la branche
feature
dansmain
. -
Vérifiez que vous obtenez un message
CONFLICT
:8. Inspectez le fichierAuto-merging keksli.md CONFLICT (content): Merge conflict in keksli.md Automatic merge failed; fix conflicts and then commit the result.
keksli.md
. Vous devriez voir des marqueurs de version : 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
dansfeature
. - Si nécessaire, résolvez les conflits et créez un nouveau commit "résolu".
- Fusionnez ensuite
feature
dansmain
.
--- 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
- Ajoutez un autre commit à votre branche
feature
. - 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.
- Tentez d'abord la commande
- Utilisez
git adog
pour vérifier qu'elle a disparu. - 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
- 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, puisnouveau projet / dépôt
.- Sélectionnez :
Créer un projet vide
. - Sélectionnez un nom pour votre projet, par exemple
lab08
. - Décochez :
Initialiser le dépôt avec un README
. - Cliquez sur :
Créer un projet
.
- Sélectionnez :
- 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
- Utilisez
git branch -a
, inspectez la liste des branches locales et distantes. - Créez une nouvelle branche locale
feature
. - Ajoutez au moins un commit à la branche
feature
. - Poussez la branche vers le dépôt distant, avec :
git push --set-upstream origin main
- Découvrez comment pousser la branche
feature
vers le dépôt distant. - 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 ?
- Supprimez la branche distante
feature
avec :
git push -d origin feature
- Utilisez
git branch -a
à nouveau pour vérifier que la branchefeature
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
- Réinitialisez votre projet de test.
- 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"
- Poussez l'étiquette vers le serveur.
- 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 ?
- Inspectez
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 branchefeature
. - 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
- Réinitialisez votre projet de test à son état original.
- Créez une branche
feature
. - Effectuez trois commits consécutifs sur la branche
feature
:- Changez la première ligne du poème en
1) Oh Keksli, sweet as a cinnamon kiss,
. Faites un commit surfeature
. - Changez la deuxième ligne du poème en
2) A tiny fluff of chocolate bliss.
. Faites un commit surfeature
.
Notez l'ID du commit du changement de la deuxième ligne. - Changez la troisième ligne du poème en
3) With eyes that glitter, soft and bright,
. Faites un commit surfeature
.
- Changez la première ligne du poème en
- Placez le HEAD de nouveau sur
main
. Faites un nouveau commit. - 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. - Vous recevrez un conflit de fusion. Inspectez et résolvez le conflit. Faites un commit final pour résoudre le conflit.
- 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
dansfeature
, il n'y a aucun risque d'endommager la branchemain
(tout comme lorsque vous fusionnezmain
dansfeature
). - Le rebase crée une répétition de chaque commit sur
feature
, basée sur le commit le plus récent demain
. - Cela transforme le graphe, de sorte que les commits de
main
etfeature
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
- Réinitialisez votre projet de test.
- Créez une branche
feature
. - Effectuez trois commits consécutifs sur la branche
feature
. - Attachez votre HEAD à
main
. - Changez la ligne du titre du poème en
#Keksli, the Rebased piece of Cake
. - 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"
- Effectuez un rebase de la branche
feature
, pour qu'elle parte du commit le plus récent demain
, en utilisantgit rebase main
. - Fusionnez
feature
dansmain
. - 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.