TP5 : Voyager entre univers parallèles
Objectifs¶
Le but de ce TP est de comprendre les commandes et concepts suivants :
La fusion des branches divergentes¶
La fusion
La fusion est très fréquente dans Git.
Lorsque vous “synchronisez” votre Local Repo et votre Remote Repo (push et pull), vous effectuez en réalité des fusions automatiques entre différentes versions de vos fichiers.
La plupart de ces fusions sont automatiques : lorsque vous êtes en avance ou en retard d’un certain nombre de commits sur une même branche, Git comprend qu’il suffit d’appliquer les commits manquants pour fusionner les différentes versions des fichiers.
Par exemple, je suis un étudiant qui a terminé son TP. J’effectue un push de mon Local Repo, depuis une machine de l’IUT, vers mon Remote Repo sur les serveurs du GitLab de l’IUT : mon Local Repo est en avance sur le Remote Repo et Git applique les nouveaux changements au Remote Repo, qui est en retard.
Pour simplifier, les hash de commits sont ici remplacés par des lettres majuscules.
main est en avance sur origin/main de 2 commits. Git comprend donc qu’il suffit d’appliquer les 2 derniers commits à origin/main pour fusionner les deux versions.
Quand une fusion ne peut pas se faire automatiquement, nous obtenons des branches divergentes avec des fusions manuelles à effectuer et des conflits potentiels à résoudre.
Les branches divergentes
Nous avons observé qu’une fusion de deux historiques peut se faire automatiquement lorsque le graphe de commits d’une branche contient entièrement l’autre graphe comme sous-graphe : il suffit d’ajouter les sommets et arêtes manquants, autrement dit d’appliquer les commits manquants à la branche en retard.
Lorsque aucun des deux graphes ne contient entièrement l’autre, nous obtenons des branches divergentes. Prenons l’exemple suivant :
main et origin/main ont 3 commits en commun : A, B et C. Malheureusement, dans main, deux commits D et E ont été appliqués après C, alors que dans origin/main, le commit F a été appliqué après C : aucun des deux historiques ne contient entièrement l’autre et aucune fusion automatique n’est possible.
Une fusion manuelle doit donc être effectuée (git merge) et nous décidons alors comment elle doit se faire.
Ces “nouveaux” changements (G), qui suivent à la fois E et F, sont ensuite ajoutés au graphe de commits de main par un add puis un commit.
Ce nouveau graphe contient désormais entièrement l’historique de origin/main et peut donc être fusionné automatiquement avec git push.
Merge sans conflit¶
D’abord, cherchons à reproduire des branches divergentes.
Créez un répertoire
TP5/avec un fichiermerge-without-conflict.txtcontenant trois lignes de texte arbitraire sur les lignes 1, 3 et 5.Synchronisez votre Local Repo avec le Remote Repo.
Localement, modifiez la troisième ligne de texte du fichier
merge-without-conflict.txt.Effectuez un commit de ce changement sans faire de push.
Sur le Web IDE, modifiez la cinquième ligne de texte du fichier
merge-without-conflict.txtafin de simuler un changement effectué sur un autre poste ou par un collaborateur.Effectuez un commit de ce changement.
Vous devez maintenant avoir deux versions de merge-without-conflict.txt.
Par exemple, sur le Local Repo, vous avez :
First line of text
Third line of text
Fifth lineSur le Remote Repo, vous avez :
First line of text
Third line
Fifth line of textEffectuez un
git pushet observez l’erreur.
Git détecte qu’il y a eu des changements sur le Remote Repo qui ne sont pas présents sur notre Local Repo et nous propose d’effectuer un git pull pour mettre d’abord notre Local Repo à jour.
Effectuez un
git pullet observez l’erreur. Avez-vous compris pourquoi nous avons créé des branches divergentes ? Sinon, appelez votre encadrant.Exécutez
git graphet observez.
Nous devons maintenant procéder à une fusion manuelle.
Exécutez la commande suivante.
git merge origin/maingit merge
git mergePour fusionner deux branches (ici, origin/main vers main), nous nous plaçons sur la branche cible (ici, main) et nous exécutons la commande git merge <branche source> (ici, origin/main).
Cette fusion est considérée comme une fusion (automatique) sans conflit car les modifications provenant des deux branches se trouvent sur des blocs de lignes distinctes.
Git a effectué un add automatiquement et vous propose d’écrire un message de commit pour l’opération de fusion, avec un message auto-généré déjà pré-rempli. Vous pouvez modifier ce message si vous le souhaitez.
Une fois le commit effectué, vérifiez que le contenu de
merge-without-conflict.txtcombine à la fois celui deorigin/mainetmain.
Par exemple :
First line of text
Third line of text
Fifth line of textExécutez
git graphet observez.Exécutez
git pushpour synchroniser votre Local Repo avec le Remote Repo.
Merge avec conflit¶
Dans l’exemple précédent, nous avons vu une fusion où les modifications apportées aux deux versions d’un même fichier ne se chevauchaient pas, ce qui a permis à Git de fusionner automatiquement les changements.
Cette fois, nous allons à nouveau créer et fusionner des branches divergentes mais avec des modifications en conflit.
Créez un fichier
merge-with-conflict.txtdansTP5/avec une seule ligne de texte arbitraire.Synchronisez votre Local Repo avec le Remote Repo.
Localement, ajoutez une deuxième ligne de texte au fichier
merge-with-conflict.txt.Effectuez un commit de ce changement sans faire de push.
Sur le Web IDE, ajoutez une autre deuxième ligne de texte à
merge-with-conflict.txt.Effectuez un commit de ce changement.
Vous devez maintenant avoir deux versions de merge-with-conflict.txt.
Par exemple, sur le Local Repo, vous avez :
First line of text
Second line of textSur le Remote Repo, vous avez :
First line of text
Another second line of textEn effectuant
git pushougit pull, vous recevrez les mêmes erreurs qu’avant concernant les branches divergentes.Exécutez
git graphet observez.
Nous devons maintenant procéder à une fusion manuelle.
Exécutez
git merge origin/main.
Cette fois, la fusion automatique échoue car les deux versions de merge-with-conflict.txt possèdent des modifications différentes sur la même ligne.
Git vous signale qu’il faut résoudre le(s) conflit(s) dans merge-with-conflict.txt puis de faire un commit de cette résolution.
Exécutez
git statuset observez.Ouvrez
merge-with-conflict.txtdans un éditeur de texte. Vous devez voir les lignes suivantes :
First line of text
<<<<<<< HEAD
Second line of text
=======
Another second line of text
>>>>>>> origin/mainLa syntaxe de conflit
Entre <<<<<<< HEAD et ======= se trouve la version courante, là où se situe HEAD dans le graphe de commits (ici, au dernier commit de la branche cible main) et entre ======= et >>>>>>> <nom de la branche source> se trouve la version venant de la branche source dans notre fusion (ici, origin/main).
Remplacez ce bloc de texte (entre
<<<<<<< HEADet>>>>>>> origin/main) par la version finale de votre choix (qui peut différer des deux versions proposées). Par exemple :
First line of text
Merged line of textCette fois, notre fusion (manuelle) avec conflit ne vient pas avec un add et commit automatiques.
Add, commit et push cette résolution de conflit.
Exécutez
git graphet observez.
Les branches¶
Les branches divergentes sont des branches créés par accident mais nous pouvons également créer des branches volontairement pour mieux gérer notre projet (par exemple, pour suivre les principes de travail collaboratif dans votre projet SAÉ).
Créer et voyager entre branches¶
Créez une branche
experiment/branchen exécutant la commande suivante.
git branch experiment/branchCréation d’une branche
La commande git branch <nom de la branche> permet de créer une branche.
Changez de branche en exécutant la commande suivante.
git checkout experiment/branchChanger de branche
La commande git checkout <nom de la branche> permet d’aller au dernier commit de <nom de la branche> dans le graphe d’historique.
Créez un fichier
experiment-branch.txtdansTP5/avec une ligne de texte arbitraire.Effectuez un add et un commit de ce changement.
Synchronisez cette nouvelle branche avec votre Remote Repo en exécutant la commande suivante.
git push --set-upstream origin experiment/branchRappel git push --set-upstream
git push --set-upstreamLa première fois que vous effectuez un push sur une nouvelle branche, Git vous demande de spécifier quelle branche vous souhaitez “pousser” votre branche courante du Local Repo vers. Ici, nous disons simplement que nous souhaitez pousser notre branche locale experiment/branch vers la branche portant le même nom sur le Remote Repo. Nous n’avons besoin de le faire qu’une seule fois par branche et nous pouvons utiliser git push et git pull dans le futur sans spécifier la branche.
Si vous avez déjà configuré la création de branche automatique sur le Remote Repo quand nous faisons un git push avec la commande suivante :
git config --global push.autoSetupRemote truealors vous n’avez pas besoin de git push --set-upstream mais seulement de git push.
Revenez sur la branche
mainen exécutant la commande suivante.
git checkout mainObservez que experiment-branch.txt n’apparaît pas dans la branche main car le commit correspondant à la création de ce fichier est dans la branche experiment/branch.
Créez un fichier
main-branch.txtdansTP5/avec une ligne de texte arbitraire.Effectuez un add et un commit de ce changement.
Exécutez
git graphet observez.
Fusionner deux branches¶
Utilisez ce que vous avez appris pour fusionner la branche
experiment/branchvers la branchemain.Exécutez
git graphet observez.
Créer une branche à partir d’un commit passé¶
Parfois, il est utile de créer une branche à partir d’un commit dans le passé
Revenez à un ancien commit grâce à
git checkout.Exécutez
git statuset observez que vous êtes en état detached HEAD.
Nous pouvons maintenant créez une branche à partir de ce commit et effectuez des changements qui seront sauvegardés dans notre historique.
Créez une branche
experiment/old-commitgrâce àgit branch.Changez de branche grâce à
git checkout.Effectuez des changements puis add et commit dans la nouvelle branche.
Exécutez
git graphet observez.Synchronisez cette nouvelle branche avec votre Remote Repo.
Lister les branches¶
Listez les branches de votre Local Repo avec la commande suivante.
git branchVous devez voir main, experiment/branch et experiment/old-commit.
Listez les branches de votre Local Repo et votre Remote Repo avec la commande suivante.
git branch -aVous devez voir six branches avec les versions distantes des branches locales.
Listez les branches de votre Local Repo qui sont déjà fusionnées avec la branche
mainavec la commande suivante.
git branch --merged mainVous pouvez aussi combiner les options -a et --merged avec la commande suivante.
git branch -a --merged mainListez les branches de votre Local Repo qui ne sont pas encore fusionnées avec la branche
mainavec la commande suivante.
git branch --no-merged mainSimilairement, vous pouvez aussi combiner les options -a et --no-merged.
git branch -a --no-merged mainStash sur plusieurs branches¶
Nous avons vu comment stash du travail en cours sur une branche avant de checkout des commits dans le passé ou une autre branche. Naturellement, nous voulons aussi pouvoir stash du travail en cours sur plusieurs branches différentes sans les mélanger.
Sur la branche
main, effectuez quelques changements sans faire de add ou de commit.Stash votre travail en cours avec la commande suivante.
git stash push -a -m "main: work in progress"git stash push -m "<message>"
git stash push -m "<message>"Nous avons vu que git stash -a permet de mettre toutes les changements non commité dans une pile.
git stash push -a -m "<message>" permet non seulement de rajouter ces changements dans la pile mais aussi de les associer au message "<message>".
Refaites la même chose sur les branches
experiment/branchetexperiment/old-commitavec les messages"experiment/branch: work in progress"et"experiment/old-commit: work in progress"respectivement.Regardez l’état de la pile de stash avec la commande suivante.
git stash listVous pouvez observez un numéro pour chaque stash avec le message associé.
Revenons à la branche
mainet réappliquons les changements demainavec la commande suivante.
git stash apply stash@{<le bon numéro>}git stash apply stash@{<numéro>}
git stash apply stash@{<numéro>}git stash apply stash@{<numéro>} permet de réappliquer le travail en cours du stash avec le numéro correspondant.
Revérifiez l’état de la pile de stash. Observez que le stash de la branche
mainest toujours présente.
git stash pop vs. git stash apply
git stash pop vs. git stash applygit stash pop permet d’appliquer le travail du premier stash stash@{0} et de le supprimer de la pile de stash.
Quand nous avons des stashes venant de plusieurs branches, il est souvent plus prudent d’utiliser git stash apply plutôt que git stash pop puis de supprimer le stash correspondant quand nous n’avons plus besoin de le reprendre.
Supprimez le stash contenant du travail de la branche
mainavec la commande suivante.
git stash drop stash@{<le bon numéro>}Revérifiez l’état de la pile de stash et observez que les numéros des stashes ont changé.
Refaites les questions de 5. à 7. pour les deux autres branches pour vous familiariser avec les commandes autour de
git stash.
Supprimer des branches¶
Nous ne pouvons pas supprimer une branche quand nous sommes sur la branche en question donc revenez d’abord sur la branche main.
Pour supprimer une branche locale qui est déjà fusionnée avec la branche
main(par exempleexperiment/branch), utilisez la commande suivante.
git branch -d <nom de la branche>Pour supprimer une branche locale qui n’est pas encore fusionnée avec la branche
main(par exempleexperiment/old-commit), utilisez la commande suivante.
git branch -D <nom de la branche>Pour supprimer les mêmes branches sur le Remote Repo, utilisez la commande suivante.
git push origin --delete <nom de la branche>Par exemple :
git push origin --delete experiment/branchRevenez aux objectifs et cochez les points que vous avez maîtrisés. Entraînez-vous sur les commandes et les notions que vous n’avez pas encore bien comprises. Faites appel à votre encadrant si besoin.