Question Est-il possible de déplacer / renommer des fichiers dans Git et de maintenir leur historique?


Je voudrais renommer / déplacer un sous-arbre de projet dans Git en le déplaçant de

/project/xyz

à

/components/xyz

Si j'utilise une plaine git mv project components, puis tout l'historique de commit pour le xyz project se perd. Y a-t-il un moyen de déplacer cela de telle sorte que l'histoire soit maintenue?


550
2018-02-22 22:11


origine


Réponses:


Git détecte les renames plutôt que de persister avec le commit, donc si vous utilisez git mv ou mv n'a pas d'importance.

La commande de journal prend un --follow argument qui poursuit l'historique avant une opération de changement de nom, c'est-à-dire qu'il recherche un contenu similaire en utilisant les heuristiques:

http://git-scm.com/docs/git-log

Pour rechercher l'historique complet, utilisez la commande suivante:

git log --follow ./path/to/file

560
2018-02-22 22:26



C'est possible pour renommer un fichier et conserver l'historique intact, bien que le fichier soit renommé tout au long de l'historique du référentiel. Ceci est probablement seulement pour les amateurs de git-log obsessionnels, et a des implications sérieuses, y compris celles-ci:

  • Vous pourriez réécrire un historique partagé, ce qui est le plus important NE PAS utiliser avec Git. Si quelqu'un d'autre a cloné le référentiel, vous le cassez. Ils devront re-cloner pour éviter les maux de tête. Cela pourrait être OK si le changement de nom est assez important, mais vous devrez y réfléchir attentivement - vous pourriez finir par bouleverser toute une communauté opensource!
  • Si vous avez référencé le fichier en utilisant son ancien nom plus tôt dans l'historique du référentiel, vous êtes en train de casser les versions antérieures. Pour remédier à cela, vous devrez faire un peu plus de saut de cerceau. Ce n'est pas impossible, juste ennuyeux et peut-être pas la peine.

Maintenant, puisque vous êtes toujours avec moi, vous êtes probablement un développeur solo renommer un fichier complètement isolé. Déplaçons un fichier en utilisant filter-tree!

Supposons que vous allez déplacer un fichier old dans un dossier dir et donnez-lui le nom new

Cela pourrait être fait avec git mv old dir/new && git add -u dir/new, mais ça brise l'histoire.

Au lieu:

git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD

volonté refaire chaque commit dans la branche, en exécutant la commande dans les ticks pour chaque itération. Beaucoup de choses peuvent mal tourner quand vous faites cela. J'effectue normalement un test pour voir si le fichier est présent (sinon il n'est pas encore là pour bouger) et ensuite effectuer les étapes nécessaires pour chausse l'arbre à mon goût. Ici vous pouvez sed à travers des fichiers pour modifier les références au fichier et ainsi de suite. Frappez-vous! :)

Une fois terminé, le fichier est déplacé et le journal est intact. Vous vous sentez comme un pirate ninja.

Aussi; Le répertoire mkdir n'est nécessaire que si vous déplacez le fichier dans un nouveau dossier, bien sûr. le si évitera la création de ce dossier plus tôt dans l'histoire que votre fichier existe.


75
2018-02-28 11:59



Non.

La réponse courte est NON, il n'est pas possible de renommer un fichier dans Git et de se souvenir de l'historique. Et c'est une douleur.

La rumeur a cela git log --follow--find-copies-harder fonctionnera mais cela ne fonctionne pas pour moi, même s'il n'y a aucun changement dans le contenu du fichier, et les mouvements ont été faits avec git mv.

(Initialement, j'ai utilisé Eclipse pour renommer et mettre à jour des paquets en une seule opération, ce qui peut avoir confondu git, mais c'est une chose très courante à faire. --follow semble fonctionner si seulement un mv est effectué, puis un commit et le mv n'est pas trop loin.)

Linus dit que vous êtes censé comprendre l'ensemble du contenu d'un projet logiciel de manière holistique, sans devoir suivre les fichiers individuels. Eh bien, malheureusement, mon petit cerveau ne peut pas faire ça.

C'est vraiment énervant que tant de gens ont répété sans réfléchir l'affirmation que git suit automatiquement les mouvements. Ils ont perdu mon temps. Git ne fait pas une telle chose. Par conception (!) Git ne suit aucun mouvement.

Ma solution consiste à renommer les fichiers à leurs emplacements d'origine. Changez le logiciel pour qu'il corresponde au contrôle de la source. Avec git, il semble que vous ayez besoin de le faire correctement la première fois.

Malheureusement, cela casse Eclipse, qui semble utiliser --follow.
git log --follow Parfois, ne montre pas l'histoire complète des fichiers avec des histoires de renommer compliquées, même si    git log Est-ce que. (Je ne sais pas pourquoi.)

(Il y a des hacks trop intelligents qui remontent et recommencent le vieux travail, mais ils sont plutôt effrayants.) Voir GitHub-Gist: emiller / git-mv-avec-histoire.)


