Git : du fichier au contenu
Qu’est-ce qu’un fichier ? Pour vous, un contenu, image, texte, feuille de calcul ou autre, identifié par un nom. Pour votre système d’exploitation, une suite de bits sur le disque dur à laquelle sont associés un nom de fichier et un chemin de répertoires. Si vous souhaitez gérer votre projet en termes de fichiers sous Git, vous allez au-devant de maintes difficultés. Si vous pensez plutôt en termes de contenu, tout devient beaucoup plus simple.
Si vous donnez un fichier à Git, il le scinde directement en deux choses :
- un contenu (suite de bits, ou blob),
- un arbre (lien entre le nom de fichier et le contenu).
Il le stocke ensuite dans l’une des deux zones suivantes :
- l’index (zone temporaire),
- la base de données d’objets (zone persistante).
Lorsque vous ajoutez un fichier (git add <fichier>) :
- l’arbre est placé dans l’index,
- le contenu est placé dans la base d’objets.
Lorsque vous commitez un fichier (git commit) :
- l’arbre est placé dans la base d’objets.
Git ne compare jamais deux fichiers entre eux. Il compare leur résumé, qui est un nombre unique calculé à partir de leur contenu. Si le résumé de deux fichiers est identique, le contenu de ces fichiers est indentique (au bit près).
L’historique de votre projet n’est pas forcément linéaire : vous pouvez lui faire suivre plusieurs routes parallèles, les branches.
Vous ne pouvez créer des branches qu’à partir d’un commit. Il faut voir les commits comme des ronds-points (la route étant l’historique de votre projet) à partir desquels vous pouvez, si vous le souhaitez, prendre une autre direction dans votre projet.
Si vous créez une branche, disons test, alors que des modifications de votre espace de travail ne sont pas commitées dans votre branche master, les modifications que vous effectuerez s’appliqueront aux fichiers non commités de votre espace de travail. Si vous faites une erreur, vous ne pourrez pas retrouver le statu quo ante de vos fichiers en revenant à la branche master.
Si vous voulez enregistrer votre travail au fil de l’eau afin de pouvoir revenir à tout moment à un état antérieur, il vous faut donc committer régulièrement et sauvegarder votre espace de travail, répertoire .git y compris, par exemple via rsync. Lorsque vous déciderez de partager votre travail, vous pourrez déplacer, fusionner ou supprimer vos commits avant de les envoyer sous forme de patchs ou de les déposer sur un dépôt central.
Les branches Git permettent de facilement effectuer en parallèle plusieurs tâches non liées :
Imaginons le scénario de travail suivant :
- On vous demande de migrer une section d’un document à un autre.
- Vous envoyez votre proposition pour validation.
- La validation se fait attendre et vous devez avancer sur d’autres parties des documents.
Comment faire sauter ce goulot d’étranglement ? C’est (relativement) simple :
- Par défaut, vous travaillez sur la branche master. Votre espace de travail contient des modifications que vous ne souhaitez pas committer avant validation.
- Créez une nouvelle branche : git checkout -b ma-branche.
- Committez vos modifications sur la nouvelle branche : git add mes-fichiers, git commit -m “mon message de commit”.
- Vous repassez sur la branche master git checkout master et passez à votre deuxième tâche. 5a. Si votre première tâche n’est pas validée, vous repassez sur la branche provisoire : git checkout ma-branche et faites un nouveau commit (que vous pourrez fusionner avec le ou les précédents après validation).
- Lorsque vous recevez la validation de la première tâche, vous mettez votre travail en cours de côté : git stash.
- Vous fusionnez la branche provisoire avec la branche master : git merge ma-branche.
- Vous récupérez votre travail en cours : git stash pop.
Si vous n’avez pas besoin d’effectuer deux lots de tâches en parallèle, vous pouvez sans problème travailler dans votre espace local. Si vous devez revenir sur vos modifications, appellez la commande git reset —hard HEAD pour écraser vos fichiers non commités du répertoire local par ceux du dernier commit.
Faire sauter les goulets d’étranglement avec les branches
Section intitulée « Faire sauter les goulets d’étranglement avec les branches »Les branches Git permettent de facilement effectuer en parallèle plusieurs tâches non liées :
Imaginons le scénario de travail suivant :
- On vous demande de migrer une section d’un document à un autre.
- Vous envoyez votre proposition pour validation.
- La validation se fait attendre et vous devez avancer sur d’autres parties des documents.
Comment faire sauter ce goulot d’étranglement ? C’est (relativement) simple :
- Par défaut, vous travaillez sur la branche master. Votre espace de travail contient des modifications que vous ne souhaitez pas committer avant validation.
- Créez une nouvelle branche : git checkout -b ma-branche.
- Committez vos modifications sur la nouvelle branche : git add mes-fichiers, git commit -m “mon message de commit”.
- Vous repassez sur la branche master git checkout master et passez à votre deuxième tâche. 5a. Si votre première tâche n’est pas validée, vous repassez sur la branche provisoire : git checkout ma-branche et faites un nouveau commit (que vous pourrez fusionner avec le ou les précédents après validation).
- Lorsque vous recevez la validation de la première tâche, vous mettez votre travail en cours de côté : git stash.
- Vous fusionnez la branche provisoire avec la branche master : git merge ma-branche.
- Vous récupérez votre travail en cours : git stash pop.
Si vous n’avez pas besoin d’effectuer deux lots de tâches en parallèle, vous pouvez sans problème travailler dans votre espace local. Si vous devez revenir sur vos modifications, appellez la commande git reset —hard HEAD pour écraser vos fichiers non commités du répertoire local par ceux du dernier commit.
Organiser son historique avec Git rebase
Section intitulée « Organiser son historique avec Git rebase »Git est d’un abord déroutant. Ses workflows s’appliquent à du contenu plutôt qu’à des fichiers. Résultat : le travail de groupe et la gestion de différentes versions concurrentes d’un même contenu deviennent beaucoup plus simples.
Git effectue des commits atomiques : il applique des lots de modifications sur un contenu souvent réparti sur plusieurs fichiers, au lieu de gérer des fichiers proprement dits. Il nous invite à raisonner par lots de tâches sur un contenu et non par fichier.
Ce fonctionnement peut sembler peu intuitif si l’on a l’habitude de travailler fichier par fichier et non tâche par tâche. Mais une fois que l’on a adapté ses habitudes de travail à ce workflow, on s’aperçoit :
- que l’on dispose d’un historique beaucoup plus facilement exploitable,
- qu’il est beaucoup plus facile de gérer des versions concurrentes d’un même contenu dans des branches de développement parallèles.
Imaginons que vous ayez identifié deux types de modifications majeurs à apporter à votre contenu :
- les synopsis d’un programme en ligne de commande,
- les corrections grammaticales du texte.
Si votre contenu est réparti dans un ensemble de fichiers modulaires, vous pourriez décider d’apporter en même temps les deux types de modifications dans chaque fichier un à un. Pour répartir le travail sur un groupe de rédacteurs techniques, il vous suffit d’allouer à chacun un lot de fichiers.
Ce workflow n’est pas le plus adapté à Git. Si vous utilisez ce système de gestion de versions, il est préférable de diviser le travail en deux lots de tâches, que l’on appelera synopsis et texte, appliqués concurremment sur tous les fichiers.
Les contraintes de production vous obligeront souvent à scinder ces deux lots de tâches en sous-lots, que vous serez obligé de faire alterner.
Vous committez chaque sous-lot à chaque fois qu’il est achevé. Votre historique de commit ressemble alors au schéma suivant :
Historique Git
Lorsque vous placerez vos commits sur le dépôt central, certains commits représenteront une étape intermédiaire de l’une des tâches. Votre historique et vos branches seront donc plus difficiles à exploiter. D’autant plus que les tâches inachevées alternent. Pour en récupérer une seule, il faudra donc choisir soigneusement les commits via la commande git cherry-pick.
Heureusement, Git vous permet de réorganiser facilement vos commits avant de les partager. Lancez la commande git rebase -i HEAD~5 pour réorganiser les commits, de la version en cours aux cinq précédentes, par exemple.
Vous pouvez alors réécrire l’histoire pour proposer à vos collaborateurs un commit pour chaque tâche réalisée en son entier, comme sur le schéma suivant :
Historique Git
Les commits ont tout d’abord été regroupés par type sur la flèche du temps de Git, puis fusionnés.
Évidemment, vous n’avez plus accès aux commits intermédiaires, mais c’est ce que vous souhaitiez : chaque commit unique représente un état cohérent de votre contenu.
Ce workflow facilite également le travail d’équipe : vous pouvez confier ces tâches à deux membres différents de votre équipe, chacun travaillant dans son espace local. Les modifications du premier sont ensuite fusionnées avec celles du second dans son espace local via des patches. Enfin, les commits sont refactorisés avant de les placer sur le dépôt central.
Important
Moins vous réorganiserez vos commits (surtout chronologiquement), plus le risque de devoir corriger manuellement des conflits sera faible. Autrement dit, git rebase ne doit pas être une excuse pour ne pas planifier rationnellement son travail.