Comment git reset –hard un sous-répertoire?

MISE À JOUR : Cela fonctionnera plus intuitivement à partir de Git 1.8.3, voir ma propre réponse .

Imaginez le cas d’utilisation suivant: je veux supprimer tous les changements dans un sous-répertoire spécifique de mon arbre de travail Git, en laissant tous les autres sous-répertoires intacts.

Quelle est la commande Git appropriée pour cette opération?

Le script ci-dessous illustre le problème. Insérez la commande appropriée en dessous du commentaire How to make files – la commande en cours restaurera le fichier a/c/ac qui est censé être exclu par l’extraction fragmentée. Notez que je ne veux pas restaurer explicitement a/a et a/b , je ne connais que “a” et je veux tout restaurer en dessous. EDIT : Et je ne sais pas non plus b , ni quels autres répertoires résident au même niveau que a .

 #!/bin/sh rm -rf repo; git init repo; cd repo for f in ab; do for g in abc; do mkdir -p $f/$g touch $f/$g/$f$g git add $f/$g git commit -m "added $f/$g" done done git config core.sparsecheckout true echo a/a > .git/info/sparse-checkout echo a/b >> .git/info/sparse-checkout echo b/a >> .git/info/sparse-checkout git read-tree -m -u HEAD echo "After read-tree:" find * -type f rm a/a/aa rm a/b/ab echo >> b/a/ba echo "After modifying:" find * -type f git status # How to make files a/* reappear without changing b and without recreating a/c? git checkout -- a echo "After checkout:" git status find * -type f 

Note ( commentée par Dan Fabulich ):

  • git checkout -- ne fait pas de réinitialisation matérielle: il remplace le contenu de l’arborescence de travail par le contenu intermédiaire.
  • git checkout HEAD -- effectue une réinitialisation matérielle pour un chemin, remplaçant à la fois l’index et l’arborescence de travail par la version issue de l’engagement HEAD .

Comme l’a répondu Ajedi32 , les deux formulaires d’extraction ne suppriment pas les fichiers supprimés dans la révision cible .
Si vous avez des fichiers supplémentaires dans l’arbre de travail qui n’existent pas dans HEAD, une git checkout HEAD -- ne les supprimera pas.

Mais cette vérification peut respecter un git update-index --skip-worktree (pour les répertoires que vous souhaitez ignorer), comme indiqué dans ” Pourquoi les fichiers exclus réapparaissent-ils dans mon extraction git? “.

Selon le développeur de Git, Duy Nguyen, qui a gentiment mis en œuvre la fonctionnalité et un commutateur de compatibilité , les éléments suivants fonctionnent comme prévu à partir de Git 1.8.3 :

 git checkout -- a 

(où a est le répertoire que vous souhaitez réinitialiser). Le comportement d’origine est accessible via

 git checkout --ignore-skip-worktree-bits -- a 

Essayez de changer

 git checkout -- a 

à

 git checkout -- `git ls-files -m -- a` 

Depuis la version 1.7.0, les ls-files de Git honorent l’indicateur skip-worktree .

Exécuter votre script de test (avec quelques modifications mineures modifiant git commit … pour git commit -q et git status vers git status --short ):

 Initialized empty Git repository in /home/user/repo/.git/ After read-tree: a/a/aa a/b/ab b/a/ba After modifying: b/a/ba D a/a/aa D a/b/ab M b/a/ba After checkout: M b/a/ba a/a/aa a/c/ac a/b/ab b/a/ba 

Exécuter votre script de test avec les sorties de modification de checkout proposées:

 Initialized empty Git repository in /home/user/repo/.git/ After read-tree: a/a/aa a/b/ab b/a/ba After modifying: b/a/ba D a/a/aa D a/b/ab M b/a/ba After checkout: M b/a/ba a/a/aa a/b/ab b/a/ba 

Dans le cas de la simple suppression des modifications, le git checkout -- path/ ou git checkout HEAD -- path/ suggéré par les autres réponses fonctionnent très bien. Toutefois, lorsque vous souhaitez réinitialiser un répertoire à une révision autre que HEAD, cette solution présente un problème important: elle ne supprime pas les fichiers supprimés dans la révision cible.

Au lieu de cela, j’ai commencé à utiliser la commande suivante:

git diff --cached commit -- subdir | git apply -R --index

Cela fonctionne en trouvant le diff entre le commit cible et l’index, puis en appliquant ce diff en sens inverse au répertoire de travail et à l’index. Fondamentalement, cela signifie que le contenu de l’index correspond au contenu de la révision que vous avez spécifiée. Le fait que git diff prenne un argument de chemin vous permet de limiter cet effet à un fichier ou à un répertoire spécifique.

Comme cette commande est assez longue et que je compte l’utiliser fréquemment, j’ai configuré un alias pour celui-ci que j’ai nommé reset-checkout :

 git config --global alias.reset-checkout '!f() { git diff --cached "$@" | git apply -R --index; }; f' 

Vous pouvez l’utiliser comme ceci:

 git reset-checkout 451a9a4 -- path/to/directory 

Ou juste:

 git reset-checkout 451a9a4 

Une réinitialisation changera normalement tout, mais vous pouvez utiliser git stash pour choisir ce que vous voulez conserver. Comme vous l’avez mentionné, stash n’accepte pas directement un chemin, mais il peut toujours être utilisé pour conserver un chemin spécifique avec l’ --keep-index . Dans votre exemple, vous devriez cacher le répertoire b, puis réinitialiser tout le rest.

 # How to make files a/* reappear without changing b and without recreating a/c? git add b #add the directory you want to keep git stash --keep-index #stash anything that isn't added git reset #unstage the b directory git stash drop #clean up the stash (optional) 

Cela vous amène à un point où la dernière partie de votre script va afficher ceci:

 After checkout: # On branch master # Changes not staged for commit: # # modified: b/a/ba # no changes added to commit (use "git add" and/or "git commit -a") a/a/aa a/b/ab b/a/ba 

Je crois que c’était le résultat cible (b rest modifié, un fichier / * est de retour, a / c n’est pas recréé).

Cette approche présente l’avantage supplémentaire d’être très flexible. vous pouvez être aussi précis que vous voulez append des fichiers spécifiques, mais pas d’autres, dans un répertoire.

Si la taille du sous-répertoire n’est pas particulièrement importante ET que vous souhaitez restr à l’écart de la CLI, voici une solution rapide pour réinitialiser manuellement le sous-répertoire:

  1. Passez à la twig principale et copiez le sous-répertoire à réinitialiser.
  2. Revenez maintenant à votre twig et remplacez le sous-répertoire par la copie que vous venez de créer à l’étape 1.
  3. Engagez les changements.

À votre santé. Vous venez de réinitialiser manuellement un sous-répertoire de votre twig pour qu’il soit identique à celui de la twig principale!

La réponse d’ Ajedi32 est ce que je cherchais mais pour certains commits j’ai rencontré cette erreur:

error: cannot apply binary patch to 'path/to/directory' without full index line

Peut-être parce que certains fichiers du répertoire sont des fichiers binarys. L’ajout de l’option –binary à la commande git diff l’a corrigé:

 git diff --binary --cached commit -- path/to/directory | git apply -R --index