Comment faire git marquer un fichier supprimé et un nouveau comme un déplacement de fichier?


505

J'ai déplacé un fichier manuellement puis je l'ai modifié. Selon Git, il s'agit d'un nouveau fichier et d'un fichier supprimé. Existe-t-il un moyen de forcer Git à le traiter comme un déplacement de fichier?


3
Pour un fichier donné old_file.txt, puis git mv old_file.txt new_file.txtest équivalent à git rm --cached old_file.txt, mv old_file.txt new_file.txt, git add new_file.txt.
Jarl

3
Jarl: non ce n'est pas. S'il y a également des modifications dans le fichier, git mvne les ajoutera pas au cache, mais le git addfera. Je préfère reculer le fichier pour pouvoir l'utiliser git mv, puis git add -prevoir mon jeu de modifications.
dhardy



Git s'est amélioré au cours des 8 dernières années, s'il ne s'agit que d'un seul fichier, la meilleure réponse stackoverflow.com/a/433142/459 n'a rien fait… mais vous pouvez suivre stackoverflow.com/a/1541072/459 pour obtenir un rm / add mis à jour à un statut mv / modify.
dlamblin

Réponses:


436

Git détectera automatiquement le déplacement / renommage si votre modification n'est pas trop sévère. Juste git addle nouveau fichier et git rml'ancien fichier. git statusmontrera alors s'il a détecté le renommage.

en outre, pour les déplacements dans les répertoires, vous devrez peut-être:

  1. cd en haut de cette structure de répertoires.
  2. Courir git add -A .
  3. Exécutez git statuspour vérifier que le "nouveau fichier" est désormais un fichier "renommé"

Si le statut de git affiche toujours "nouveau fichier" et non "renommé", vous devez suivre les conseils de Hank Gay et effectuer le déplacement et modifier en deux validations distinctes.


75
Clarification: «pas trop grave» signifie que le nouveau fichier et l'ancien fichier sont> 50% «similaires» sur la base de certains indices de similitude utilisés par git.
pjz

151
Quelque chose qui m'a suspendu pendant quelques minutes: si les fichiers renommés et les fichiers supprimés ne sont pas mis en scène pour la validation, ils apparaîtront comme une suppression et un nouveau fichier. Une fois que vous les avez ajoutés à l'index de transfert, il le reconnaîtra comme un renommage.
marczych

19
Il convient de mentionner qu'en parlant de " Git détectera automatiquement le mouvement / renommer ". Il le fait au moment où vous utilisez git status, git logou git diff, pas au moment où vous le faites git add, git mvou git rm. En parlant de détection de renommage, cela n'a de sens que pour les fichiers intermédiaires. Ainsi, un git mvsuivi d'un changement dans le fichier peut apparaître git statuscomme s'il le considère comme un rename, mais lorsque vous utilisez git stage(le même que git add) sur le fichier, il devient clair que le changement est trop important pour être détecté comme un renommage.
Jarl

5
La vraie clé semble être d'ajouter à la fois les nouveaux et les anciens emplacements git add, ce qui, comme le suggère @ jrhorn424, devrait probablement être tout le repo à la fois.
brianary

5
Ce n'est pas infaillible cependant, surtout si le fichier a changé et est petit. Existe-t-il une méthode pour informer spécifiquement git du déménagement?
Rebs

112

Effectuez le déplacement et la modification dans des validations distinctes.


12
Je comprends que Git est capable de gérer simultanément les mouvements et les modifications. Lors de la programmation en Java et de l'utilisation d'un IDE, renommer une classe est à la fois une modification et un déplacement. Je comprends que Git devrait même être capable de le comprendre automatiquement quand il y a un mouvement (hors d'un retrait et d'une création).
pupeno

1
Perl en a également besoin, et je n'ai jamais vu Git détecter le mouvement / renommer.
jrockway

10
@jrockway, j'avais. Cela arrive facilement avec de petits fichiers, je suppose qu'ils "deviennent" trop différents "pour signifier un mouvement".
ANeves

2
Existe-t-il un moyen de le faire automatiquement? J'ai de nombreux fichiers dans l'index, je veux d'abord valider le déplacement, puis le changement, mais c'est difficile à faire manuellement
ReDetection

5
Une chose à noter est que si git diffne reconnaît pas le renommage sur un commit, il ne le reconnaîtra pas sur deux commits. Vous auriez besoin d'utiliser -Maka --find-renamespour le faire. Donc, si la motivation qui vous a amené à cette question est de voir le changement de nom dans une demande de tirage (parlant d'expérience), le diviser en deux commits ne vous fera pas avancer vers cet objectif.
mattliu

44

