Pourquoi existe-t-il deux façons de supprimer un fichier dans Git?

1170

Parfois, git suggère git rm --cachedde supprimer un fichier, parfois git reset HEAD file. Quand devrais-je utiliser quoi?

ÉDITER:

D:\code\gt2>git init
Initialized empty Git repository in D:/code/gt2/.git/
D:\code\gt2>touch a

D:\code\gt2>git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       a
nothing added to commit but untracked files present (use "git add" to track)

D:\code\gt2>git add a

D:\code\gt2>git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   a
#
D:\code\gt2>git commit -m a
[master (root-commit) c271e05] a
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 a

D:\code\gt2>touch b

D:\code\gt2>git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       b
nothing added to commit but untracked files present (use "git add" to track)

D:\code\gt2>git add b

D:\code\gt2>git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   b
#
Senthess
la source
20
Pourquoi? Je dirais que c'est parce que l'interface de ligne de commande de git a évolué de manière organique et n'a jamais fait l'objet d'une restructuration majeure pour rendre les choses cohérentes. (Si vous n'êtes pas d'accord, notez comment git rmpeut à la fois mettre en scène une suppression et mettre en scène un ajout )
Roman Starkov
3
@romkyns: Je suis d'accord que l'interface de Git a plusieurs bizarreries car elle a évolué de manière organique, mais une suppression est sûrement une fonction inverse d'un ajout, donc n'est-il pas logique rmd'annuler add? Comment pensez-vous que rmdevrait se comporter?
Zaz
6
La seule vraie réponse à votre question est que, juste après, git initil n'y a pas HEADde réinitialisation.
Miles Rout
Meilleurs documents pour cela: help.github.com/articles/changing-a-remote-s-url
ScottyBlades
4
@Zaz, je vais donner mon avis. rmimplique la suppression dans un contexte Unix. Ce n'est pas l'opposé d'ajouter à l'index. Une fonction pour supprimer des fichiers ne doit pas être surchargée de fonctions pour changer l'état de transfert. S'il y a des détails d'implémentation qui les rendent pratiques à combiner, cela indique simplement l'absence d'une couche d'abstraction réfléchie dans git, ce qui rendrait l'utilisabilité claire.
Joshua Goldberg

Réponses:

1894