59
2017-09-28 05:46



git log --follow [file]

vous montrera l'histoire en renommant.


37
2018-02-22 22:25



Je fais:

git mv {old} {new}
git add -u {new}

16
2017-11-24 19:07



Objectif

  • Utilisation git am  (inspiré de Smar, emprunté de Exherbo)
  • Ajouter l'historique de validation des fichiers copiés / déplacés
  • D'un répertoire à l'autre
  • Ou d'un référentiel à l'autre

Limitation

  • Les tags et les branches ne sont pas conservés
  • L'historique est coupé sur le nom du fichier de chemin (renommer le répertoire)

Résumé

  1. Extraire l'historique au format e-mail en utilisant
    git log --pretty=email -p --reverse --full-index --binary
  2. Réorganiser l'arborescence de fichiers et mettre à jour les noms de fichiers
  3. Ajouter une nouvelle histoire en utilisant
    cat extracted-history | git am --committer-date-is-author-date

1. Extrait l'histoire au format e-mail

Exemple: Extrait l'histoire de file3, file4 et file5

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

Définir / nettoyer la destination

export historydir=/tmp/mail/dir       # Absolute path
rm -rf "$historydir"    # Caution when cleaning the folder

Extrait l'historique de chaque fichier au format e-mail

cd my_repo/dirB
find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'

Option malheureusement --follow ou --find-copies-harder ne peut pas être combiné avec --reverse. C'est pourquoi l'historique est coupé lorsque le fichier est renommé (ou lorsqu'un répertoire parent est renommé).

Historique temporaire au format e-mail:

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

Dan Bonachea suggère d'inverser les boucles de la commande git log generation dans cette première étape: plutôt que d'exécuter git log une fois par fichier, exécutez-le exactement une fois avec une liste de fichiers sur la ligne de commande et générez un seul journal unifié. De cette façon, il est accepté que modifier plusieurs fichiers reste une seule validation dans le résultat, et tous les nouveaux commits conservent leur ordre relatif d'origine. Notez que cela nécessite également des modifications à la deuxième étape ci-dessous lors de la réécriture des noms de fichiers dans le journal (maintenant unifié).


2. Réorganiser l'arborescence de fichiers et mettre à jour les noms de fichiers

Supposons que vous voulez déplacer ces trois fichiers dans cet autre repo (peut être le même repo).

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # from subdir
│   │   ├── file33    # from file3
│   │   └── file44    # from file4
│   └── dirB2         # new dir
│        └── file5    # from file5
└── dirH
    └── file77

Donc réorganisez vos fichiers:

cd /tmp/mail/dir
mkdir -p dirB/dirB1
mv subdir/file3 dirB/dirB1/file33
mv subdir/file4 dirB/dirB1/file44
mkdir -p dirB/dirB2
mv file5 dirB/dirB2

Votre histoire temporaire est maintenant:

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

Changer aussi les noms de fichiers dans l'historique:

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

3. Appliquer une nouvelle histoire

Votre autre repo est:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

Appliquer les validations à partir des fichiers d'historique temporaires:

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am --committer-date-is-author-date

--committer-date-is-author-date conserve les horodatages de validation d'origine (Dan Bonacheale commentaire).

Votre autre repo est maintenant:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB
│   ├── dirB1
│   │   ├── file33
│   │   └── file44
│   └── dirB2
│        └── file5
└── dirH
    └── file77

Utilisation git status pour voir le nombre de commits prêts à être poussés :-)


Truc supplémentaire: Vérifiez les fichiers renommés / déplacés dans votre repo

Pour lister les fichiers ayant été renommés:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

Plus de personnalisations: vous pouvez compléter la commande git log en utilisant des options --find-copies-harder ou --reverse. Vous pouvez également supprimer les deux premières colonnes en utilisant cut -f3- et grepping complete pattern '{. * =>. *}'.

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'

12
2017-11-24 18:36



Bien que le noyau de git, la plomberie git ne garde pas trace de renommer, l'histoire que vous affichez avec le git log "porcelaine" peut les détecter si vous le souhaitez.

Pour un donné git log utilisez l'option -M:

git log -p -M

Avec une version actuelle de git.

Cela fonctionne pour d'autres commandes comme git diff ainsi que.

Il existe des options pour rendre les comparaisons plus ou moins rigoureuses. Si vous renommez un fichier sans apporter de modifications importantes au fichier en même temps, il est plus facile pour git log et vos amis de détecter le changement de nom. Pour cette raison, certaines personnes renomment les fichiers dans un commit et les modifient dans un autre.

Chaque fois que vous demandez à git de trouver où les fichiers ont été renommés, il y a un coût en utilisation de cpu, donc si vous l'utilisez ou non, et quand, c'est à vous de décider.

Si vous souhaitez que votre historique soit toujours signalé avec la détection de renommer dans un référentiel particulier, vous pouvez utiliser:

git config diff.renames 1

Fichiers se déplaçant d'un répertoire à un autre est détecté. Voici un exemple:

