Est-il possible de déplacer/renommer des fichiers dans Git et de maintenir leur histoire?
je voudrais renommer/déplacer un projet de sous-arborescence dans Git en le déplaçant d'
/project/xyz
à
/components/xyz
si j'utilise un simple git mv project components
, alors toute l'histoire de propagation du xyz project
est perdue. Est-il un moyen de déplacer ce tels que l'histoire est maintenue?
9 réponses
git détecte les renommages plutôt que de persister l'opération avec le commit, donc que vous utilisiez git mv
ou mv
n'a pas d'importance.
la commande log prend un argument --follow
qui poursuit l'histoire avant une opération de renommage, c'est-à-dire qu'elle recherche un contenu similaire en utilisant les heuristiques:
http://git-scm.com/docs/git-log
pour rechercher l'historique complet, utilisez le suivant commande:
git log --follow ./path/to/file
Il est possible pour renommer un fichier et de conserver l'historique intact, bien qu'il provoque le fichier sera renommé au long de toute l'histoire du référentiel. Ce n'est probablement que pour les obsessionnels git-log-lovers, et a quelques implications sérieuses, dont celles-ci:
- vous pourriez réécrire une histoire partagée, qui est le plus important Ne pas tout en utilisant Git. Si quelqu'un d'autre a cloné le dépôt, vous aurez briser il faire ce. Ils devront recloniser pour éviter les maux de tête. Cela pourrait être correct si le renommage est assez important, mais vous devrez considérer cela avec soin -- 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'histoire du dépôt, vous cassez effectivement les versions précédentes. Pour remédier à cela, vous aurez à faire un peu plus de saut de cerceau. Il n'est pas impossible, juste fastidieux et peut-être pas la peine.
maintenant, puisque vous êtes toujours avec moi, vous êtes probablement un développeur solo renommant 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 lui donner le nom new
cela pourrait être fait avec git mv old dir/new && git add -u dir/new
, mais cela brise l'histoire.
au lieu de:
git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD
sera refaire chaque commit de la branche, l'exécution de la commande dans le tiques pour chaque itération. Beaucoup de choses peuvent mal tourner quand tu fais ça. Normalement je teste pour voir si le fichier est présent (sinon il n'est pas encore là pour bouger) et puis je fais les démarches nécessaires pour dénouer l'arbre à mon goût. Ici, vous pouvez passer par des fichiers pour modifier les références au fichier et ainsi de suite. Faites-vous plaisir! :)
une fois terminé, le fichier est déplacé et le journal est intact. Tu te sens comme un pirate ninja.
aussi; le dir mkdir n'est nécessaire que si vous déplacez le fichier vers 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.
Pas de.
la réponse courte est Non , il n'est pas possible de renommer un fichier en Git et se souvenir de l'histoire. Et c'est une douleur.
la rumeur dit que git log --follow
--find-copies-harder
va fonctionner, mais il ne fonctionne pas pour moi, même si il ya zéro changements dans le contenu du fichier, et les mouvements ont été effectués avec git mv
.
(initialement J'ai utilisé Eclipse pour renommer et mettre à jour les paquets dans une opération, ce qui a pu confondre git. Mais c'est une chose très commune à faire. --follow
ne semble pas fonctionner si seulement un mv
est effectué et un commit
et le mv
n'est pas trop loin.)
Linus dit que vous êtes censé comprendre le contenu entier d'un projet de logiciel de manière holistique, sans avoir besoin de suivre des fichiers individuels. Malheureusement, mon petit cerveau ne peut pas faire ça.
c'est vraiment ennuyeux que tant de gens ont répété sans réfléchir la déclaration que git suit automatiquement les mouvements. Ils ont perdu mon temps. Git ne fait rien de tel. par conception (!) Git ne suit pas du tout les mouvements.
Ma solution est de renommer les fichiers vers leur emplacement d'origine. Changez le logiciel pour qu'il corresponde au contrôle source. Avec git, vous semblez avoir besoin de git dès la première fois.
malheureusement, cela brise L'éclipse, qui semble utiliser --follow
.
git log --follow
Parfois ne montre pas l'histoire complète des fichiers avec des histoires de renommage compliquées, même si
git log
faire. (Je ne sais pas pourquoi.)
(il y a des hacks trop intelligents qui reviennent en arrière et réengagent de vieux travaux, mais ils sont plutôt effrayants. Voir Github-Gist: emiller/git-mv-with-history .)
git log --follow [file]
vous montrera l'histoire à travers les renoms.
objectif
- utilisation
git am
(inspiré de Smar , emprunté de Exherbo ) - Ajouter commettre l'histoire de copié/déplacé les fichiers
- D'un répertoire à un autre
- ou d'un dépôt à un autre
Limitation
- les étiquettes et les branches ne sont pas conservées
- l'Histoire est coupée sur le chemin d'accès du fichier rename (renommer le répertoire)
résumé
- extraire l'historique dans le format de courriel en utilisant
git log --pretty=email -p --reverse --full-index --binary
- réorganiser l'arborescence des fichiers et mettre à jour les noms de fichiers
- ajouter une nouvelle histoire en utilisant
cat extracted-history | git am --committer-date-is-author-date
1. Historique d'extrait dans le format de courriel
exemple: extrait de 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
régler / nettoyer la destination
export historydir=/tmp/mail/dir # Absolute path
rm -rf "$historydir" # Caution when cleaning the folder
extrait historique de chaque fichier dans le format de courriel
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 -- ""151920920"" > "$historydir/"151920920""' {} ';'
malheureusement l'option --follow
ou --find-copies-harder
ne peut pas être combinée avec --reverse
. Ce est pourquoi l'histoire est coupée lorsque le fichier est renommé (ou lorsqu'un parent directory est renommé).
Temporaire de l'histoire au format électronique:
/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 de générer un seul log unifié. De cette façon, s'engage à modifier plusieurs fichiers rester un seul commit dans le résultat, et tous les nouveaux commits maintiennent leur ordre relatif d'origine. Notez que cela nécessite également des changements dans 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 des fichiers et mettre à jour les noms de fichiers
Supposons que vous souhaitez déplacer ces trois fichiers dans ce 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
réorganisez donc votre dossiers:
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
Modifier aussi les noms de fichiers dans l'histoire:
cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:/"151970920":g" -i ""151970920""' {} ';'
3. Appliquer la nouvelle histoire
votre autre pension est:
my_other_repo
├── dirF
│ ├── file55
│ └── file56
└── dirH
└── file77
Appliquer s'engage temporaires, l'historique des fichiers:
cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am --committer-date-is-author-date
--committer-date-is-author-date
préserve les horodatage d'origine ( Dan Bonachea commentaire).
votre autre pension est maintenant:
my_other_repo
├── dirF
│ ├── file55
│ └── file56
├── dirB
│ ├── dirB1
│ │ ├── file33
│ │ └── file44
│ └── dirB2
│ └── file5
└── dirH
└── file77
utiliser git status
pour voir la quantité de propagations prêtes à être poussées: -)
astuce supplémentaire: vérifiez les fichiers renommés / déplacés dans votre rapport
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
utilisant les options --find-copies-harder
ou --reverse
. Vous pouvez également supprimer les deux premières colonnes en utilisant cut -f3-
et gropping complete pattern '{.* = > .*}'.
find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'
alors que le noyau de git, la plomberie git ne garde pas trace de renom, l'histoire que vous affichez avec le git log "porcelaine" peut les détecter si vous le souhaitez.
pour un git log
utiliser l'option-M:
git log-p-M
Avec une version actuelle de git.
cela fonctionne aussi pour d'autres commandes comme git diff
.
il y a des options pour rendre les comparaisons plus ou moins rigoureuses. Si vous renommez un fichier sans apporter de changements significatifs au fichier en même temps, il est plus facile pour git log et ses amis de détecter le renommage. Pour cette raison, certaines personnes renomment des fichiers dans une propagation et les changent dans une autre.
il y a un coût dans l'utilisation cpu chaque fois que vous demandez à git de trouver où les fichiers ont été renommés, donc que vous l'utilisiez ou non, et quand, c'est à vous de décider.
si vous souhaitez que votre historique soit toujours signalé avec la détection de renommage dans un dépôt particulier, vous pouvez utiliser:
git config diff.renommes 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
veuillez noter que cela fonctionne chaque fois que vous utilisez diff, et 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 que j'ai fait une petite modification à un fichier dans une branche et l'a commis, puis dans la branche master j'ai renommé le fichier, commis, puis fait un petit changement dans une autre partie du fichier et engagé. Quand je suis allé à la branche des fonctionnalités et ai fusionné à partir de master la fusion a renommé le fichier et a fusionné les changements. 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 apportées au texte. Il est donc possible pour git de faire la bonne chose malgré le fait qu'il ne trace pas explicitement les renames.
il s'agit d'une réponse tardive à une ancienne question, de sorte que les autres réponses étaient peut-être correctes pour la version git à l'époque.
je voudrais renommer/déplacer un projet de sous-arborescence dans Git en le déplaçant d'
/project/xyz
à
/composants/xyz
si j'utilise un simple
git mv project components
, alors toute l'histoire de propagation du projetxyz
est perdue.
Non (8 ans plus tard, Git 2.19, Q3 2018), parce que Git va détecter le répertoire renommé , et c'est maintenant mieux documentées.
Voir commettre b00bf1c , commettre 1634688 , commettre 0661e49 , commettre 4d34dff , commettre 983f464 , commettre c840e1a , commettre 9929430 (27 Juin 2018), et commettre d4e8062 , commettre 5dacd4a (25 Juin 2018) par Élie Newren ( newren
) .
(fusionné par Junio CA Hamano -- gitster
-- in commit 0ce5a69 , 24 juil 2018)
qui est maintenant expliqué dans Documentation/technical/directory-rename-detection.txt
:
exemple:
lorsque tous les
x/a
,x/b
etx/c
ont été déplacés àz/a
,z/b
etz/c
, il est probable quex/d
ajouté dans l'intervalle voudrait également passer àz/d
par prendre le soupçon que l'ensemble du répertoire 'x
'déplacé'z
'.
, Mais ils sont beaucoup d'autres cas, comme:
d'un côté de l'histoire renomme
x -> z
, et l'autre renomme un fichier pourx/e
, ce qui rend la fusion nécessaire pour faites un changement de nom.
pour simplifier la détection de renommage de répertoire, ces règles sont appliquées par Git:
quelques règles de base limite quand la détection de renommage de répertoire s'applique:
- si un répertoire donné existe encore des deux côtés d'une fusion, nous ne le considérons pas comme ayant été renommé.
- si un sous-ensemble de fichiers à renommer a un fichier ou un répertoire manière (ou serait dans la voie de l'autre), "désactiver" le répertoire renommé pour ces sous-chemins spécifiques et signaler le conflit à l'utilisateur.
- si l'autre côté de l'histoire a renommé un répertoire en un chemin que votre côté de l'histoire a renommé ailleurs, alors ignorez ce renommage particulier de l'autre côté de l'histoire pour tout renommage implicite du répertoire (mais prévenez l'utilisateur).
, Vous pouvez voir beaucoup d'essais en t/t6043-merge-rename-directories.sh
, qui soulignent aussi que:
- a) Si renomme diviser un répertoire en deux ou plusieurs autres, le répertoire avec le plus renomme, "gagne".
- B) éviter la détection de répertoire-renommé pour un chemin, si ce chemin est la source d'un renommé de chaque côté d'une fusion.
- c) n'appliquer les renomages implicites de répertoire aux répertoires que si le autre côté l'histoire est celle de faire le changement de nom.
je fais le déplacement des fichiers et ensuite faire
git add -A
qui ont mis dans la zone de chargement 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 cela fonctionne pour moi.