Git : du fichier au contenu
Pensez contenu plutôt que fichiers avec Git : branches, commits et diffs pour la documentation technique simplifient la collaboration et la gestion des versions.
Définition
Section intitulée « Définition »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.
Principes clés
Section intitulée « Principes clés »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 les stocke ensuite dans deux zones, l’index (zone temporaire) et la base de données d’objets (zone persistante) :
| Action | Index | Base d’objets |
|---|---|---|
Ajouter un fichier (git add <fichier>) | Arbre | Contenu |
Commiter le fichier (git commit) | - | Arbre, stocké de façon permanente |
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 identique (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 CommitInstantané enregistré des modifications apportées à un dépôt Git, accompagné d'un auteur, d'une date et d'un message qui explique le changement. La suite des commits forme l'historique du projet, point de départ des branches et base de comparaison pour les relecteurs et les traducteurs.Voir dans le glossaire →. 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.
Une BrancheLigne de travail parallèle dans un système de gestion de versions : les modifications faites sur une branche restent isolées jusqu'à leur relecture et leur fusion dans la ligne principale, ce qui permet de faire avancer en même temps plusieurs rédacteurs, versions ou langues.Voir dans le glossaire → ne contient que des commits. Si vous créez une branche, disons test, alors que votre espace de travail contient des modifications non commitées, ces modifications ne sont pas transférées sur test. Elles restent dans votre espace de travail et vous suivent lorsque vous changez de branche. Seul un commit les ajoute à l’historique d’une branche ; changer de branche peut donc vous obliger à committer ou à mettre de côté (git stash) vos modifications au préalable. 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.
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 (tâche 1).
- Vous envoyez votre proposition pour validation.
- La validation se fait attendre et vous devez avancer sur d’autres parties des documents (tâche 2).
Plutôt que de suivre une longue suite de commandes, raisonnez par situations : à chaque étape, demandez-vous où se trouve le travail et dans quel état il est.
Mettre la première tâche de côté
Section intitulée « Mettre la première tâche de côté »Vous êtes sur la branche master. Votre espace de travail contient les modifications de la tâche 1, que vous ne souhaitez pas committer sur master avant validation. Créez une branche à partir du commit courant, puis committez-y la tâche 1 :
git checkout -b ma-branchegit add mes-fichiersgit commit -m "mon message de commit"La création de la branche n’isole rien à elle seule : vos modifications non commitées restent dans l’espace de travail. C’est le commit qui enregistre la tâche 1 dans l’historique de ma-branche. La tâche 1 est désormais en sécurité dans ma-branche.
Passer à une autre tâche
Section intitulée « Passer à une autre tâche »Repassez sur la branche master :
git checkout masterVotre espace de travail reflète maintenant master, sans la tâche 1. La tâche 1 reste sur sa branche pendant que la tâche 2 avance sur master :
Reprendre ou intégrer la première tâche
Section intitulée « Reprendre ou intégrer la première tâche »La suite dépend de l’état de la tâche 1.
La tâche 1 doit être modifiée
Section intitulée « La tâche 1 doit être modifiée »Si la tâche 2 comporte des modifications non commitées, mettez-les d’abord de côté avec git stash. Sinon, elles vous suivent sur ma-branche, ou Git refuse de changer de branche.
Repassez ensuite sur la branche, apportez les modifications demandées et committez-les :
git checkout ma-branche
# apportez vos modifications
git add mes-fichiersgit commit -m "prise en compte des retours de validation"Recommencez autant de fois que nécessaire, puis repassez sur master avec git checkout master et récupérez la tâche 2 avec git stash pop si vous l’aviez mise de côté. Vous pourrez fusionner ces commits en un seul après validation (voir Organiser son historique avec Git rebase).
La tâche 1 est validée
Section intitulée « La tâche 1 est validée »Fusionnez ma-branche dans master. Si la tâche 2 comporte des modifications non commitées, mettez-les d’abord de côté :
git stashgit merge ma-branchegit stash popLe stash est nécessaire, car la tâche 2 n’existe que dans votre espace de travail, et non dans un commit. Git refuse la fusion si elle risque d’écraser des fichiers que vous êtes en train de modifier, et mêler une fusion à un travail en cours rend l’un et l’autre plus difficiles à vérifier. git stash vous donne un espace de travail propre pour la fusion, et git stash pop rétablit la tâche 2 dans l’espace de travail, par-dessus le résultat :
Si git stash pop signale un conflit, les tâches 1 et 2 ont modifié les mêmes lignes. Résolvez le conflit dans les fichiers indiqués, puis supprimez l’entrée de stash, que Git conserve au cas où vous en auriez encore besoin, avec git stash drop.
Retenez cette distinction : la branche contient l’historique de la tâche, tandis que l’espace de travail contient ce que vous êtes en train de modifier.
Travailler simultanément sur deux branches
Section intitulée « Travailler simultanément sur deux branches »Changer de branche dans un même espace de travail convient lorsque vous passez occasionnellement d’une tâche à l’autre. Si vous devez réellement travailler simultanément sur deux branches, utilisez git worktree, qui associe un répertoire de travail supplémentaire à une branche du même dépôt.
Depuis projet/, votre répertoire de travail principal, lancez :
git worktree add ../tache-1 ma-brancheCette commande crée tache-1/, un répertoire de travail distinct à côté de projet/, sur lequel ma-branche est extraite. Vous disposez maintenant de deux répertoires voisins, chacun associé à sa propre branche et à son propre travail en cours :
projet/ master tâche 2tache-1/ ma-branche tâche 1Vous modifiez et committez la tâche 1 dans tache-1/, et la tâche 2 dans projet/. Inutile de mettre la tâche 2 de côté simplement pour changer de branche : projet/ reste sur master, et vous changez de répertoire.
Lorsque la tâche 1 est validée, fusionnez-la depuis le répertoire de travail principal :
cd projetgit merge ma-brancheLes commits effectués dans tache-1/ sont déjà visibles depuis projet/, puisque les deux répertoires partagent le même dépôt. Si la fusion touche des fichiers que la tâche 2 est encore en train de modifier, committez ou mettez d’abord la tâche 2 de côté. Vous pouvez ensuite supprimer le répertoire de travail supplémentaire avec git worktree remove ../tache-1.
Annuler des modifications non commitées
Section intitulée « Annuler des modifications non commitées »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. Pour annuler vos modifications non commitées de fichiers suivis, restaurez-les depuis Git :
git restore .ou, pour un seul fichier :
git restore chemin/vers/mon-fichierSans l’option --staged, ces commandes n’annulent que les modifications non indexées des fichiers suivis spécifiés : les modifications déjà indexées avec git add sont conservées, et les fichiers non suivis ne sont pas concernés.
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 versionsEnregistrement de chaque modification d'un ensemble de fichiers, qui permet de restaurer n'importe quel état antérieur, de comparer les changements et de travailler à plusieurs en parallèle. Git est le logiciel de gestion de versions le plus répandu ; en docs-as-code, la documentation est placée sous gestion de versions à côté du code du produit.Voir dans le glossaire →, il est préférable de diviser le travail en deux lots de tâches, que l’on appellera 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 avant le rebase
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.
Pour réorganiser vos commits :
- Créez une branche de sauvegarde provisoire, afin de pouvoir revenir à tout moment à l’historique actuel :
git branch sauvegarde. - Lancez la commande
git rebase -i HEAD~5pour réorganiser les commits, de la version en cours aux cinq précédentes, par exemple. - Dans l’éditeur qui s’ouvre, réordonnez les lignes pour regrouper les commits par tâche, et remplacez
pickparsquashsur chaque commit à fusionner avec celui qui le précède. - Enregistrez et fermez l’éditeur. Git réécrit l’historique, puis vous invite à modifier les messages des commits fusionnés.
Vous pouvez alors proposer à vos collaborateurs un commit pour chaque tâche réalisée en son entier, comme sur le schéma suivant :
Historique Git après le rebase
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, une approche qui favorise l’intégration de la documentation dans les processus de développement. 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.