Git: Comment supprimer un fichier de l'index sans supprimer les fichiers d'un référentiel

163

Lorsque vous utilisez

git rm --cached myfile

il ne supprime pas du système de fichiers local, ce qui est l'objectif. Mais si vous avez déjà versionné et validé le fichier, l'avez poussé dans un référentiel central et l'avez extrait dans un autre référentiel avant d'utiliser la commande, cela supprimera le fichier de ce système.

Existe-t-il un moyen de supprimer simplement le fichier de la gestion des versions sans le supprimer de tout système de fichiers?

Edit: Clarifié, j'espère.

Fletcher Moore
la source
Peut-être pourriez-vous développer votre cas d'utilisation. L'index est la zone de préparation pour la prochaine validation, alors pourquoi voulez-vous supprimer le fichier de l'index si vous ne voulez pas le supprimer de la branche actuelle?
CB Bailey
1
Je suis désolé. Je voulais dire que myfile a, pour une raison quelconque, été validée et distribuée il y a longtemps. Maintenant, le but est de le supprimer de la gestion des versions sans le supprimer des systèmes des utilisateurs. La raison pour laquelle cela est apparu est que j'ai accidentellement versionné un fichier de configuration. Le fichier de configuration est nécessaire, je ne veux donc pas le supprimer des systèmes étrangers.
Fletcher Moore
J'ai modifié la question pour être plus claire.
Fletcher Moore
3
git rm --cached ne supprimera pas le fichier de l'autre répertoire de travail. Le fichier ne sera supprimé que si une personne travaillant dans ce répertoire exécute une extraction. Le fichier peut facilement être récupéré avec "git checkout HEAD @ {1} foo" (s'il est exécuté immédiatement après l'extraction.)
William Pursell

Réponses:

119

Je ne pense pas qu'un commit Git puisse enregistrer une intention comme "arrêter le suivi de ce fichier, mais ne le supprimez pas".

La mise en œuvre d'une telle intention nécessitera une intervention en dehors de Git dans tous les référentiels qui fusionnent (ou rebasent sur) un commit qui supprime le fichier.


Enregistrer une copie, appliquer la suppression, restaurer

La chose la plus simple à faire est probablement de dire à vos utilisateurs en aval d'enregistrer une copie du fichier, d'extraire votre suppression, puis de restaurer le fichier. S'ils tirent via rebase et "portent" des modifications au fichier, ils obtiendront des conflits. Pour résoudre de tels conflits, utilisez git rm foo.conf && git rebase --continue(si la validation en conflit comporte des modifications en plus de celles du fichier supprimé) ou git rebase --skip(si la validation en conflit n'a été modifiée que sur le fichier supprimé).

Restaurer le fichier comme non suivi après avoir extrait un commit qui le supprime

S'ils ont déjà récupéré votre validation de suppression, ils peuvent toujours récupérer la version précédente du fichier avec git show :

git show @{1}:foo.conf >foo.conf

Ou avec git checkout (par commentaire de William Pursell; mais n'oubliez pas de le retirer de l'index!):

git checkout @{1} -- foo.conf && git rm --cached foo.conf

S'ils ont pris d'autres mesures depuis le retrait de votre suppression (ou s'ils tirent avec rebase dans une HEAD détachée), ils peuvent avoir besoin d'autre chose que @{1}. Ils pourraient utiliser git log -gpour trouver le commit juste avant de retirer votre suppression.


Dans un commentaire, vous mentionnez que le fichier que vous souhaitez «décompresser, mais conserver» est une sorte de fichier de configuration nécessaire à l'exécution du logiciel (directement à partir d'un référentiel).

Conserver le fichier par défaut et l'activer manuellement / automatiquement

S'il n'est pas totalement inacceptable de continuer à maintenir le contenu du fichier de configuration dans le référentiel, vous pourrez peut-être renommer le fichier suivi de (par exemple) foo.confà foo.conf.default, puis indiquer à vos utilisateurs après cp foo.conf.default foo.confavoir appliqué la validation de changement de nom. Ou, si les utilisateurs utilisent déjà une partie existante du référentiel (par exemple un script ou un autre programme configuré par le contenu du référentiel (par exemple Makefileou similaire)) pour lancer / déployer votre logiciel, vous pouvez incorporer un mécanisme par défaut dans le lancement / processus de déploiement:

