Dans la première partie de cette mini-série Apprendre Git, on a découvert l’outil de contrôle de version Git.
On l’a installé, configuré brièvement et on a fait nos premiers commits. On a même vu comment restaurer des fichiers en cas de souci.
Dans cette deuxième partie de Apprendre Git, on va parler des branches.
Qu’est-ce qu’une branche ?
Les branches sont un concept fondamental de Git. Pour faire simple, une branche est simplement une version indépendante de votre projet avec son propre historique de commits.
Une branche se crée à partir d’un certain commit d’une certaine branche (en général le dernier) et permet de développer sa fonctionnalité en toute sécurité car les commits suivants que vous y ferez ne seront pas mélangés avec les commits de la branche principale ou des autres branches.
Vous pouvez créer des branches à partir de n’importe quelle autre branche. Un projet Git a donc une structure arborescente, comme le montre ce schéma.
Sur le schéma, on peut voir que la branche 1 a été créée à partir du commit B de la branche master
, et la branche 2 a été crée à partir du commit C. La branche 2 aurait aussi pu être créée depuis le commit C1 de la branche 1, par exemple.
A partir de ces moments, les historiques de commits des branches divergent. La branche master
contient les commits A, B, C et D, alors que la branche 1 contient les commits A, B, C1, D1, E1 et la branche 2 contient les commits A, B, C, D2. Aucune des deux branches 1 ou 2 ne contient le commit D.
Manipuler les branches avec git branch
Retournons sur notre projet, et voyons où nous en sommes avec git status
.
Notre copie de travail est propre.
Maintenant, imaginons que nous voulons tester plusieurs patterns de navigation. On ne veux pas que chaque essai pollue la branche master
, on va donc créer une branche pour y développer notre fonctionnalité.
Pour créer une branche, on utilise simplement git branch [nom_de_ma_branche]
.
On va donc créer notre première branche :
git branch inline-navigation
Puis, on va lister les branches disponibles avec git branch --list
ou le raccourci git branch -l
ou encore git branch
, tout simplement. Voilà ce qu’on obtient :
git branch
est une commande assez simple qui permet de créer des branches ou de les lister, mais aussi de renommer des branches (avec -m
, pour move) ou de supprimer des branches (avec -d
pour delete).
On a bien créé notre branche. Par contre, vous remarquerez qu’on est toujours sur la branche master
, qui a une petite étoile devant et qui est surlignée.
Si on modifie notre code, c’est sur la branche master
qu’on préparera un commit. Nous, on veut travailler sur la branche inline-navigation
.
Naviguer avec git checkout
Maintenant, on va basculer sur notre nouvelle branche en utilisant git checkout
.
git checkout inline-navigation
Voilà, on est bien sur notre branche inline-navigation
. Maintenant, on peut travailler en toute sécurité, en étant sûr de ne pas mettre notre code dans master
, qui est normalement censé être la dernière version stable de notre code.
Alors, techniquement, on aurait pu commencer à travailler dans master
, puis utiliser git branch
et git checkout
pour changer de branche. Nos modifications auraient été conservées dans la nouvelle branche.
Par contre, naviguer depuis une branche avec des modifications non-validées vers une autre branche existante est plus compliqué. Dans ce cas, Git nous prévient en nous demandant de remiser (mettre de côté avec git stash
) ou abandonner nos modifications avant de changer.
Dans tous les cas, le plus prudent est quand même de changer de branche avant de commencer les développements pour éviter les commits accidentels.
A noter aussi : on peut utiliser un raccourci pour basculer dans une branche ET la créer simultanément : git checkout -b [nom-de-la-branche]
. C’est très pratique.
Notre premier commit
On va simplement ajouter une liste de liens, qu’on va placer en display: inline-block;
<!-- in index.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
...
</head>
<body>
<main>
<div class="wrapper">
<h1>Introduction à Git</h1>
<nav class="main-navigation">
<ul>
<li><a href="/about">About</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</div>
</main>
</body>
</html>
h1 {
text-align: center
}
nav li {
display: inline-block;
}
On va valider ces changements.
git add .
git commit -m "Added basic inline navigation"
Naviguer avec git switch
Maintenant, vous pouvez vous amuser à basculer de la branche inline-navigation
vers master
et vice-versa, et voir votre code se mettre à jour automatiquement.
Vous remarquerez que j’ai utilisé git switch
et non git checkout
. Techniquement, il n’y a pas de différence. Les deux commandes permettent de mettre à jour votre copie de travail et de basculer sur une autre branche.
En fait, git checkout
permet de changer de branche ou de restaurer des fichiers. Pour plus de simplicité, les développeurs du logiciel ont décidé de diviser ces deux fonctionnalité en git switch
et git restore
dont on a parlé dans la première partie.
D’ailleurs, je n’utilise plus git checkout
maintenant. J’utilise exclusivement git switch
ou git restore
.
En coulisses, git switch
(ou git checkout
) met à jour votre copie de travail au dernier commit de la branche de destination (la tête ou HEAD), et change de branche.
Fusionner vos branches avec git merge
Imaginons que nous avons testé plusieurs types de navigation dans plusieurs branches, et qu’au final, votre responsable vous annonce que c’est la navigation en ligne de la branche inline-navigation
qui est validée.
Maintenant, on doit l’incorporer dans le code en production représenté par la branche master
. On va donc fusionner notre branche inline-navigation
dans notre branche master
.
Pour faire cela, on se place dans la branche de destination master
en utilisant git switch
et on utilise la commande git merge
pour fusionner une autre branche dans la branche courante.
git switch master
git merge inline-navigation
git merge
a réussi sans souci à fusionner nos deux branches. Git a détecté 7 lignes ajoutées dans index.html
et 3 dans style.css
. Aussi, la commande a automatiquement ajouté le commit de inline-navigation
contenant toutes nos modifications, comme on peut le voir si on fait un petit git log
.
Le log nous indique même quelle branche a été fusionnée, et quel(s) commit(s) a(ont) été appliqué(s).
Aucun changement n’a été fait sur la branche master
pendant que nous travaillions sur inline-navigation
, donc la fusion a pu se dérouler sans souci, car elle consistait simplement à ajouter des commits.
On peut représenter notre opération comme ceci :
Ce type de fusion est appellé un fast-forward (avance-rapide). Vous pouvez d’ailleurs en voir la mention sur le résultat de la commande git merge
.
Pour simplifier, on peut dire que notre branche master
a “rattrapé” les commits qu’elle avait “en retard”. Par défaut Git va toujours essayer de faire un fast-forward quand vous allez tenter de fusionner.
Fusionner des changements dans les deux branches
Le fast-forward comme précédemment, c’est le cas le plus simple. Si vous travaillez seul sur un dépôt local comme on est en train de le faire, vous serez souvent dans cette situation. C’est-à-dire que tant que vous travaillez sur une branche, aucune modification n’est effectuée dans les autres.
Maintenant, si vous collaborez sur un projet à partir d’un dépôt distant, cette situation risque d’être plus rare.
En effet, vos collègues vont probablement faire la même chose que vous de leur côté : créer une branche à partir de master
, faire leurs modifications et fusionner leur branche dans master
. Voire travailler dans master
directement !
Autrement dit, vos collègues ne vont pas attendre que vous ayez fini de travailler sur votre branche pour modifier ou corriger des petits bugs sur master
.
On verra dans la troisième partie de cette série comment travailler avec les dépôts distants, ne vous inquiétez pas.
Pour le moment, on va imaginer que vous continuez de travailler sur votre navigation, mais qu’un autre collègue (ou vous!) a fait une modification sur master
.
Plus précisément, il a ajouté un élément de menu: un lien vers la page produits.
<!-- in index.html -->
...
<nav class="main-navigation">
<ul>
<li><a href="/about">About</a></li>
<li><a href="/products">Products</a></li> <!-- new -->
<li><a href="/services">Services</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
...
Ce lien est donc présent dans le dernier commit (HEAD) de la branche master
, mais pas dans celui de la branche inline-navigation
.
Dans la branche inline-navigation
, on va simplement ajouter quelques styles.
...
nav ul {
text-align: center;
padding: 0;
}
nav li {
display: inline-block;
text-transform: uppercase;
font-size: larger;
margin-left: 8px;
}
nav li:first-of-type {
margin-left: 0;
}
Puis, on va valider ces changements. On a vu que pour ajouter tous nos fichiers modifiés en staging, on peut utiliser git add .
.
Astuce ! On peut aussi combiner cet ajout au staging ET notre commit en une seule commande en utilisant les flags -am
, comme ceci :
git commit -am "Added more navigation styles"
Maintenant, on a des modifications différentes dans master
ET dans inline-navigation
.
On va fusionner nos changements dans master
. On se place donc dans master
avec git switch
et on va fusionner notre branche inline-navigation
avec git merge
.
Ici, git merge
m’a demandé de donner un message de commit, car il a automatiquement crée un commit de fusion. Mon système m’a juste ouvert l’éditeur de texte nano
pour éditer mon message.
Un commit a été créé et si on regarde l’état de notre code, on a bien notre nouvel élément de navigation ET nos nouveaux styles.
Tout a fonctionné sans souci !
Dans notre cas, Git a comparé les commits en tête des branches avec le commit ancêtre commun le plus proche, pour déterminer l’ordre et la nature des modifications. Il a détecté du code nouveau dans chacune des branches et a pu fusionner nos modifications sans souci, car ces modifications ne concernent pas les mêmes lignes dans les mêmes fichiers.
Vous remarquerez que Git nous annonce avoir utilisé une stratégie ort
. C’est simplement la stratégie récursive par défaut. On ne parlera pas des stratégies dans cette série, car dans 99% des cas vous n’avez pas à vous en soucier. Git choisit toujours la meilleure automatiquement !
On peut schématiser cette opération comme ceci :
Ou alors comme ça :
Résoudre les conflits
Ces deux derniers cas n’ont pas posé de souci. Vu que les modifications effectuées dans les branches étaient bien distinctes.
Mais que se passe-t-il si les modifications dans les deux branches concernent le même code ?
Voyons ça maintenant. Imaginons que dans master
, un collègue ait modifié le titre de la page en Débuter avec Git, et ait validé son changement.
De votre côté, vous avez continué à travailler sur votre navigation, mais avez aussi changé le titre en Apprendre Git.
On a donc le titre Débuter avec Git dans master
et Apprendre Git dans inline-navigation
.
Tentons la fusion. On se place sur la branche master
, et on essaie de fusionner inline-navigation
.
git switch master
git merge --no-commit inline-navigation
Ici, j’utilise le flag --no-commit
pour expliciter à Git de ne pas créer le commit de fusion. On le fera nous-même. C’est utile quand on n’est pas sûr de notre coup ! Sinon, Git essaie automatiquement de mettre le résultat de la fusion en staging et de créer un commit.
Notre fusion a échoué !
Git a détecté un conflit dans index.html
. Plus précisément, il a détecté des changements concernant la même ligne et ne peut pas décider automatiquement quelles sont les lignes de code à garder.
Dans ce cas, il nous faut nous-même résoudre le conflit. Pour nous aider, Git a ajouté des marqueurs dans le fichier en question. git diff
nous montre l’état de notre fichier.
Le HEAD nous signale quel est le changement courant (dans la branche dans laquelle nous fusionnons, soit master
), et inline-navigation
nous signale le changement dans la branche que nous tentons de fusionner (les incoming changes
ou changement entrants).
VSCode et son intégration avec Git est un peu plus clair grâce aux couleurs et nous permet même de régler le conflit très vite en cliquant sur les boutons Accept Current Change
ou Accept Incoming Changes
.
Si vous ne pouvez pas résoudre le conflit, vous pouvez annuler la fusion avec :
git merge --abort
Git va alors restaurer votre copie de travail au dernier état propre connu. Sinon, vous pouvez soit cliquer le bouton pour accepter les changements dans VSCode, soit simplement éditer le fichier manuellement avec le bon titre.
Quand votre code est ok, vous pouvez poursuivre la fusion en créant votre commit. Si vous n’avez pas utilisé le flag --no-commit
, vous pouvez aussi utiliser git merge --continue
pour continuer la procédure automatique.
Sans le flag --no-commit
, Git tente de mettre en staging et créer automatiquement un commit. En cas de conflit, vous aurez surement des fichiers déjà en staging et vous aurez juste à vous occuper de vos conflits. Le commit est donc préparé pour vous.
Eviter les conflits
Vous voyez, ce n’était pas si compliqué !
Git nous aide beaucoup. Les messages du terminal sont clairs et il nous suggère toujours la marche à suivre. Si on suit ses instructions en général tout se passe bien.
Cependant, si vous collaborez sur le même dépôt, des conflits vont inévitablement arriver.
Pour les éviter ou les garder faciles à régler, pensez à mettre à jour votre code avant de travailler ! Votre dépôt local contient surement une version périmée du projet. Mettez votre branche principale à jour, puis créez une branche à partir de la dernière version.
Aussi, essayer de faire de petits commits. Essayez de ne pas travailler sur plusieurs choses à la fois. Si possible un commit doit correspondre à un seul changement, même s’il affecte plusieurs fichiers.
Conclusion
Apprendre Git n’est pas si compliqué si on procède pas à pas ! Dans la première partie la série, on s’est concentré sur les bases : initialiser le dépôt et faire nos premiers commits.
Dans cette partie 2, on a étudié le fonctionnement des branches. Comprendre les branches est essentiel pour bien travailler avec Git. Si vous avez bien compris ce qu’on a fait aujourd’hui, vous avez toutes les compétences nécessaires pour développer proprement vos projets locaux.
Dans la partie 3 de cette mini-série Apprendre Git, on va mettre en pratique tout ce qu’on a appris et voir comment utiliser Git pour collaborer et travailler à plusieurs sur des dépôts distants.