Enregistrer l'opération de copie de fichier avec Git

141

Lorsque je déplace un fichier dans git en utilisant git-mv, l'état montre que le fichier a été renommé et même si je modifie certaines parties, il considère toujours que c'est presque la même chose (ce qui est bien car cela me permet de suivre l'historique de celui-ci) .

Lorsque je copie un fichier, le fichier original a un historique que j'aimerais associer à la nouvelle copie.

J'ai essayé de déplacer le fichier puis d'essayer de revérifier l'emplacement d'origine - une fois déplacé, git ne me permettra pas de récupérer l'emplacement d'origine.

J'ai essayé de faire une copie du système de fichiers, puis d'ajouter le fichier - git le répertorie comme un nouveau fichier.

Existe-t-il un moyen de faire en sorte que git enregistre une opération de copie de fichier d'une manière similaire à la façon dont il enregistre un fichier renommer / déplacer où l'historique peut être retracé jusqu'au fichier d'origine?

Hexdoll
la source

Réponses:

113

Git ne fait pas de suivi des renommés ni de suivi des copies, ce qui signifie qu'il n'enregistre ni les renommés ni les copies. Ce qu'il fait à la place est la détection de renommer et de copie . Vous pouvez demander la détection de changement de nom dans git diff(et git show) en utilisant l' -Moption, vous pouvez demander une détection de copie supplémentaire dans les fichiers modifiés en utilisant l' -Coption ( -Cimplique -M), et vous pouvez demander une détection de copie plus coûteuse parmi tous les fichiers avec --find-copies-harderou -C -C(ce qui implique -C, ce qui implique -M). Consultez la page de manuel git-diff .

Vous pouvez également configurer git pour qu'il fasse toujours la détection de renommage en définissant diff.renamesune valeur booléenne vraie (par exemple trueou 1), et vous pouvez demander à git de faire également la détection de copie en le définissant sur copyou copies. Consultez la page de manuel git-config .

Vérifiez également l' -loption git diffet la variable de configuration associée diff.renameLimit.


Notez que cela git log <pathspec>fonctionne différemment dans Git: voici un <pathspec>ensemble de délimiteurs de chemin, où chemin peut être un nom de (sous) répertoire. Il filtre et simplifie l'historique avant que la détection de changement de nom et de copie n'entre en jeu. Si vous souhaitez suivre les renommés et les copies, utilisez git log --follow <filename>(qui est actuellement un peu limité et ne fonctionne que pour un seul fichier).

Jakub Narębski
la source
1
@allyourcode: De quoi êtes-vous confus? Pour activer la détection de copie par défaut, vous réglez diff.renamessur copies(par exemple ' git config diff.renames copies'). Je conviens que c'est un peu contre-intuitif.
Jakub Narębski
Une section que je n'arrive pas à analyser est "et vous pouvez demander à faire par défaut également la détection de changement de nom". Êtes-vous en train de dire qu'il y a quatre valeurs que les noms différents peuvent utiliser (vrai, 1, copie, copies) et qu'ils font tous la même chose?
allyourcode
1
@allyourcode: Je suis désolé, je n'ai pas remarqué cela. Corrigé maintenant, merci.
Jakub Narębski
4
@ peschü: Git utilise une base de données d'objets adressés au contenu comme stockage de référentiel. Le contenu du fichier est stocké dans le contenu 'blob' sous une adresse qui est le hachage SHA-1 du contenu (enfin, type + longueur + contenu). Cela signifie que le contenu donné n'est stocké qu'une seule fois. Nb. cette déduplication automatique a été à l'origine de la création d'un système de sauvegarde "bup", en utilisant le format git pack.
Jakub Narębski
1
Contrairement à la solution ci-dessous, cela ne fonctionne pas avec le suivi des modifications dans une plage. Git log autorise un argument de plage ( git log -L123,456:file.xyz) qui suit correctement les renommés, mais pas les copies, et vous ne pouvez pas passer --follow dans ce cas; aussi, AFAICT, cela ne fonctionne pas avec git blame.
Clément
57

2020-05-19: La solution suivante présente les avantages de ne pas modifier le journal du fichier d'origine, de ne pas créer de conflit de fusion et d'être plus courte.

Vous pouvez forcer Git à détecter l'historique du fichier copié en trois commits:

  • Au lieu de copier, basculez vers une nouvelle branche et déplacez le fichier vers son nouvel emplacement.
  • Ajoutez à nouveau le fichier original à cet endroit.
  • Fusionner la nouvelle branche avec la branche d'origine avec l'option sans avance rapide --no-ff.

(Les crédits reviennent à Raymond Chen .)


La première solution comportait quatre commits:

  • Au lieu de copier, basculez vers une nouvelle branche et déplacez le fichier vers son nouvel emplacement.
  • Basculez vers la branche d'origine et renommez le fichier.
  • Fusionnez la nouvelle branche dans la branche d'origine, résolvant le conflit trivial en conservant les deux fichiers.
  • Restaurez le nom de fichier d'origine dans un commit séparé.

(Solution tirée de https://stackoverflow.com/a/44036771/1389680 .)

Robert Pollak
la source
7
Simplicité, brièveté, 100% ... Cette réponse est un service public ... voter pour tout ce qui est en vue
ptim
1
Quelle est la différence entre moveet rename?
vovan
@vovan Faites-vous référence au fait qu'en bash vous utiliseriez mvpour les deux opérations? J'utilisais «déplacer» pour le cas qui peut impliquer de changer le répertoire du fichier, et «renommer» pour le cas où ce n'est pas le cas.
Robert Pollak
2
J'ai essayé de suivre cette (nouvelle) recette et cela n'a pas fonctionné. Cela pourrait aider si vous montriez les commandes réelles.
Greg Lindahl le
1
@RobertPollak J'ai essayé différentes versions de ceci mais elles n'ont pas fonctionné. Par "déplacer le fichier", voulez-vous dire git mv orig new? Par "lu l'original", voulez-vous dire cp new orig && git add orig?
ᆼ ᆺ ᆼ