commit c3ee8dfb01e357eba1ab18003be1490a46325992
Author: John S. Gruber <JohnSGruber@gmail.com>
Date:   Wed Feb 22 22:20:19 2017 -0500

    test rename again

diff --git a/yyy/power.py b/zzz/power.py
similarity index 100%
rename from yyy/power.py
rename to zzz/power.py

commit ae181377154eca800832087500c258a20c95d1c3
Author: John S. Gruber <JohnSGruber@gmail.com>
Date:   Wed Feb 22 22:19:17 2017 -0500

    rename test

diff --git a/power.py b/yyy/power.py
similarity index 100%
rename from power.py
rename to yyy/power.py

S'il vous plaît noter que cela fonctionne lorsque vous utilisez diff, pas seulement avec git log. Par exemple:

$ git diff HEAD c3ee8df
diff --git a/power.py b/zzz/power.py
similarity index 100%
rename from power.py
rename to zzz/power.py

Comme un essai j'ai fait un petit changement dans un dossier dans une branche de caractéristique et l'a commise et alors dans la branche principale j'ai renommé le dossier, commis, et ai fait alors un petit changement dans une autre partie du dossier et commited cela. Quand je suis allé à la fonctionnalité feature et fusionné à partir du master, la fusion a renommé le fichier et a fusionné les modifications. Voici la sortie de la fusion:

 $ git merge -v master
 Auto-merging single
 Merge made by the 'recursive' strategy.
  one => single | 4 ++++
  1 file changed, 4 insertions(+)
  rename one => single (67%)

Le résultat a été un répertoire de travail avec le fichier renommé et les deux modifications de texte effectuées. Il est donc possible que git fasse ce qu'il faut malgré le fait qu'il ne suit pas les renames de manière explicite.

C'est une réponse tardive à une vieille question, donc les autres réponses peuvent avoir été correctes pour la version git à ce moment-là.


2
2018-02-23 04:54



Je voudrais renommer / déplacer un sous-arbre de projet dans Git en le déplaçant de

/project/xyz

à

/ composants / xyz

Si j'utilise une plaine git mv project components, puis tout l'historique de commit pour le xyz le projet est perdu.

Non (8 ans plus tard, Git 2.19, T3 2018), car Git détecter le nom du répertoire, et ceci est maintenant mieux documenté.

Voir commettre b00bf1c, commenter 1634688, commenter 0661e49, commettre 4d34dff, commettre 983f464, commettre c840e1a, commettre 9929430 (27 juin 2018), et commettre d4e8062, commettre 5dacd4a (25 juin 2018) par Elijah Newren (newren).
(Fusionné par Junio ​​C Hamano - gitster- dans commettre 0ce5a69, 24 juillet 2018) 

Cela est maintenant expliqué dans Documentation/technical/directory-rename-detection.txt:

Exemple:

Quand tout x/a, x/b et x/c ont déménagé à z/a, z/b et z/c, il est probable que x/d ajouté entre-temps voudrait aussi passer à z/d par   en prenant l'indice que l'ensemble du répertoire 'x'déplacé vers'z'.

Mais ils sont nombreux autres cas, comme:

un côté de l'histoire renomme x -> z, et l'autre renomme un fichier à    x/e, provoquant le besoin de la fusion pour faire un renommage transitif.

Pour simplifier la détection de renommer les répertoires, ces règles sont appliquées par Git:

un couple de règles de base limite quand la détection de renommer le répertoire s'applique:

  1. Si un répertoire donné existe toujours des deux côtés d'une fusion, nous ne considérons pas qu'il a été renommé.
  2. Si un sous-ensemble de fichiers à renommer doit contenir un fichier ou un répertoire (ou se gêner l'un l'autre), désactivez le nom du répertoire pour ces sous-chemins spécifiques et signalez le conflit à l'utilisateur .
  3. Si l'autre côté de l'historique a renommé un répertoire en un chemin que votre côté de l'historique a renommé, ignorez ce renommage particulier de l'autre côté de l'historique pour tout renommage de répertoire implicite (mais avertissez l'utilisateur).

Tu peux voir beaucoup des tests dans t/t6043-merge-rename-directories.sh, qui soulignent également que:

  • a) Si renommer scinde un répertoire en deux ou plusieurs autres, le répertoire le plus renommé est "wins".
  • b) Éviter la détection de renommer le répertoire pour un chemin, si ce chemin est la source d'un changement de nom de part et d'autre d'une fusion.
  • c) N'applique les noms de répertoires implicites aux répertoires que si l'autre côté   de l'histoire est celui qui fait le renommage.

0
2017-07-30 19:37



Je fais bouger les fichiers puis fais

git add -A

qui a mis dans la zone de saturation tous les fichiers supprimés / nouveaux. Ici git se rend compte que le fichier est déplacé.

git commit -m "my message"
git push

Je ne sais pas pourquoi mais ça marche pour moi.


-1
2017-12-18 14:34