C'est tout une chose perceptive. Git est généralement assez bon pour reconnaître les mouvements, car GIT est un tracker de contenu

Tout cela dépend vraiment de la façon dont votre "stat" l'affiche. La seule différence ici est le drapeau -M.

git log --stat -M

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300


        Category Restructure

     lib/Gentoo/Repository.pm                |   10 +++++-----
     lib/Gentoo/{ => Repository}/Base.pm     |    2 +-
     lib/Gentoo/{ => Repository}/Category.pm |   12 ++++++------
     lib/Gentoo/{ => Repository}/Package.pm  |   10 +++++-----
     lib/Gentoo/{ => Repository}/Types.pm    |   10 +++++-----
     5 files changed, 22 insertions(+), 22 deletions(-)

git log --stat

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300

    Category Restructure

 lib/Gentoo/Base.pm                |   36 ------------------------
 lib/Gentoo/Category.pm            |   51 ----------------------------------
 lib/Gentoo/Package.pm             |   41 ---------------------------
 lib/Gentoo/Repository.pm          |   10 +++---
 lib/Gentoo/Repository/Base.pm     |   36 ++++++++++++++++++++++++
 lib/Gentoo/Repository/Category.pm |   51 ++++++++++++++++++++++++++++++++++
 lib/Gentoo/Repository/Package.pm  |   41 +++++++++++++++++++++++++++
 lib/Gentoo/Repository/Types.pm    |   55 +++++++++++++++++++++++++++++++++++++
 lib/Gentoo/Types.pm               |   55 -------------------------------------
 9 files changed, 188 insertions(+), 188 deletions(-)

git help log

   -M
       Detect renames.

   -C
       Detect copies as well as renames. See also --find-copies-harder.

76
Désolé si cela semble un peu pédant, mais "Git est généralement plutôt bon pour reconnaître les mouvements, car GIT est un tracker de contenu" me semble être un non-séquenceur. C'est un tracker de contenu, oui, et peut-être qu'il est bon pour détecter les mouvements, mais l'une des déclarations ne suit pas vraiment l'autre. Tout simplement parce que c'est un tracker de contenu, la détection de mouvement n'est pas nécessairement bonne. En fait, un tracker de contenu pourrait ne pas avoir de détection de mouvement du tout.
Laurence Gonsalves

1
@WarrenSeine lorsque vous renommez un répertoire, le SHA1 des fichiers de ce répertoire ne change pas. Tout ce que vous avez est un nouvel objet TREE avec les mêmes SHA1. Il n'y a aucune raison qu'un renommage de répertoire entraîne des modifications importantes des données.
Kent Fredric

1
@KentFredric Vous avez montré qu'en utilisant -M avec "git log", nous pouvons vérifier si le fichier est renommé ou non et je l'ai essayé et son bon fonctionnement, mais quand je poste mon code pour examen et que je vois ce fichier dans gerrit ( gerritcodereview.com ) là, il montre que le fichier est nouvellement ajouté et que le précédent a été supprimé. Il y a donc une option dans "git commit" à l'aide de laquelle je commets et gerrit l'affiche correctement.
Patrick

1
Non, je n'ai rien montré du tout. J'ai montré que Git est capable de prétendre qu'un ajout + suppression est un changement de nom car le contenu est le même. Il n'y a aucun moyen pour lui de savoir ce qui s'est passé, et cela ne fait rien. Tout ce dont Git se souvient est "ajouté" et "supprimé". "Renommé" n'est jamais enregistré.
Kent Fredric

1
Par exemple, j'ai fait beaucoup de "Copie + Edition" sur un repo récemment. La plupart des façons de le voir ne voient que "nouveau fichier", mais si vous réussissez git log -M1 -C1 -B1 -D --find-copies-harder, git peut "découvrir" que le nouveau fichier a peut-être été copié en premier. Parfois, il fait ce droit, d'autres fois, il trouve des fichiers totalement indépendants qui avaient un contenu identique.
Kent Fredric

36

git diff -Mou git log -Mdevrait détecter automatiquement ces changements comme un renommage avec des changements mineurs tant qu'ils le sont. Si vos modifications mineures ne sont pas mineures, vous pouvez réduire la similitude, par exemple

$ git log -M20 -p --stat

pour le réduire de 50% à 20% par défaut.


13
Est-il possible de définir un seuil dans la config?
ReDetection

34

Voici une solution rapide et sale pour un ou plusieurs fichiers renommés et modifiés qui ne sont pas validés.

Disons que le fichier a été nommé fooet maintenant il est nommé bar:

  1. Renommez barun nom temporaire:

    mv bar side
    
  2. Commander foo:

    git checkout HEAD foo
    
  3. Renommer foopour barGit:

    git mv foo bar
    
  4. Renommez maintenant votre fichier temporaire en bar.

    mv side bar
    