git rm --cached <filePath> ne supprime pas un fichier, il effectue en fait la suppression du ou des fichiers du référentiel (en supposant qu'il a déjà été validé auparavant) mais laisse le fichier dans votre arborescence de travail (vous laissant avec un fichier non suivi).

git reset -- <filePath>se désindexer tout mis en scène les modifications du fichier donné (s).

Cela dit, si vous avez utilisé git rm --cachedun nouveau fichier qui est mis en scène, il semblerait que vous veniez de le décompresser, car il n'avait jamais été validé auparavant.

Mettre à jour git 2.24
Dans cette nouvelle version de git, vous pouvez utiliser à la git restore --stagedplace de git reset. Voir git docs .

Ryan Stewart
la source
71
Je dirais git rm --cachedqu'unstages le fichier mais ne le supprime pas du répertoire de travail.
Pierre de LESPINAY
4
Pour supprimer un fichier mis en attente pour l'ajout afin qu'il ne soit plus mis en scène peut sûrement être appelé "unstaging un fichier mis en attente pour l'ajout", non? Le résultat final n'est pas une suppression par étapes , c'est sûr, donc je pense que le malentendu est totalement compréhensible.
Roman Starkov du
4
Donc, généralement, on utilise git rm --cached <filePath>pour supprimer certains fichiers du référentiel après avoir réalisé qu'ils n'auraient jamais dû être dans le référentiel: il est donc très probable d'exécuter cette commande, puis d'ajouter les fichiers pertinents gitignore. Ai-je raison?
Adrien Be
13
Avec autant de votes sur les questions et les réponses, je dirais que, apparemment, nous voulons avoir un unstagecommandement git.
milosmns
4
"git status" conseille maintenant: utilisez "git restore --staged <file> ..." pour unstage
yucer
334

git rm --cachedest utilisé pour supprimer un fichier de l'index. Dans le cas où le fichier est déjà dans le référentiel, git rm --cachedsupprimera le fichier de l'index, le laissant dans le répertoire de travail et un commit le supprimera désormais également du référentiel. Fondamentalement, après la validation, vous auriez annulé la version du fichier et conservé une copie locale.

git reset HEAD file(qui par défaut utilise l' --mixedindicateur) est différent en ce que dans le cas où le fichier est déjà dans le référentiel, il remplace la version d'index du fichier par celle du référentiel (HEAD), supprimant efficacement les modifications qui y sont apportées .

Dans le cas d'un fichier non versionné, il va décompresser le fichier entier car le fichier n'était pas là dans le HEAD. Dans cet aspect git reset HEAD fileet git rm --cachedsont les mêmes, mais ils ne sont pas identiques (comme expliqué dans le cas des fichiers déjà dans le repo)

À la question de Why are there 2 ways to unstage a file in git?- il n'y a jamais vraiment qu'une seule façon de faire quoi que ce soit dans git. c'est ça la beauté :)

manojlds
la source
7
La réponse acceptée et celle-ci sont excellentes et expliquent pourquoi vous utiliseriez l'une contre l'autre. Mais ils ne répondent pas directement à la question implicite de savoir pourquoi git suggère deux méthodes différentes. Dans le premier cas de l'exemple de l'OP, un git init vient d'être fait. Dans ce cas, git suggère "git rm --cached" car à ce stade, il n'y a pas de commit dans le référentiel et donc HEAD n'est pas valide. "git reset HEAD - a" produit: "fatal: Impossible de résoudre 'HEAD' comme une référence valide."
sootsnoot
5
avec «git checkout», ne perdriez-vous pas toutes les modifications que vous avez apportées au fichier? Ce n'est pas la même chose que de supprimer un fichier, sauf erreur de ma part.
John Deighan
there is never really only one way to do anything in git. that is the beauty of it- Hmm .. Pour quoi ? c'est toujours génial, quand il n'y a qu'une seule façon évidente. cela économise beaucoup de temps et de mémoire dans le cerveau))
Oto Shavadze
128

Tout simplement:

  • git rm --cached <file> fait que git arrête de suivre complètement le fichier (le laissant dans le système de fichiers, contrairement à plain git rm*)
  • git reset HEAD <file> annule toutes les modifications apportées au fichier depuis le dernier commit (mais ne les annule pas dans le système de fichiers, contrairement à ce que le nom de la commande pourrait suggérer **). Le fichier reste sous contrôle de révision.

Si le fichier n'était pas sous contrôle de révision auparavant (c'est-à-dire que vous supprimez un fichier que vous venez de modifier git addpour la première fois), alors les deux commandes ont le même effet, d'où l'apparence de ces deux façons de faire quelque chose. ".

* Gardez à l'esprit la mise en garde mentionnée par @DrewT dans sa réponse, concernant git rm --cachedun fichier qui a été précédemment validé dans le référentiel. Dans le cadre de cette question, d'un fichier qui vient d'être ajouté et non encore validé, il n'y a rien à craindre.

** J'ai eu peur pendant une longue période embarrassante d'utiliser la commande git reset à cause de son nom - et aujourd'hui encore je recherche souvent la syntaxe pour m'assurer de ne pas bousiller. ( mise à jour : j'ai finalement pris le temps de résumer l'utilisation de git resetdans une page tldr , donc maintenant j'ai un meilleur modèle mental de la façon dont cela fonctionne, et une référence rapide pour quand j'oublie certains détails.)

waldyrious
la source
Il estgit rm <file> --cached
néonate
8
Je ne pense vraiment pas que l' édition du 4 août 2015 de cette réponse était une amélioration globale. Cela pourrait avoir corrigé l'exactitude technique (je ne me sens pas qualifié pour évaluer cela), mais je crains que cela rende le ton de la réponse beaucoup moins accessible, en introduisant un langage comme "désactive l'impératif de commencer à suivre un fichier actuellement non suivi. ", et en utilisant un jargon comme" index "et" HEAD ", précisément le genre de truc qui fait peur aux débutants. Si quelqu'un le peut, veuillez modifier pour restaurer une langue plus adaptée aux nouveaux arrivants.
waldyrious
5
D'accord avec @waldyrious. La réponse originale n'était peut-être pas directement sortie du manuel Git, mais elle répondait à la question à un niveau technique suffisant. Les détails techniques auraient dû être clarifiés dans les commentaires, et non comme une modification qui a obscurci l'intention initiale.
Simon Robb
J'ai annulé le montage. Je pense qu'il y a eu une validation suffisante par la communauté (dans les commentaires précédents et les votes sur eux) que la modification a nui à la clarté de la réponse.
waldyrious
Remarque @DrewT avertit que si vous utilisez rm --cachedet poussez , toute personne tirant la même branche verra le ou les fichiers supprimés de son arborescence de travail.
Tom Hale
53

Ce fil est un peu ancien, mais je veux quand même ajouter une petite démonstration car ce n'est toujours pas un problème intuitif:

me$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   to-be-added
#   modified:   to-be-modified
#   deleted:    to-be-removed
#

me$ git reset -q HEAD to-be-added

    # ok

me$ git reset -q HEAD to-be-modified

    # ok

me$ git reset -q HEAD to-be-removed

    # ok

# or alternatively:

me$ git reset -q HEAD to-be-added to-be-removed to-be-modified

    # ok

me$ git status
# On branch master
# Changes not staged for commit:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   to-be-modified
#   deleted:    to-be-removed
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   to-be-added
no changes added to commit (use "git add" and/or "git commit -a")

git reset HEAD(sans -q) donne un avertissement sur le fichier modifié et son code de sortie est 1 qui sera considéré comme une erreur dans un script.

Modifier: git checkout HEAD to-be-modified to-be-removedfonctionne également pour la suppression de la mise en scène, mais supprime complètement la modification de l'espace de travail

Mise à jour de git 2.23.0: De temps en temps, les commandes changent. Maintenant, git statusdit:

  (use "git restore --staged <file>..." to unstage)

... qui fonctionne pour les trois types de changement

Daniel Alder
la source
Merci, il n'était pas tout à fait clair des deux premières réponses (probablement juste mon ignorance sur la terminologie) que git reset a laissé les modifications dans le fichier localement (par opposition à git checkout qui les annulerait).
soupdog
Vous devriez mettre un avertissement au début sur la version, car l'ancienne version supprime les fichiers dans les nouvelles versions
Luis Mauricio
@LuisMauricio quelle commande supprime les fichiers?
Daniel Alder
@DanielAlder sry, je viens de retester, ça ne supprime pas, mon erreur.
Luis Mauricio
36

si vous avez accidentellement organisé des fichiers que vous ne souhaitez pas valider et que vous souhaitez être certain de conserver les modifications, vous pouvez également utiliser:

git stash
git stash pop

cela effectue une réinitialisation de HEAD et réapplique vos modifications, vous permettant de recréer les fichiers individuels pour validation. cela est également utile si vous avez oublié de créer une branche de fonctionnalité pour les demandes d'extraction ( git stash ; git checkout -b <feature> ; git stash pop).

ives
la source
3
Ceci est une solution propre et beaucoup moins inquiétante que de taper "git rm"
Subimage
1
git stashprésente d'autres avantages, car il crée des entrées dans le reflog qui seront ensuite disponibles à l'avenir. en cas de doute, allez-y et faites un git stash(par exemple git stash save -u "WIP notes to self"(le '-u' est d'inclure tous les fichiers nouveaux / non suivis dans le commit de cache) ... puis essayez git reflog show stashde voir la liste des validations de cache et leurs sha. Je recommande un shell alias commealias grs="git reflog show stash"
cweekly
15

Ces 2 commandes ont plusieurs différences subtiles si le fichier en question est déjà dans le repo et sous contrôle de version (précédemment validé etc.):

  • git reset HEAD <file> décompresse le fichier dans le commit en cours.
  • git rm --cached <file>décompactera également le fichier pour les futures validations. Il n'est pas mis en scène jusqu'à ce qu'il soit à nouveau ajouté avecgit add <file> .

Et il y a une autre différence importante:

  • Après avoir exécuté git rm --cached <file>et poussé votre branche vers la télécommande, toute personne tirant votre branche de la télécommande verra le fichier réellement supprimé de son dossier, même si dans votre jeu de travail local, le fichier n'est plus suivi (c'est-à-dire qu'il n'est pas physiquement supprimé du dossier).

Cette dernière différence est importante pour les projets qui incluent un fichier de configuration où chaque développeur de l'équipe a une configuration différente (c'est-à-dire une URL de base, une adresse IP ou un paramètre de port différents), donc si vous utilisez git rm --cached <file>quelqu'un qui tire votre branche, vous devrez manuellement créez la configuration, ou vous pouvez leur envoyer la vôtre et ils peuvent la rééditer dans leurs paramètres IP (etc.), car la suppression n'effectue que les personnes tirant votre branche de la télécommande.

DrewT
la source
10

Disons que vous avez stageun répertoire entier via git add <folder>, mais vous voulez exclure un fichier de la liste intermédiaire (c'est-à-dire la liste qui se génère lors de l'exécution git status) et conserver les modifications dans le fichier exclu (vous travailliez sur quelque chose et il n'est pas prêt pour la validation, mais vous ne voulez pas perdre votre travail ...). Vous pouvez simplement utiliser:

git reset <file>

Lorsque vous exécutez git status, vous verrez que les fichiers que vous resetêtes unstagedet le reste des fichiers addedsont toujours dans la stagedliste.

jiminikiz
la source
10

1.

D:\code\gt2>git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   a

(utilisez "git rm --cached ..." pour mettre en scène)

  • git est un système de pointeurs

  • vous n'avez pas encore de commit pour changer votre pointeur en

  • la seule façon de `` retirer des fichiers du seau pointé '' est de supprimer les fichiers auxquels vous avez demandé à git de surveiller les changements

2.

D:\code\gt2>git commit -m a
[master (root-commit) c271e05] a
0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 a

git commit -ma

  • vous vous êtes engagé, " enregistré »

3.

D:\code\gt2>git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   b
#

(utilisez "git reset HEAD ..." pour mettre en scène)

  • vous avez fait un commit dans votre code à ce moment
  • vous pouvez maintenant réinitialiser votre pointeur sur votre commit " revenir à la dernière sauvegarde "
Timothy LJ Stewart
la source
1
C'est en fait la seule réponse qui répond correctement à la question, l'OMI. Il répond en fait à la question, qui n'est pas `` quelles sont les différences entre 'git rm --cached' et 'git reset HEAD' mais 'pourquoi git donne-t-il de manière incohérente les deux comme options?', La réponse étant qu'il n'y a pas de HEAD à réinitialiser à quand vous git initpour la première fois.
Miles Rout
5

Je suis surpris que personne n'ait mentionné le git reflog ( http://git-scm.com/docs/git-reflog ):

# git reflog
<find the place before your staged anything>
# git reset HEAD@{1}

Le reflog est un historique git qui non seulement suit les modifications apportées au référentiel, mais également les actions de l'utilisateur (par exemple, tirer, extraire une branche différente, etc.) et permet d'annuler ces actions. Ainsi, au lieu de supprimer la mise en scène du fichier qui a été mal organisé, vous pouvez revenir au point où vous n'avez pas mis en scène les fichiers.

Ceci est similaire git reset HEAD <file>mais peut dans certains cas être plus granuleux.

Désolé - ne répond pas vraiment à votre question, mais indique simplement une autre façon de décompresser les fichiers que j'utilise assez souvent (pour ma part, j'aime beaucoup les réponses de Ryan Stewart et waldyrious.);) J'espère que cela aide.

Alex
la source
5

Utilisez simplement:

git reset HEAD <filename>

Cela décompose le fichier et conserve les modifications que vous y avez apportées, vous pouvez donc, à son tour, modifier les branches si vous le souhaitez et git addces fichiers à la place d'une autre branche. Toutes les modifications sont conservées.

Edgar Quintero
la source
3

Il me semble que git rm --cached <file>le fichier est supprimé de l'index sans le supprimer du répertoire où un simple git rm <file>ferait les deux, tout comme un système d'exploitation rm <file>supprimerait le fichier du répertoire sans supprimer sa version.

ernie.cordell
la source
1

Pour les versions 2.23 et supérieures uniquement,

Au lieu de ces suggestions, vous pouvez utiliser git restore --staged <file>pour unstagele (s) fichier (s).

Kaan Taha Köken
la source
Cela fonctionne aussi bien avec les options --stagequ'avec --staged.
dhana1310