Gérer les renommages de fichiers dans git

440

Je lirais que lorsque vous renommez des fichiers dans git , vous devez valider toutes les modifications, effectuer votre renommage, puis mettre en scène votre fichier renommé. Git reconnaîtra le fichier à partir du contenu, plutôt que de le voir comme un nouveau fichier non suivi, et conservera l'historique des modifications.

Cependant, en faisant juste ce soir, j'ai fini par revenir git mv.

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#

Renommer ma feuille de style dans le Finder de iphone.cssàmobile.css

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   deleted:    css/iphone.css
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   css/mobile.css

Git pense maintenant que j'ai supprimé un fichier CSS et en ai ajouté un nouveau. Pas ce que je veux, permet d'annuler le changement de nom et de laisser git faire le travail.

> $ git reset HEAD .
Unstaged changes after reset:
M   css/iphone.css
M   index.html

Retour là où j'ai commencé.

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#

Permet d'utiliser à la git mvplace.

> $ git mv css/iphone.css css/mobile.css
> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    css/iphone.css -> css/mobile.css
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   index.html
#

On dirait que nous sommes bons. Alors pourquoi git n'a-t-il pas reconnu le changement de nom la première fois quand j'ai utilisé le Finder?

Greg K
la source
29
Git suit le contenu, pas les fichiers, donc peu importe comment vous obtenez votre index dans le bon état - add+rmou mv- il produit le même résultat. Git utilise ensuite sa détection de renommer / copier pour vous faire savoir qu'il s'agissait d'un renommage. La source que vous avez citée est également inexacte. Peu importe que vous modifiiez + renommer dans le même commit ou non. Lorsque vous effectuez un diff à la fois sur la modification et le changement de nom, la détection de changement de nom le verra comme un changement de nom + modification, ou si la modification est une réécriture totale, elle s'affichera comme ajoutée et supprimée - peu importe comment vous avez effectué il.
Cascabel
6
Si cela est vrai, pourquoi ne l'a-t-il pas détecté avec mon changement de nom à l'aide du Finder?
Greg K
26
git mv old newmet automatiquement à jour l'index. Lorsque vous renommez en dehors de Git, vous devrez faire le git add newet git rm oldpour mettre en scène les modifications de l'index. Une fois que vous avez fait cela, git statuscela fonctionnera comme prévu.
Chris Johnsen
4
Je viens de déplacer un tas de fichiers dans un répertoire public_html, qui sont suivis dans git. Après avoir joué git add .et git commit, il montrait toujours un tas de fichiers «supprimés» dans git status. J'ai effectué un git commit -aet les suppressions ont été validées mais maintenant je n'ai plus d'historique sur les fichiers qui y vivent public_htmlmaintenant. Ce flux de travail n'est pas aussi fluide que je le souhaiterais.
Greg K

Réponses:

352

Pour git mvla page de manuel dit

L'index est mis à jour après la réussite, […]

Donc, dans un premier temps, vous devez mettre à jour l'index par vous-même (en utilisant git add mobile.css). Cependant
git status , deux fichiers différents seront toujours affichés

$ git status
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       new file:   mobile.css
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       deleted:    iphone.css
#

Vous pouvez obtenir une sortie différente en exécutant git commit --dry-run -a, ce qui se traduit par ce que vous attendez:

Tanascius@H181 /d/temp/blo (master)
$ git commit --dry-run -a
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       renamed:    iphone.css -> mobile.css
#

Je ne peux pas vous dire exactement pourquoi nous voyons ces différences entre git statuset
git commit --dry-run -a, mais voici un indice de Linus :

git ne se soucie vraiment pas de toute la "détection de renommage" en interne, et toutes les validations que vous avez faites avec des renommages sont totalement indépendantes de l'heuristique que nous utilisons ensuite pour afficher les renommages.

A dry-runutilise les vrais mécanismes de changement de nom, alors que git statusprobablement pas.

