Git
Chapters ▾ 2nd Edition

7.3 Utilitaires Git - Remisage et nettoyage

Remisage et nettoyage

Souvent, lorsque vous avez travaillé sur une partie de votre projet, les choses sont dans un état instable mais vous voulez changer de branche pour travailler momentanément sur autre chose. Le problème est que vous ne voulez pas valider un travail à moitié fait seulement pour pouvoir y revenir plus tard. La réponse à cette problématique est la commande git stash.

Remiser prend l’état en cours de votre répertoire de travail, c’est-à-dire les fichiers modifiés et l’index, et l’enregistre dans la pile des modifications non finies que vous pouvez ré-appliquer à n’importe quel moment.

Remiser votre travail

Pour démontrer cette possibilité, allez dans votre projet et commencez à travailler sur quelques fichiers et indexez l’un de ces changements. Si vous exécutez git status, vous pouvez voir votre état modifié :

$ git status
Modifications qui seront validées :
  (utilisez "git reset HEAD <fichier>..." pour désindexer)

	modifié :   index.html

Modifications qui ne seront pas validées :
  (utilisez "git add <fichier>..." pour mettre à jour ce qui sera validé)
  (utilisez "git checkout -- <fichier>..." pour annuler les modifications dans la copie de travail)

	modifié :   lib/simplegit.rb

À ce moment-là, vous voulez changer de branche, mais vous ne voulez pas encore valider ce travail ; vous allez donc remiser vos modifications. Pour créer une nouvelle remise sur votre pile, exécutez git stash :

$ git stash
Saved working directory and index state \
  "WIP on master: 049d078 added the index file"
HEAD is now at 049d078 added the index file
(To restore them type "git stash apply")

Votre répertoire de travail est propre :

$ git status
Sur la branche master
rien à valider, la copie de travail est propre

À ce moment, vous pouvez facilement changer de branche et travailler autre part ; vos modifications sont conservées dans votre pile. Pour voir quelles remises vous avez sauvegardées, vous pouvez utiliser la commande git stash list :

$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051... Revert "added file_size"
stash@{2}: WIP on master: 21d80a5... added number to log

Dans ce cas, deux remises ont été créées précédemment, vous avez donc accès à trois travaux remisés différents. Vous pouvez ré-appliquer celui que vous venez juste de remiser en utilisant la commande affichée dans la sortie d’aide de la première commande de remise : git stash apply. Si vous voulez appliquer une remise plus ancienne, vous pouvez la spécifier en la nommant, comme ceci : git stash apply stash@{2}. Si vous ne spécifiez pas une remise, Git présume que vous voulez la remise la plus récente et essaye de l’appliquer.

$ git stash apply
Sur la branche master
Modifications qui ne seront pas validées :
  (utilisez "git add <fichier>..." pour mettre à jour ce qui sera validé)
  (utilisez "git checkout -- <fichier>..." pour annuler les modifications dans la copie de travail)

     modified:   index.html
     modified:   lib/simplegit.rb

Vous pouvez observer que Git remodifie les fichiers non validés lorsque vous avez créé la remise. Dans ce cas, vous aviez un répertoire de travail propre lorsque vous avez essayé d’appliquer la remise et vous l’avez fait sur la même branche que celle où vous l’aviez créée ; mais avoir un répertoire de travail propre et l’appliquer sur la même branche n’est pas nécessaire pour réussir à appliquer une remise. Vous pouvez très bien créer une remise sur une branche, changer de branche et essayer d’appliquer ces modifications. Vous pouvez même avoir des fichiers modifiés et non validés dans votre répertoire de travail quand vous appliquez une remise, Git vous indique les conflits de fusions si quoi que ce soit ne s’applique pas proprement.

