# Git : du fichier au contenu

> **Note**
>
> Vous êtes habitué à manipuler des fichiers ? Git vous invite à penser autrement. Avantage : vous avez une maîtrise beaucoup plus grande de votre contenu.
>
> Cet article explique comment Git stocke le contenu, comment [utiliser les branches pour contourner les goulets d'étranglement de la validation](#faire-sauter-les-goulets-détranglement-avec-les-branches) et comment [mettre de l'ordre dans votre historique avec git rebase](#organiser-son-historique-avec-git-rebase) avant de le partager.

## 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

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](https://docs.redaction-technique.org/fr/tech-writing-process/using-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.

Une branche 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

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é

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 :

```bash
git checkout -b ma-branche
git add mes-fichiers
git 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

Repassez sur la branche *master* :

```bash
git checkout master
```

Votre 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* :

> **Schéma: La tâche 1 mise de côté sur sa branche pendant que la tâche 2 avance sur master**
>
> Historique Git à deux branches : master pointe sur le commit A, avec la tâche 2 en cours dans l'espace de travail ; ma-branche part du commit A et pointe sur le commit B, où la tâche 1 est commité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

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* :

```bash
git checkout ma-branche

# apportez vos modifications

git add mes-fichiers
git 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](#organiser-son-historique-avec-git-rebase)).

> **Astuce: Si Git refuse de changer de branche ou de fusionner**
>
> Git s'arrête sur le message `Your local changes to the following files would be overwritten by checkout` (ou `by merge`) et liste les fichiers concernés. Rien n'a encore été modifié : *committez* ou mettez de côté vos modifications, puis relancez la commande.

#### 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é :

```bash
git stash
git merge ma-branche
git stash pop
```

Le *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 :

> **Schéma: master après la fusion de la tâche 1**
>
> Historique Git de la branche master : le commit A suivi du commit B, où la tâche 1 est désormais intégrée.

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

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 :

```bash
git worktree add ../tache-1 ma-branche
```

Cette 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 :

```text
projet/    master      tâche 2
tache-1/   ma-branche  tâche 1
```

Vous 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 :

```bash
cd projet
git merge ma-branche
```

Les *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`.

> **Astuce**
>
> Utilisez une branche pour isoler une tâche. Utilisez `git worktree` pour travailler simultanément sur deux branches dans des répertoires distincts.

### 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 :

```bash
git restore .
```

ou, pour un seul fichier :

```bash
git restore chemin/vers/mon-fichier
```

Sans 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.

> **Attention**
>
> `git reset --hard HEAD` est plus destructive : elle réinitialise à la fois l'index et l'espace de travail sur `HEAD`. Elle peut effacer définitivement toutes les modifications non *commitées* des fichiers suivis, indexées ou non.

## 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 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 :

> **Schéma: Historique de commits entrelacés avant le rebase**
>
> Historique Git montrant cinq commits alternant entre corrections de texte et modifications de synopsis, avant leur réorganisation par rebase.

**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.

> **Attention**
>
> La commande `rebase` est potentiellement destructive ; veillez à sauvegarder votre espace de travail, répertoire *.git* compris, avant de l'exécuter, sous risque de perdre des données. Comme `rebase` réécrit l'historique, ne réorganisez que des *commits* que vous n'avez pas encore partagés : réécrire un historique déjà récupéré par d'autres les oblige à réconcilier des versions divergentes.

Pour réorganiser vos *commits* :

1. Créez une branche de sauvegarde provisoire, afin de pouvoir revenir à tout moment à l'historique actuel : `git branch sauvegarde`.
2. 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.
3. Dans l'éditeur qui s'ouvre, réordonnez les lignes pour regrouper les *commits* par tâche, et remplacez `pick` par `squash` sur chaque *commit* à fusionner avec celui qui le précède.
4. 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 :

> **Schéma: Historique de commits regroupés après le rebase**
>
> Historique Git montrant deux commits après rebase, l'un regroupant toutes les corrections de texte et l'autre tous les changements de synopsis.

**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.

> **Astuce: Si le rebase tourne mal**
>
> Tant que le rebase est en cours, par exemple lorsqu'il s'arrête sur un conflit, `git rebase --abort` rétablit l'historique tel qu'il était auparavant. Une fois le rebase terminé, `git reset --hard sauvegarde` ramène votre branche à la branche de sauvegarde ; comme tout `git reset --hard`, cette commande efface les modifications non *commitées*.

> **Note**
>
> Si vous avez effectué simultanément les deux tâches sur un ou plusieurs fichiers, pas de panique : grâce à la commande `git add -p` vous pouvez répartir vos modifications imbriquées sur les *commits* idoines. Lorsque vous lancez `git status`, vous vous apercevez alors que vos fichiers sont à la fois prêts et non prêts à être *commités* : il y a deux états des fichiers, chaque état représentant un stade partiel de votre travail et la somme des deux représentant la totalité des modifications que vous avez apportées.

É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](https://docs.redaction-technique.org/fr/tech-writing-process/integrating-documentation-into-development/). 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.

> **Attention: 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.

## Articles connexes

- [Les systèmes de gestion de versions - rustiques mais fiables](https://docs.redaction-technique.org/fr/tech-writing-process/version-control-systems/)
- [Référentiel](https://docs.redaction-technique.org/fr/tech-writing-process/repository/)
- [Un référentiel unique ?](https://docs.redaction-technique.org/fr/tech-writing-process/single-repository/)

---

Source: https://docs.redaction-technique.org/fr/tech-writing-process/git-from-file-to-content/