tanascius
la source
1
Vous avez omis de mentionner l'étape où vous l'avez fait git add mobile.css. Sans cela git status -a, il aurait seulement «vu» la suppression du iphone.cssfichier précédemment suivi mais n'aurait pas touché le nouveau mobile.cssfichier non suivi . En outre, git status -an'est pas valide avec Git 1.7.0 et versions ultérieures. "" Git status "n'est plus" git commit --dry-run "." dans kernel.org/pub/software/scm/git/docs/RelNotes-1.7.0.txt . À utiliser git commit --dry-run -asi vous souhaitez cette fonctionnalité. Comme d'autres l'ont dit, il suffit de mettre à jour l'index et git statusfonctionnera comme prévu par l'OP.
Chris Johnsen
3
si vous faites une opération normale, git commitelle ne valide pas le fichier renommé et l'arborescence de travail est toujours la même. git commit -adéfait à peu près tous les aspects du workflow / modèle de réflexion de git - chaque changement est engagé. que se passe-t-il si vous souhaitez uniquement renommer le fichier, mais valider les modifications index.htmldans un autre commit?
knittl
@Chris: oui, bien sûr, j'ai ajouté mobile.cssce que j'aurais dû mentionner. Mais c'est le point de ma réponse: la page de manuel dit que the index is updatedlorsque vous utilisez git-mv. Merci pour la status -aclarification, j'ai utilisé git 1.6.4
tanascius
4
Très bonne réponse! Je me cognais la tête contre le mur en essayant de comprendre pourquoi git statusne détectait pas le changement de nom. Courir git commit -a --dry-runaprès avoir ajouté mes "nouveaux" fichiers a montré les renommées et m'a finalement donné confiance pour m'engager!
stephen.hanson
1
Dans git 1.9.1 git statusse comporte maintenant comme git commit.
Jacques René Mesrine
77

Vous devez ajouter les deux fichiers modifiés à l'index avant que git ne le reconnaisse comme un mouvement.

La seule différence entre mv old newet git mv old newest que le git mv ajoute également les fichiers à l'index.

mv old newalors git add -Aça aurait marché aussi.

Notez que vous ne pouvez pas simplement l'utiliser git add .car cela n'ajoute pas de suppressions à l'index.

Voir Différence entre "git add -A" et "git add".

non rectangulaire
la source
3
Merci pour le git add -Alien, très utile, car je cherchais un tel raccourci!
PhiLho
5
Notez que avec git 2, n'ajouter le transfert à l'index. git add .
Nick McCurdy
19

Le mieux est de l'essayer par vous-même.

mkdir test
cd test
git init
touch aaa.txt
git add .
git commit -a -m "New file"
mv aaa.txt bbb.txt
git add .
git status
git commit --dry-run -a

Maintenant, git status et git commit --dry-run -a affiche deux résultats différents où git status affiche bbb.txt comme un nouveau fichier / aaa.txt est supprimé, et les commandes --dry-run montrent le renommage réel.

~/test$ git status

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   bbb.txt
#
# 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)
#
#   deleted:    aaa.txt
#


/test$ git commit --dry-run -a

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    aaa.txt -> bbb.txt
#

Maintenant, allez-y et faites l'enregistrement.

git commit -a -m "Rename"

Vous pouvez maintenant voir que le fichier est en fait renommé et que ce qui est affiché dans git status est faux.

Morale de l'histoire: si vous n'êtes pas sûr que votre fichier ait été renommé, lancez un "git commit --dry-run -a". Si cela montre que le fichier est renommé, vous êtes prêt à partir.

dimuthu
la source
3
Pour ce qui compte pour Git, les deux sont corrects . Cependant, ce dernier est plus proche de la façon dont vous, en tant que commissaire, le voyez probablement. La vraie différence entre renommer et supprimer + créer se situe uniquement au niveau du système d'exploitation / système de fichiers (par exemple, le même inode # contre le nouvel inode #), ce dont Git ne se soucie pas vraiment.
Alois Mahdal
17

Pour git 1.7.x, les commandes suivantes ont fonctionné pour moi:

git mv css/iphone.css css/mobile.css
git commit -m 'Rename folder.' 

