Stash, reset et nettoyage#
Le développement logiciel n’est pas un processus linéaire. On est souvent interrompu en plein travail par un bug urgent à corriger, on se rend compte qu’une série de commits part dans la mauvaise direction, ou l’on accumule des fichiers temporaires qui encombrent le répertoire de travail. Git fournit un ensemble de commandes de « ménage » pour gérer ces situations avec précision et sécurité.
Ce chapitre couvre quatre outils complémentaires : git stash pour mettre de côté un travail en cours, git restore pour annuler des modifications dans le répertoire de travail ou la staging area, git reset pour déplacer HEAD et éventuellement effacer des changements, et git clean pour supprimer les fichiers non suivis. Maitriser ces commandes, c’est pouvoir naviguer sereinement entre les contextes de travail sans jamais perdre de données — sauf quand on le décide explicitement.
Git stash#
Définition 45 (Stash)
Le stash (littéralement « réserve », « planque ») est une pile (stack) gérée par Git qui permet de sauvegarder temporairement les modifications en cours — fichiers modifiés suivis et changements indexés (staged) — puis de restaurer le répertoire de travail à l’état du dernier commit. C’est un presse-papiers pour le travail en cours (work in progress, WIP). Chaque entrée du stash est stockée comme un couple de commits spéciaux dans .git/refs/stash.
Sauvegarder le travail en cours#
La commande de base est git stash (ou git stash push). Elle sauvegarde les modifications et revient à un répertoire propre.
%%bash
cd /tmp && rm -rf demo-stash && mkdir demo-stash && cd demo-stash && git init
git config user.email "demo@example.com" && git config user.name "Demo"
echo "initial" > file.txt && git add . && git commit -m "Initial"
Dépôt Git vide initialisé dans /tmp/demo-stash/.git/
[main (commit racine) b617903] Initial
1 file changed, 1 insertion(+)
create mode 100644 file.txt
%%bash
cd /tmp/demo-stash
# Effectuons des modifications
echo "travail en cours" >> file.txt
echo "nouveau fichier" > notes.txt && git add notes.txt
echo "=== Etat avant stash ==="
git status --short
# Sauvegarder le travail
git stash push -m "WIP: ajout notes et modification file.txt"
echo ""
echo "=== Etat après stash ==="
git status --short
=== Etat avant stash ===
M file.txt
A notes.txt
Arbre de travail et état de l'index sauvegardés dans On main: WIP: ajout notes et modification fil
e.txt
=== Etat après stash ===
Le répertoire de travail est propre : les modifications ont été sauvegardées dans le stash. On peut ajouter un message descriptif avec -m pour s’y retrouver plus tard.
Lister les stashs#
%%bash
cd /tmp/demo-stash
# Ajoutons un deuxième stash pour illustrer la pile
echo "autre modification" >> file.txt
git stash push -m "WIP: autre modification"
echo "=== Liste des stashs ==="
git stash list
Arbre de travail et état de l'index sauvegardés dans On main: WIP: autre modification
=== Liste des stashs ===
stash@{0}: On main: WIP: autre modification
stash@{1}: On main: WIP: ajout notes et modification file.txt
Chaque stash est identifié par stash@{N} où N est son index dans la pile. Le stash le plus récent est stash@{0}.
Examiner le contenu d’un stash#
%%bash
cd /tmp/demo-stash
echo "=== Contenu du stash le plus récent ==="
git stash show -p stash@{0}
=== Contenu du stash le plus récent ===
diff --git a/file.txt b/file.txt
index e79c5e8..dfe04cb 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +
1,2 @@
initial
+autre modification
L’option -p affiche le diff complet du stash. Sans elle, seul un résumé des fichiers modifiés est affiché.
Restaurer un stash : pop vs apply#
Deux commandes permettent de restaurer un stash, avec une différence importante.
%%bash
cd /tmp/demo-stash
echo "=== Avant pop ==="
git stash list
# pop : applique ET supprime le stash du sommet de la pile
git stash pop
echo ""
echo "=== Apres pop ==="
git stash list
echo ""
git status --short
=== Avant pop ===
stash@{0}: On main: WIP: autre modification
stash@{1}: On main: WIP: ajout notes et modification file.txt
Sur la branche main
Modifications qui ne seront pas validées :
(utilisez "git add <fichier>..." p
our mettre à jour ce qui sera validé)
(utilisez "git restore <fichier>..." pour annuler les modi
fications dans le répertoire de travail)
modifié : file.txt
aucune modification n'a ét
é ajoutée à la validation (utilisez "git add" ou "git commit -a")
refs/stash@{0} supprimé (d2ada8f0dfba3bcf2c79341bfd26c654aa9d8fc7)
=== Apres pop ===
stash@{0}: On main: WIP: ajout notes et modification file.txt
M file.txt
git stash pop applique le stash le plus récent et le retire de la pile. En revanche, git stash apply applique le stash mais le conserve dans la pile — utile si l’on souhaite appliquer le même stash sur plusieurs branches.
Supprimer un stash#
%%bash
cd /tmp/demo-stash
git stash list
echo ""
# Supprimer un stash spécifique sans l'appliquer
git stash drop stash@{0}
echo ""
echo "=== Apres drop ==="
git stash list
stash@{0}: On main: WIP: ajout notes et modification file.txt
stash@{0} supprimé (a3203f4eb4f88c1e7ddcf90d9a598154a5f7db56)
=== Apres drop ===
Pour vider entièrement la pile de stashs, on utilise git stash clear.
Remarque 44
Par défaut, git stash ne sauvegarde pas les fichiers non suivis (untracked). Si vous avez créé de nouveaux fichiers qui ne sont pas encore indexés, ils ne seront pas inclus dans le stash. Pour les inclure, utilisez git stash -u (ou --include-untracked). L’option -a (ou --all) va encore plus loin et inclut également les fichiers ignorés par .gitignore.
Exemple 14 (Scénario typique : interruption par un bug urgent)
Vous travaillez sur une nouvelle fonctionnalité (feature-X) quand un bug critique est signalé en production. Voici la démarche :
Sauvegarder le travail en cours :
git stash push -m "WIP: feature-X en cours"
Corriger le bug sur la branche principale :
git switch main # ... correction du bug ... git add . && git commit -m "fix: correction bug critique #42"
Reprendre le travail :
git switch feature-X git stash pop
Votre répertoire de travail est restauré exactement comme vous l’aviez laissé. Aucune modification n’a été perdue.
Git restore#
Définition 46 (git restore)
git restore est la commande dédiée à la restauration de fichiers dans le répertoire de travail ou la staging area. Introduite dans Git 2.23 (aout 2019), elle prend en charge la partie « restauration de fichiers » qui était auparavant mélangée dans git checkout. Son rôle est d’annuler des modifications locales, de désindexer des fichiers, ou de restaurer un fichier depuis un commit spécifique.
Annuler les modifications dans le répertoire de travail#
Préparons un dépôt de démonstration.
%%bash
cd /tmp && rm -rf demo-restore && mkdir demo-restore && cd demo-restore && git init
git config user.email "demo@example.com" && git config user.name "Demo"
echo "ligne 1" > fichier.txt && git add . && git commit -m "Initial"
Dépôt Git vide initialisé dans /tmp/demo-restore/.git/
[main (commit racine) 0ea8774] Initial
1 file changed, 1 insertion(+)
create mode 100644 fichier.t
xt
%%bash
cd /tmp/demo-restore
# Modifier le fichier
echo "modification non desirée" >> fichier.txt
echo "=== Avant restore ==="
cat fichier.txt
# Annuler les modifications
git restore fichier.txt
echo ""
echo "=== Apres restore ==="
cat fichier.txt
=== Avant restore ===
ligne 1
modification non desirée
=== Apres restore ===
ligne 1
La commande git restore <fichier> restaure le fichier depuis la staging area (ou depuis le dernier commit si rien n’est indexé). Les modifications locales sont perdues.
Désindexer un fichier (unstage)#
%%bash
cd /tmp/demo-restore
echo "nouveau contenu" >> fichier.txt
git add fichier.txt
echo "=== Avant unstage ==="
git status --short
# Désindexer le fichier (le retirer de la staging area)
git restore --staged fichier.txt
echo ""
echo "=== Apres unstage ==="
git status --short
=== Avant unstage ===
M fichier.txt
=== Apres unstage ===
M fichier.txt
L’option --staged retire le fichier de la staging area tout en conservant les modifications dans le répertoire de travail. C’est l’équivalent moderne de git reset HEAD <fichier>.
Restaurer depuis un commit spécifique#
%%bash
cd /tmp/demo-restore
echo "ligne 2" >> fichier.txt && git add . && git commit -m "Ajout ligne 2"
echo "ligne 3" >> fichier.txt && git add . && git commit -m "Ajout ligne 3"
echo "=== Etat actuel ==="
cat fichier.txt
# Restaurer le fichier tel qu'il était 2 commits en arrière
git restore --source=HEAD~2 fichier.txt
echo ""
echo "=== Après restore --source=HEAD~2 ==="
cat fichier.txt
[main e01038b] Ajout ligne 2
1 file changed, 2 insertions(+)
[main 89c5861] Ajout ligne 3
1 file changed, 1 insertion(+)
=== Etat actuel ===
ligne 1
nouveau contenu
ligne 2
ligne 3
=== Après restore --source=HEAD~2 ===
ligne 1
L’option --source permet de spécifier le commit depuis lequel restaurer. On peut utiliser un hash, un nom de branche, un tag, ou toute référence valide.
Remarque 45
git restore (Git 2.23+) a remplacé git checkout -- <fichier> pour la restauration de fichiers. L’ancienne syntaxe fonctionne toujours, mais git restore est plus claire dans son intention : elle ne sert qu’à restaurer des fichiers, tandis que git checkout servait à la fois à basculer entre branches et à restaurer des fichiers, ce qui était source de confusion et d’erreurs.
Git reset#
Définition 47 (git reset)
git reset déplace le pointeur HEAD (et la branche courante) vers un commit spécifique. Son effet sur la staging area et le répertoire de travail dépend du mode utilisé :
git reset --soft HEAD~1: déplace HEAD d’un commit en arrière. Les changements restent indexés (dans la staging area). C’est un « désengagement » : le commit est défait, mais tout est prêt à être recommité.git reset --mixed HEAD~1(mode par défaut) : déplace HEAD et vide la staging area. Les changements restent dans le répertoire de travail, mais ne sont plus indexés.git reset --hard HEAD~1: déplace HEAD, vide la staging area et écrase le répertoire de travail. Toutes les modifications sont perdues définitivement.
Visualisation des trois modes de reset#
Le diagramme suivant montre les trois zones de Git (répertoire de travail, staging area, dépôt) et ce que chaque mode de reset affecte.
En résumé :
--softne touche qu’au pointeur HEAD. Les changements restent indexés, prêts à être recommités. Utile pour reformuler le message d’un commit ou fusionner plusieurs commits en un seul.--mixed(par défaut) réinitialise également la staging area. Les modifications sont toujours présentes dans le répertoire de travail mais doivent être ré-indexées avecgit add.--hardréinitialise tout : HEAD, staging area et répertoire de travail. Les modifications non commitées sont détruites.
Démonstration des trois modes#
%%bash
cd /tmp && rm -rf demo-reset && mkdir demo-reset && cd demo-reset && git init
git config user.email "demo@example.com" && git config user.name "Demo"
echo "initial" > file.txt && git add . && git commit -m "Initial"
echo "v2" >> file.txt && git add . && git commit -m "Commit 2"
echo "v3" >> file.txt && git add . && git commit -m "Commit 3"
echo "=== Historique initial ==="
git log --oneline
Dépôt Git vide initialisé dans /tmp/demo-reset/.git/
[main (commit racine) 0084153] Initial
1 file changed, 1 insertion(+)
create mode 100644 file.txt
[main 9949667] Commit 2
1 file changed, 1 insertion(+)
[main 09c7272] Commit 3
1 file changed, 1 insertion(+)
=== Historique initial ===
09c7272 Commit 3
9949667 Commit 2
0084153 Initial
Reset soft#
%%bash
cd /tmp/demo-reset
git reset --soft HEAD~1
echo "=== Après reset --soft HEAD~1 ==="
git log --oneline
echo ""
echo "=== git status ==="
git status --short
echo ""
echo "=== Le fichier dans le répertoire de travail ==="
cat file.txt
=== Après reset --soft HEAD~1 ===
9949667 Commit 2
0084153 Initial
=== git status ===
M file.txt
=== Le fichier dans le répertoire de travail ===
initial
v2
v3
Le commit « Commit 3 » a disparu de l’historique, mais les modifications sont toujours indexées (marquées M en vert dans git status). Un simple git commit suffirait à recréer un commit.
%%bash
cd /tmp/demo-reset
# Remettre le commit pour la suite de la démo
git commit -m "Commit 3 (restauré)"
[main 72b662d] Commit 3 (restauré)
1 file changed, 1 insertion(+)
Reset mixed (par défaut)#
%%bash
cd /tmp/demo-reset
git reset HEAD~1
echo "=== Après reset --mixed HEAD~1 ==="
git log --oneline
echo ""
echo "=== git status ==="
git status --short
echo ""
echo "=== Le fichier dans le répertoire de travail ==="
cat file.txt
Modifications non indexées après reset :
M file.txt
=== Après reset --mixed HEAD~1 ===
9949667 Commit 2
0084153 Initial
=== git status ===
M file.txt
=== Le fichier dans le répertoire de travail ===
initial
v2
v3
Cette fois, les modifications sont dans le répertoire de travail mais ne sont plus indexées (marquées M en rouge). Il faut faire git add avant de pouvoir commiter.
%%bash
cd /tmp/demo-reset
# Remettre le commit pour la suite de la démo
git add . && git commit -m "Commit 3 (restauré)"
[main 72b662d] Commit 3 (restauré)
1 file changed, 1 insertion(+)
Reset hard#
%%bash
cd /tmp/demo-reset
echo "=== Contenu avant reset --hard ==="
cat file.txt
git reset --hard HEAD~1
echo ""
echo "=== Après reset --hard HEAD~1 ==="
git log --oneline
echo ""
echo "=== git status ==="
git status --short
echo ""
echo "=== Contenu du fichier ==="
cat file.txt
=== Contenu avant reset --hard ===
initial
v2
v3
HEAD est maintenant à 9949667 Commit 2
=== Après reset --hard HEAD~1 ===
9949667 Commit 2
0084153 Initial
=== git status ===
=== Contenu du fichier ===
initial
v2
Avec --hard, le fichier lui-même a été modifié : la ligne « v3 » a disparu. Le répertoire de travail correspond exactement au commit précédent. Il n’y a aucun moyen simple de récupérer les modifications perdues.
Remarque 46
git reset --hard est une commande destructive. Les modifications non commitées sont perdues définitivement — elles n’apparaissent ni dans l’historique, ni dans le reflog, ni dans le stash. Avant d’utiliser --hard, prenez le reflexe de vérifier git status et git stash list. Si vous devez absolument revenir en arrière mais souhaitez conserver une chance de récuperation, préférez --soft ou --mixed, ou faites un git stash avant le reset.
Git clean#
Définition 48 (git clean)
git clean supprime les fichiers non suivis (untracked) du répertoire de travail. Il n’affecte ni les fichiers suivis, ni les fichiers modifiés, ni la staging area : il ne cible que les fichiers que Git ne connait pas. C’est l’outil de nettoyage pour les fichiers temporaires, les artefacts de compilation ou les fichiers générés qui n’ont pas été ajoutés au .gitignore.
Dry run : vérifier avant de supprimer#
%%bash
cd /tmp && rm -rf demo-clean && mkdir demo-clean && cd demo-clean && git init
git config user.email "demo@example.com" && git config user.name "Demo"
echo "initial" > file.txt && git add . && git commit -m "Initial"
# Créer des fichiers non suivis
echo "temp" > temp.log
echo "build output" > output.bin
mkdir build && echo "artifact" > build/result.o
echo "=== Fichiers présents ==="
git status --short
Dépôt Git vide initialisé dans /tmp/demo-clean/.git/
[main (commit racine) 9e1f51f] Initial
1 file changed, 1 insertion(+)
create mode 100644 file.txt
=== Fichiers présents ===
?? build/
?? output.bin
?? temp.log
%%bash
cd /tmp/demo-clean
echo "=== Dry run : qu'est-ce qui serait supprimé ? ==="
git clean -n
echo ""
echo "=== Dry run avec les répertoires ==="
git clean -nd
=== Dry run : qu'est-ce qui serait supprimé ? ===
Supprimerait output.bin
Supprimerait temp.log
=== Dry run avec les répertoires ===
Supprimerait build/
Supprimerait output.bin
Supprimerait temp.log
L’option -n (ou --dry-run) affiche ce qui serait supprimé sans rien toucher. C’est une étape de vérification indispensable.
Supprimer les fichiers non suivis#
%%bash
cd /tmp/demo-clean
git clean -f
echo "=== Apres git clean -f ==="
git status --short
Suppression de output.bin
Suppression de temp.log
=== Apres git clean -f ===
?? build/
L’option -f (pour force) est requise : par sécurité, Git refuse d’exécuter git clean sans elle. Notez que les répertoires non suivis ne sont pas supprimés par -f seul.
Supprimer fichiers ET répertoires non suivis#
%%bash
cd /tmp/demo-clean
echo "=== Répertoire build encore présent ==="
git status --short
echo ""
git clean -fd
echo "=== Après git clean -fd ==="
git status --short
=== Répertoire build encore présent ===
?? build/
Suppression de build/
=== Après git clean -fd ===
L’option -d ajoute la suppression des répertoires non suivis. La combinaison -fd est la plus courante pour un nettoyage complet.
Remarque 47
Faites toujours un dry run (git clean -n ou git clean -nd) avant d’exécuter la suppression réelle. Un fichier non suivi supprimé par git clean est perdu définitivement : il n’est pas dans l’historique Git, pas dans le stash, pas dans la corbeille du système. C’est une suppression sans filet. Si vous avez un doute, ajoutez d’abord les fichiers importants au suivi (git add) ou au .gitignore.
Résumé#
Le tableau suivant récapitule les commandes de ménage vues dans ce chapitre.
Commande |
Effet |
|---|---|
|
Sauvegarde les modifications en cours sur la pile de stash |
|
Idem, avec un message descriptif |
|
Sauvegarde en incluant les fichiers non suivis |
|
Liste les entrées de la pile de stash |
|
Affiche le contenu détaillé d’un stash |
|
Applique le stash le plus récent et le supprime de la pile |
|
Applique le stash le plus récent sans le supprimer |
|
Supprime une entrée spécifique du stash |
|
Vide entièrement la pile de stash |
|
Annule les modifications locales d’un fichier |
|
Désindexe un fichier (le retire de la staging area) |
|
Restaure un fichier depuis un commit spécifique |
|
Recule HEAD de N commits, conserve staging et working dir |
|
Recule HEAD, réinitialise staging, conserve working dir |
|
Recule HEAD, réinitialise staging et working dir (destructif) |
|
Dry run : affiche les fichiers non suivis qui seraient supprimés |
|
Supprime les fichiers non suivis |
|
Supprime les fichiers et répertoires non suivis |