Cette dernière étape est ce qui récupère votre contenu modifié dans le fichier.

Bien que cela puisse fonctionner, si le fichier déplacé a un contenu trop différent du git d'origine, il sera plus efficace de décider qu'il s'agit d'un nouvel objet. Permettez-moi de démontrer:

$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md
    modified:   work.js

$ git add README.md work.js # why are the changes unstaged, let's add them.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ git stash # what? let's go back a bit
Saved working directory and index state WIP on dir: f7a8685 update
HEAD is now at f7a8685 update
$ git status
On branch workit
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .idea/

nothing added to commit but untracked files present (use "git add" to track)
$ git stash pop
Removing README
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md

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:    README
    modified:   work.js

Dropped refs/stash@{0} (1ebca3b02e454a400b9fb834ed473c912a00cd2f)
$ git add work.js
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

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:    README

$ git add README # hang on, I want it removed
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ mv README.md Rmd # Still? Try the answer I found.
$ git checkout README
error: pathspec 'README' did not match any file(s) known to git.
$ git checkout HEAD README # Ok the answer needed fixing.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

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:    README.md
    modified:   work.js

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

    Rmd

$ git mv README README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   work.js

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

    Rmd

$ mv Rmd README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md
    modified:   work.js

$ # actually that's half of what I wanted; \
  # and the js being modified twice? Git prefers it in this case.

27
Ce processus ne sert à rien. git n'ajoute aucune métadonnée pour les renommages, git mvc'est simplement une commodité pour une paire git rm/ git add. Si vous avez déjà fait 'mv bar foo', alors tout ce que vous avez à faire est de vous assurer que vous avez git add fooet git rm baravant de faire le commit. Cela pourrait être fait comme une seule git add -Acommande, ou éventuellement une git add foo; git commit -aséquence.
CB Bailey

17
Tout ce que je sais, c'est qu'avant de le faire, Git ne l'a pas reconnu comme un mouvement. Après avoir fait cela, Git l'a reconnu comme un mouvement.

8
Il le reconnaît comme un mouvement. Mais il a également le changement comme un changement non mis en scène maintenant. Une fois que vous avez à addnouveau le fichier, git rompt le déplacement / modification dans un supprimé / ajouté à nouveau.
Michael Piefel

4
Ce processus fonctionnerait si vous git commitaprès l'étape 3, sinon il est incorrect. De plus, @CharlesBailey est correct, vous pouvez aussi facilement faire une normale mv blah fooà l'étape 3 suivie d'un commit et obtenir les mêmes résultats.
DaFlame

5
"Ce processus ne sert à rien." - Cela sert à quelque chose lorsque git est devenu confus et pense qu'un fichier a été supprimé et qu'un autre fichier a été ajouté. Cela élimine la confusion de Git et marque le fichier comme déplacé explicitement sans avoir à effectuer de validations intermédiaires.
Danack

20

Si vous parlez de git statusne pas afficher les noms, essayez git commit --dry-run -aplutôt


11

Si vous utilisez TortoiseGit, il est important de noter que la détection automatique de renommage de Git se produit pendant la validation, mais le fait que cela va se produire n'est pas toujours affiché par le logiciel au préalable. J'avais déplacé deux fichiers vers un répertoire différent et effectué quelques légères modifications. J'utilise TortoiseGit comme outil de validation et la liste des modifications apportées montre les fichiers supprimés et ajoutés, pas déplacés. L'exécution de git status depuis la ligne de commande a montré une situation similaire. Cependant, après avoir validé les fichiers, ils se sont révélés être renommés dans le journal. Donc, la réponse à votre question est, tant que vous n'avez rien fait de trop drastique, Git devrait automatiquement le renommer.

Modifier: Apparemment, si vous ajoutez les nouveaux fichiers, puis effectuez un état git à partir de la ligne de commande, le renommage devrait apparaître avant de valider.

Edit 2: De plus, dans TortoiseGit, ajoutez les nouveaux fichiers dans la boîte de dialogue de validation mais ne les validez pas. Ensuite, si vous allez dans la commande Afficher le journal et regardez le répertoire de travail, vous verrez si Git a détecté le renommage avant de valider.

La même question a été soulevée ici: https://tortoisegit.org/issue/1389 et a été enregistrée comme un bug à corriger ici: https://tortoisegit.org/issue/1440 Il s'avère que c'est un problème d'affichage avec la validation de TortoiseGit dialogue et existe en quelque sorte dans l'état git si vous n'avez pas ajouté les nouveaux fichiers.