Il n'y avait pas besoin de git add, car le fichier d'origine (c'est-à-dire css / mobile.css) était déjà dans les fichiers validés précédemment.

GrigorisG
la source
6
Cette. Toutes les autres réponses sont ridiculement et inutilement complexes. Cela conserve l'historique des fichiers entre les validations afin que les fusions avant / après le changement de nom du fichier ne soient pas interrompues.
Phlucious
10

vous devez git add css/mobile.cssle nouveau fichier et git rm css/iphone.css, donc git le sait. alors il affichera la même sortiegit status

vous pouvez le voir clairement dans la sortie d'état (le nouveau nom du fichier):

# Untracked files:
#   (use "git add <file>..." to include in what will be committed)

et (l'ancien nom):

# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)

je pense que dans les coulisses git mvn'est rien de plus qu'un script wrapper qui fait exactement cela: supprimer le fichier de l'index et l'ajouter sous un nom différent

knittl
la source
Je ne pensais pas que je devais le faire git rm css/iphone.cssparce que je pensais que cela supprimerait l'historique existant. Peut-être que je comprends mal le flux de travail dans git.
Greg K
4
@Greg K: git rmne supprimera pas l'historique. Il supprime uniquement une entrée de l'index afin que le prochain commit n'ait pas l'entrée. Cependant, il existera toujours dans les commits ancestraux. Ce qui peut vous dérouter, c'est que (par exemple) git log -- news'arrêtera au point où vous vous êtes engagé git mv old new. Si vous souhaitez suivre les renommages, utilisez git log --follow -- new.
Chris Johnsen
9

Pensons à vos fichiers du point de vue git.

Gardez à l'esprit que git ne suit aucune métadonnée sur vos fichiers

Votre référentiel a (entre autres)

$ cd repo
$ ls
...
iphone.css
...

et il est sous contrôle git:

$ git ls-files --error-unmatch iphone.css &>/dev/null && echo file is tracked
file is tracked

Testez cela avec:

$ touch newfile
$ git ls-files --error-unmatch newfile &>/dev/null && echo file is tracked
(no output, it is not tracked)
$ rm newfile

Quand tu fais

$ mv iphone.css mobile.css

Du point de vue git,

  • il n'y a pas iphone.css (il est supprimé -git vous en avertit-).
  • il y a un nouveau fichier mobile.css .
  • Ces fichiers sont totalement indépendants.

Ainsi, git conseille sur les fichiers qu'il connaît déjà ( iphone.css ) et les nouveaux fichiers qu'il détecte ( mobile.css ) mais uniquement lorsque les fichiers sont dans l'index ou que HEAD git commence à vérifier leur contenu.

En ce moment, ni "suppression de iphone.css" ni mobile.css ne sont sur l'index.

Ajouter la suppression iphone.css à l'index

$ git rm iphone.css

git vous dit exactement ce qui s'est passé: ( iphone.css est supprimé. Il ne s'est plus rien passé)

puis ajoutez un nouveau fichier mobile.css

$ git add mobile.css

Cette fois, la suppression et le nouveau fichier sont sur l'index. Maintenant, git détecte que le contexte est le même et l'expose comme un renommage. En fait, si les fichiers sont similaires à 50%, cela détectera cela comme un renommage, ce qui vous permettra de changer un peu mobile.css tout en conservant l'opération comme un renommage.

Voir ceci est reproductible sur git diff. Maintenant que vos fichiers sont sur index, vous devez utiliser --cached. Modifiez un peu mobile.css , ajoutez-le à l'index et voyez la différence entre:

$ git diff --cached 

et

$ git diff --cached -M

-Mest l'option "détecter les renommages" pour git diff. -Msignifie -M50%(50% ou plus de similitude fera git l'exprimer comme un renommage) mais vous pouvez le réduire à -M20%(20%) si vous éditez beaucoup mobile.css.

albfan
la source
8

Étape 1: renommer le fichier de l'ancien fichier en nouveau fichier

git mv #oldfile #newfile

Étape 2: git commit et ajout de commentaires

git commit -m "rename oldfile to newfile"

Étape 3: envoyez cette modification au serveur distant

git push origin #localbranch:#remotebranch
Haimei
la source
1
Veuillez ajouter un commentaire afin qu'il soit utile à OP
Devrath
L'étape 2 n'est pas nécessaire. Après git mv, le nouveau fichier est déjà dans l'index.
Alois Mahdal
7

Git reconnaîtra le fichier à partir du contenu, plutôt que de le voir comme un nouveau fichier non suivi

C'est là que vous vous êtes trompé.

Ce n'est qu'après avoir ajouté le fichier, que git le reconnaîtra à partir du contenu.

hasen
la source
Exactement. Lorsque git mis en scène affichera le renommage correctement.
Max MacLeod
3

Vous n'avez pas mis en scène les résultats de votre mouvement de recherche. Je crois que si vous faisiez le déplacement via le Finder et que vous le git add css/mobile.css ; git rm css/iphone.cssfaisiez, git calculerait le hachage du nouveau fichier et ne réaliserait alors que le hachage des fichiers correspond (et donc c'est un renommage).

Mike Seplowitz
la source
2

Dans les cas où vous devez vraiment renommer les fichiers manuellement, par exemple. en utilisant un script pour renommer par lots un tas de fichiers, puis en utilisant git add -A .travaillé pour moi.

pckben
la source
2

Pour les utilisateurs de Xcode: si vous renommez votre fichier dans Xcode, l'icône du badge change pour s'ajouter. Si vous effectuez une validation en utilisant XCode, vous créerez en fait un nouveau fichier et perdrez l'historique.

Une solution de contournement est facile mais vous devez le faire avant de valider en utilisant Xcode:

  1. Faites un git Status sur votre dossier. Vous devez voir que les modifications par étapes sont correctes:

renommé: Project / OldName.h -> Project / NewName.h renommé: Project / OldName.m -> Project / NewName.m

  1. faire un commit -m 'changement de nom'

Revenez ensuite à XCode et vous verrez le badge changé de A à M et il est sauvegardé pour valider les changements futurs en utilisant xcode maintenant.

doozMen
la source