Par défaut, les modifications de vos fichiers sont ré-appliquées, mais pas les indexations. Pour cela, vous devez exécuter la commande git stash apply avec l’option --index pour demander à Git d’essayer de ré-appliquer les modifications de votre index. Si vous exécutez cela à la place de la commande précédente, vous vous retrouvez dans la position d’origine précédent la remise :

$ git stash apply --index
Sur la branche master
Modifications qui seront validées :
  (utilisez "git reset HEAD <fichier>..." pour désindexer)

     modifié :   index.html

Modifications qui ne seront pas validées :
  (utilisez "git add <fichier>..." pour mettre à jour ce qui sera validé)
  (utilisez "git checkout -- <fichier>..." pour annuler les modifications dans la copie de travail)

     modified:   lib/simplegit.rb

L’option apply essaye seulement d’appliquer le travail remisé, vous aurez toujours la remise dans votre pile. Pour la supprimer, vous pouvez exécuter git stash drop avec le nom de la remise à supprimer :

$ git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051... Revert "added file_size"
stash@{2}: WIP on master: 21d80a5... added number to log
$ git stash drop stash@{0}
Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)

Vous pouvez également exécuter git stash pop pour appliquer et supprimer immédiatement la remise de votre pile.

Remisage créatif

Il existe des variantes de remisages qui peuvent s’avérer utiles. La première option assez populaire est l’option --keep-index de la commande stash save. Elle indique à Git de ne pas remiser ce qui aurait été déjà indexé au moyen de la commande git add.

C’est particulièrement utile si vous avez réalisé des modifications mais souhaitez ne valider que certaines d’entre elles et gérer le reste plus tard.

$ git status -s
M  index.html
 M lib/simplegit.rb

$ git stash --keep-index
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file

$ git status -s
M  index.html

Une autre option utile de stash est la possibilité de remiser les fichiers non suivis aussi bien que les fichiers suivis. Par défaut, git stash ne sauve que les fichiers qui sont déjà suivis ou indexés. Si vous spécifiez l’option --include-untracked ou -u, Git remisera aussi les fichiers non-suivis du répertoire de travail.

$ git status -s
M  index.html
 M lib/simplegit.rb
?? new-file.txt

$ git stash -u
Saved working directory and index state WIP on master: 1b65b17 added the index file
HEAD is now at 1b65b17 added the index file

$ git status -s
$

Enfin, si vous ajoutez l’option --patch, Git ne remisera pas tout le contenu modifié, mais vous invitera à sélectionner interactivement les modifications que vous souhaitez remiser et celles que vous souhaiter conserver dans la copie de travail.

$ git stash --patch
diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 66d332e..8bb5674 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -16,6 +16,10 @@ class SimpleGit
         return `#{git_cmd} 2>&1`.chomp
       end
     end
+
+    def show(treeish = 'master')
+      command("git show #{treeish}")
+    end

 end
 test
Stash this hunk [y,n,q,a,d,/,e,?]? y

Saved working directory and index state WIP on master: 1b65b17 added the index file

Défaire l’effet d’une remise

Dans certains cas, il est souhaitable de pouvoir appliquer une modification remisée, réaliser d’autres modifications, puis défaire les modifications de la remise. Git ne fournit pas de commande stash unapply mais il est possible d’obtenir le même effet en extrayant les modifications qui constituent la remise et en appliquant leur inverse :

$ git stash show -p stash@{0} | git apply -R

Ici aussi, si la remise n’est pas indiquée, Git utilise la plus récente.

$ git stash show -p | git apply -R

La création d’un alias permettra d’ajouter effectivement la commande stash-unapply à votre Git. Par exemple :

$ git config --global alias.stash-unapply '!git stash show -p | git apply -R'
$ git stash
$ #... travail, travail, travail
$ git stash-unapply

Créer une branche depuis une remise