2
Vous avez raison, même si TortoiseGit affiche supprimer + ajouter, même si l'état git affiche supprimer + ajouter même si git commit --dry-run affiche supprimer + ajouter, après git commit je vois renommer et non supprimer + ajouter.
Tomas Kubes

1
Je suis presque sûr que la détection automatique de renommage se produit pendant la récupération de l'historique ; dans le commit, c'est toujours ajouter + supprimer. Cela explique également le comportement schizophrénique que vous décrivez. D'où les options ici: stackoverflow.com/a/434078/155892
Mark Sowul

10

Ou vous pourriez essayer la réponse à cette question ici par Amber ! Pour le citer à nouveau:

Tout d'abord, annulez votre ajout par étapes pour le fichier déplacé manuellement:

$ git reset path/to/newfile
$ mv path/to/newfile path/to/oldfile

Ensuite, utilisez Git pour déplacer le fichier:

$ git mv path/to/oldfile path/to/newfile

Bien sûr, si vous avez déjà validé le déplacement manuel, vous souhaiterez peut-être réinitialiser la révision avant le déplacement, puis simplement git mv à partir de là.


7

Utilisez la git mvcommande pour déplacer les fichiers, au lieu des commandes de déplacement du système d'exploitation: https://git-scm.com/docs/git-mv

Veuillez noter que la git mvcommande n'existe que dans les versions Git 1.8.5 et supérieures. Vous devrez donc peut-être mettre à jour votre Git pour utiliser cette commande.


3

J'ai eu ce problème récemment, lors du déplacement (mais sans modification) de certains fichiers.

Le problème est que Git a changé certaines fins de ligne lorsque j'ai déplacé les fichiers et n'a pas pu dire que les fichiers étaient les mêmes.

L'utilisation a git mvréglé le problème, mais cela ne fonctionne que sur des fichiers / répertoires uniques, et j'avais beaucoup de fichiers à faire à la racine du référentiel.

Une façon de résoudre ce problème serait d'utiliser de la magie bash / batch.

Une autre façon est la suivante

  • Déplacez les fichiers et git commit. Cela met à jour les fins de ligne.
  • Déplacez les fichiers vers leur emplacement d'origine, maintenant qu'ils ont les nouvelles fins de ligne, et git commit --amend
  • Déplacez à nouveau les fichiers et git commit --amend. Il n'y a pas de changement dans les fins de ligne cette fois, donc Git est content

1

Pour moi, cela a fonctionné de sauvegarder toutes les modifications avant la validation et de les ressortir. Cela a fait git ré-analyser les fichiers ajoutés / supprimés et les a correctement marqués comme déplacés.


0

Il y a probablement une meilleure façon de faire cela en ligne de commande, et je sais que c'est un hack, mais je n'ai jamais pu trouver une bonne solution.

Utilisation de TortoiseGIT: Si vous avez un commit GIT où certaines opérations de déplacement de fichier apparaissent comme une charge d'ajout / suppression plutôt que de renommer, même si les fichiers n'ont que de petites modifications, alors procédez comme suit:

  1. Vérifiez ce que vous avez fait localement
  2. Enregistrer un mini changement d'une ligne dans un 2ème commit
  3. Accédez à GIT log in tortoise git
  4. Sélectionnez les deux validations, faites un clic droit et sélectionnez «fusionner en une seule validation»

Le nouveau commit affichera désormais correctement les renommages de fichiers… ce qui aidera à maintenir un historique de fichier correct.


0

Lorsque je modifie, renomme et déplace un fichier en même temps, aucune de ces solutions ne fonctionne. La solution est de le faire en deux commits (éditer et renommer / déplacer séparément) puis fixuple deuxième commit via git rebase -ipour l'avoir en un seul commit.


0

La façon dont je comprends cette question est "Comment faire en sorte que git reconnaisse la suppression d'un ancien fichier et la création d'un nouveau fichier lors d'un déplacement de fichier".

Oui dans le répertoire de travail une fois que vous avez supprimé un ancien fichier et inséré un ancien fichier, git statusdira " deleted: old_file" et "Untracked files: ... new_file "

Mais dans l'index / niveau intermédiaire une fois que vous ajoutez et supprimez un fichier à l'aide de git, il sera reconnu comme un déplacement de fichier. Pour ce faire, en supposant que vous avez effectué la suppression et la création à l'aide de votre système d'exploitation, donnez les commandes suivantes:

git add new_file
git rm old_file

Si le contenu du fichier est similaire à 50% ou plus, l'exécution de la git statuscommande devrait vous donner:

renamed: old_file -> new_file

1
git statusdira " deleted: old_file" et " Untracked files: ... new_file": Pas depuis Git 2.18: stackoverflow.com/a/50573107/6309
VonC
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.