test -f foo.conf || test -f foo.conf.default &&
    cp foo.conf.default foo.conf

Avec un tel mécanisme défaillant en place, les utilisateurs devraient être en mesure de tirer un commettras qui renomme foo.confà foo.conf.defaultsans avoir à faire un travail supplémentaire. De plus, vous évitez d'avoir à copier manuellement un fichier de configuration si vous effectuez des installations / référentiels supplémentaires à l'avenir.

La réécriture de l'historique nécessite de toute façon une intervention manuelle…

S'il est inacceptable de conserver le contenu dans le référentiel, vous voudrez probablement l'éliminer complètement de l'historique avec quelque chose comme git filter-branch --index-filter …. Cela revient à réécrire l'historique, ce qui nécessitera une intervention manuelle pour chaque branche / référentiel (voir la section «Récupération à partir de la rebase en amont» dans la page de manuel git rebase ). Le traitement spécial requis pour votre fichier de configuration serait juste une autre étape à effectuer lors de la récupération de la réécriture:

  1. Enregistrez une copie du fichier de configuration.
  2. Récupérez de la réécriture.
  3. Restaurez le fichier de configuration.

Ignorez-le pour éviter la récurrence

Quelle que soit la méthode que vous utilisez, vous souhaiterez probablement inclure le nom du fichier de configuration dans un .gitignorefichier du référentiel afin que personne ne puisse à git add foo.confnouveau par inadvertance (c'est possible, mais nécessite -f/ --force). Si vous avez plus d'un fichier de configuration, vous pouvez envisager de les `` déplacer '' tous dans un seul répertoire et d'ignorer le tout (en `` déplaçant '', je veux dire changer l'endroit où le programme s'attend à trouver ses fichiers de configuration, et obtenir les utilisateurs (ou le mécanisme de lancement / déploiement) pour copier / déplacer les fichiers vers leur nouvel emplacement; vous ne voudriez évidemment pas git mv un fichier dans un répertoire que vous ignorerez).

Chris Johnsen
la source
5
Réponse incroyablement approfondie. Je vous remercie!
Fletcher Moore
La réponse de Tom Power, ci-dessous, semble contredire votre première phrase.
Mike S
1
@MikeS: L'effet de --{,no-}assume-unchangedest purement local: son état n'est pas directement enregistré dans les commits. Cela peut aider à empêcher un référentiel de valider de nouvelles modifications dans le fichier, mais il ne le supprime pas du contrôle de version. Si vous pouvez le définir pour tous vos référentiels pertinents, liés et non dénudés, cela peut améliorer votre situation, mais ce n'est pas quelque chose que vous pouvez directement pousser + tirer / rebaser dans d'autres référentiels connexes et non nus (en particulier ceux qui vous ne contrôlez pas, selon les commentaires de clarification du demandeur initial sur la question: voir «systèmes des gens» / «systèmes étrangers»).
Chris Johnsen
1
I do not think a Git commit can record an intention like “stop tracking this file, but do not delete it”.- maintenant il peut, avecgit rm --cached foo.conf
Nick Volynkin
1
@NickVolynkin: La question n'indique-t-elle pas déjà que c'est inadéquat pour le but du demandeur: conserver (automatiquement) le fichier lorsque le commit résultant est extrait dans un autre référentiel?
Chris Johnsen
86

J'ai eu le même problème cette semaine lorsque j'ai commis accidentellement, puis essayé de supprimer un fichier de construction d'un référentiel partagé, et ceci:

http://gitready.com/intermediate/2009/02/18/temporately-ignoring-files.html

a bien fonctionné pour moi et n'a pas été mentionné jusqu'à présent.

git update-index --assume-unchanged <file>

Pour supprimer le fichier qui vous intéresse du contrôle de version, utilisez toutes vos autres commandes comme d'habitude.

git update-index --no-assume-unchanged <file>

Si jamais vous avez voulu le remettre.

Edit: veuillez consulter les commentaires de Chris Johnsen et de KPM, cela ne fonctionne que localement et le fichier reste sous contrôle de version pour les autres utilisateurs s'ils ne le font pas également. La réponse acceptée donne des méthodes plus complètes / correctes pour gérer cela. Aussi quelques notes du lien si vous utilisez cette méthode:

De toute évidence, il y a quelques mises en garde qui entrent en jeu avec cela. Si vous ajoutez directement le fichier, il sera ajouté à l'index. La fusion d'un commit avec cet indicateur activé entraînera l'échec de la fusion afin que vous puissiez la gérer manuellement.

Tom Power
la source
7
Ce n'est pas une réponse au problème spécifique mentionné. Cela ne fonctionnera que pour votre propre référentiel local, donc chaque utilisateur doit le faire lui-même. C'est une douleur.
KPM le
28

Pour supprimer le fichier de l'index, utilisez:

git reset myfile

Cela ne devrait pas affecter votre copie locale ou celle de quelqu'un d'autre.

Armand
la source
1
resetne supprime le fichier de l'index que si le fichier n'est pas dans la validation HEAD actuelle, sinon il rétablit simplement la version d'index à la version HEAD actuelle.
CB Bailey
1
J'ai peut-être mal compris la question, mais elle semble concerner la suppression d'un fichier de l'index sans affecter les validations.
Armand
Comme l'a dit Charles, la réinitialisation ne "supprime pas un fichier". Tapez git help reset pour plus d'informations.
Simon B.
4
Cela répond à la question intitulée "Comment supprimer un fichier de l'index sans supprimer les fichiers d'un référentiel". Bien que l'OP demandait vraiment "Comment annuler le suivi d'un fichier sans supprimer la copie locale."
hewsonism
@hewsonism l'OP a dit "n'importe quel système de fichiers" (même avant toute modification).
jbobbins
19
  1. git rm --cached remove_file
  2. ajouter un fichier à gitignore
  3. git add .gitignore
  4. git commit -m "Excluding"
  5. S'amuser ;)
Анастасия Ермолик
la source
1
C'est le plus simple.
Julien
15

Après avoir exécuté la git rm --cachedcommande, essayez d'ajouter myfileau .gitignorefichier (créez-en un s'il n'existe pas). Cela devrait dire à git d'ignorer myfile.

Le .gitignorefichier est versionné, vous devrez donc le valider et le pousser vers le référentiel distant.

horrible
la source
1

Ma solution est de tirer sur l'autre copie de travail, puis de faire:

git log --pretty="format:" --name-only -n1 | xargs git checkout HEAD^1

qui dit obtenir tous les chemins de fichiers dans le dernier commentaire et les extraire du parent de HEAD. Travail accompli.

Tom Viner
la source
0

Les solutions ci-dessus fonctionnent correctement dans la plupart des cas. Cependant, si vous devez également supprimer toutes les traces de ce fichier (c'est-à-dire les données sensibles telles que les mots de passe), vous voudrez également le supprimer de tout votre historique de validation, car le fichier pourrait encore être récupéré à partir de là.

Voici une solution qui supprime toutes les traces du fichier de tout votre historique de validation, comme s'il n'avait jamais existé, tout en maintenant le fichier en place sur votre système.

https://help.github.com/articles/remove-sensitive-data/

Vous pouvez en fait passer à l'étape 3 si vous êtes dans votre référentiel git local et que vous n'avez pas besoin d'effectuer une analyse à sec. Dans mon cas, je n'avais besoin que des étapes 3 et 6, car j'avais déjà créé mon fichier .gitignore et j'étais dans le référentiel sur lequel je voulais travailler.

Pour voir vos modifications, vous devrez peut-être accéder à la racine GitHub de votre référentiel et actualiser la page. Naviguez ensuite à travers les liens pour accéder à un ancien commit qui avait autrefois le fichier, pour voir qu'il a maintenant été supprimé. Pour moi, le simple fait d'actualiser l'ancienne page de validation n'a pas montré le changement.

Cela avait l'air intimidant au début, mais vraiment, c'était facile et fonctionnait comme un charme! :-)

SherylHohman
la source