Si vous remisez votre travail, et l’oubliez pendant un temps en continuant sur la branche où vous avez créé la remise, vous pouvez avoir un problème en ré-appliquant le travail. Si l’application de la remise essaye de modifier un fichier que vous avez modifié depuis, vous allez obtenir des conflits de fusion et vous devrez essayer de les résoudre. Si vous voulez un moyen plus facile de tester une nouvelle fois les modifications remisées, vous pouvez exécuter git stash branch qui créera une nouvelle branche à votre place, récupérant le commit où vous étiez lorsque vous avez créé la remise, ré-appliquera votre travail dedans, et supprimera finalement votre remise si cela a réussi :

$ git stash branch testchanges
Basculement sur la nouvelle branche 'testchanges'
Sur la branche testchanges
Modifications qui seront validées :
  (utilisez "git reset HEAD <fichier>..." pour désindexer)

     modifié :   index.html

Modifications qui ne seront pas validées :
  (utilisez "git add <fichier>..." pour mettre à jour ce qui sera validé)
  (utilisez "git checkout -- <fichier>..." pour annuler les modifications dans la copie de travail)

     modified:   lib/simplegit.rb

refs/stash@{0} supprimé (f0dfc4d5dc332d1cee34a634182e168c4efc3359)

C’est un bon raccourci pour récupérer facilement du travail remisé et pouvoir travailler dessus dans une nouvelle branche.

Nettoyer son répertoire de travail

Enfin, vous pouvez ne pas souhaiter remiser certain fichiers de votre répertoire de travail, mais simplement vous en débarrasser. La commande git clean s’en chargera pour vous.

Le besoin le plus commun pourra être d’éliminer les scories générées par les fusions ou les outils externes ou d’éliminer les artefacts de compilation pour pouvoir relancer une compilation propre.

Faites néanmoins très attention avec cette commande car elle supprime des fichiers non-suivis de votre répertoire de travail. Si vous changez d’avis, il est souvent impossible de récupérer après coup le contenu de ces fichiers. Une option plus sécurisée consiste à lancer git stash --all pour tout sauvegarder dans une remise.

En supposant que vous souhaitez réellement éliminer les scories et nettoyer votre répertoire de travail, vous pouvez lancer git clean. Pour supprimer tous les fichiers non-suivis, vous pouvez lancer git clean -f -d, qui effacera aussi tout sous-répertoire vide. L’option -f signifie « force », soit « fais-le réellement ».

Si vous souhaitez visualiser ce qui serait fait, vous pouvez lancer la commande avec l’option -n qui signifie « fais-le à blanc et montre-moi ce qui serait supprimé ».

$ git clean -d -n
Supprimerait test.o
Supprimerait tmp/

Par défaut, la commande git clean ne va supprimer que les fichiers non-suivis qui ne sont pas ignorés. Tout fichier qui correspond à un motif de votre fichier .gitignore ou tout autre fichier similaire ne sera pas supprimé. Si vous souhaitez supprimer aussi ces fichiers, comme par exemple les fichiers .o généré par un compilateur pour faire une compilation totale, vous pouvez ajouter l’option -x à la commande de nettoyage.

$ git status -s
 M lib/simplegit.rb
?? build.TMP
?? tmp/

$ git clean -n -d
Supprimerait build.TMP
Supprimerait tmp/

$ git clean -n -d -x
Supprimerait build.TMP
Supprimerait test.o
Supprimerait tmp/

Si vous ne savez pas ce que la commande git clean va effectivement supprimer, lancez-la une première fois avec -n par sécurité avant de transformer le -n en -f et nettoyer définitivement. Un autre choix pour s’assurer de ce qui va être effacé consiste à lancer la commande avec l’option -i ou --interactive.

La commande sera lancée en mode interactif.

$ git clean -x -i
Supprimerait les éléments suivants :
  build.TMP  test.o
*** Commandes ***
    1: clean                2: filter by pattern    3: select by numbers    4: ask each             5: quit
    6: help
Et maintenant ?>

De cette manière, vous pouvez détailler chaque fichier individuellement ou spécifier un motif pour la suppression interactive.