205 votes

Copie Git d'un fichier en conservant l'historique

J'ai une question quelque peu confuse dans Git. Disons que j'ai un fichier dir1/A.txt commis et git conserve l'historique des commits

Je dois maintenant copier le fichier dans dir2/A.txt (pas déplacer, mais copier). Je sais qu'il existe un git mv mais j'ai besoin de la commande dir2/A.txt pour avoir le même historique de commits que dir1/A.txt et dir1/A.txt d'y rester.

Je ne prévois pas de mise à jour A.txt une fois la copie créée et tous les travaux ultérieurs seront effectués sur dir2/A.txt

Je sais que cela semble confus, j'ajouterai que cette situation concerne un module basé sur java (projet mavenisé) et que nous avons besoin de créer une nouvelle version du code pour que nos clients aient la possibilité d'avoir 2 versions différentes en cours d'exécution, la première version sera supprimée à terme lorsque l'alignement sera effectué. Nous pouvons utiliser le versioning maven bien sûr, je suis juste novice en Git et curieux de savoir ce que Git peut apporter ici.

213voto

Peter Dillinger Points 11

Tout ce que vous avez à faire, c'est.. :

  1. déplacer le fichier à deux endroits différents,
  2. fusionner les deux commits qui font ce qui précède, et
  3. déplacer une copie vers l'emplacement d'origine.

Vous pourrez voir les attributions historiques (à l'aide de git blame ) et l'historique complet des modifications (en utilisant git log ) pour les deux fichiers.

Supposons que vous souhaitiez créer une copie du fichier foo appelé bar . Dans ce cas, le flux de travail que vous utiliseriez ressemblerait à ceci :

git mv foo bar
git commit

SAVED=`git rev-parse HEAD`
git reset --hard HEAD^
git mv foo copy
git commit

git merge $SAVED     # This will generate conflicts
git commit -a        # Trivially resolved like this

git mv copy foo
git commit

Pourquoi cela fonctionne-t-il ?

Après avoir exécuté les commandes ci-dessus, vous obtenez un historique des révisions qui ressemble à ceci :

( revision history )            ( files )

    ORIG_HEAD                      foo
     /     \                      /   \
SAVED       ALTERNATE          bar     copy
     \     /                      \   /
      MERGED                     bar,copy
        |                           |
     RESTORED                    bar,foo

Lorsque vous interrogez Git sur l'histoire de la foo , il le fera :

  1. détecter le renommage de copy entre FUSION et RESTAURATION,
  2. détecter que copy provient du parent ALTERNE de MERGED, et
  3. détecter le renommage de foo entre ORIG_HEAD et ALTERNATE.

A partir de là, il se penchera sur l'histoire de la foo .

Lorsque vous interrogez Git sur l'histoire de la bar , il le fera :

  1. ne pas noter de changement entre FUSION et RESTAURATION,
  2. détecter que bar provient du parent SAVED de MERGED, et
  3. détecter le renommage de foo entre ORIG_HEAD et SAVED.

A partir de là, il se penchera sur l'histoire de la foo .

C'est aussi simple que cela :)

Il suffit de forcer Git à accepter deux copies traçables du ou des fichiers, ce que nous faisons en déplaçant parallèlement l'original (que nous annulons ensuite).

84voto

CliffordVienna Points 1187

Contrairement à subversion, git n'a pas d'historique par fichier. Si vous regardez la structure de données du commit, elle pointe seulement vers les commits précédents et le nouvel objet arbre pour ce commit. Aucune information explicite n'est stockée dans l'objet commit sur les fichiers modifiés par le commit, ni sur la nature de ces modifications.

Les outils d'inspection des changements peuvent détecter les renommages sur la base d'une heuristique. Par exemple, "git diff" possède l'option -M qui active la détection des renommages. Ainsi, dans le cas d'un renommage, "git diff" peut vous indiquer qu'un fichier a été supprimé et qu'un autre a été créé, alors que "git diff -M" détectera effectivement le déplacement et affichera le changement en conséquence (voir "man git diff" pour plus de détails).

Dans git, il ne s'agit donc pas de la manière dont vous livrez vos modifications, mais de la manière dont vous regardez les modifications livrées plus tard.

5 votes

Mon exemple reproductible sur pastebin.com/zEREyeaL montre que git blame connaît également l'historique des renommages - sans utiliser aucune option. Cela ne signifie-t-il pas que l'historique est stocké d'une manière ou d'une autre ?

8 votes

@DanielAlder No. Like git diff -M il s'agit simplement d'une analyse intelligente des objets de l'arbre. De la git blame page de manuel : "L'origine des lignes est automatiquement suivie à travers les renommages de fichiers entiers (il n'y a actuellement pas d'option pour désactiver le suivi des renommages)."

21 votes

Pourquoi les git mv existent alors ?

39voto

Il suffit de copier le fichier, de l'ajouter et de le valider :

cp dir1/A.txt dir2/A.txt
git add dir2/A.txt
git commit -m "Duplicated file from dir1/ to dir2/"

Les commandes suivantes affichent alors l'historique complet de la pré-copie :

git log --follow dir2/A.txt

Pour voir les annotations ligne par ligne héritées du fichier original, utilisez ceci :

git blame -C -C -C dir2/A.txt

Git ne suit pas les copies au moment de la validation, mais il détecte lors de l'inspection de l'historique, par exemple. git blame y git log .

La plupart de ces informations proviennent des réponses données ici : Enregistrer une opération de copie de fichier avec Git

21voto

Lukas Eder Points 48046

J'ai légèrement modifié Réponse de Peter pour créer un script shell réutilisable et non interactif appelé git-split.sh :

#!/bin/sh

if [[ $# -ne 2 ]] ; then
  echo "Usage: git-split.sh original copy"
  exit 0
fi

git mv "$1" "$2"
git commit -n -m "Split history $1 to $2 - rename file to target-name"
REV=`git rev-parse HEAD`
git reset --hard HEAD^
git mv "$1" temp
git commit -n -m "Split history $1 to $2 - rename source-file to temp"
git merge $REV
git commit -a -n -m "Split history $1 to $2 - resolve conflict and keep both files"
git mv temp "$1"
git commit -n -m "Split history $1 to $2 - restore name of source-file"

10voto

Hervé Points 89

Pour être complet, j'ajouterais que, si vous vouliez copier un répertoire entier rempli de fichiers contrôlés ET non contrôlés, vous pourriez utiliser ce qui suit :

git mv old new
git checkout HEAD old

Les fichiers non contrôlés seront copiés, vous devez donc les nettoyer :

git clean -fdx new

3 votes

D'après ce que je vois, les premières commandes sont les suivantes pas de copie (mais les déplacer), et quel est l'intérêt de les déplacer si vous les supprimez ensuite avec la commande "clean" ?

0 votes

@hans_meine vous avez raison, autant nettoyer d'abord et déménager après.

11 votes

Seul le(s) fichier(s) original(aux) reste(nt) connecté(s) à l'historique lorsque je fais cela, la copie est considérée comme un nouveau fichier avec un nouvel historique. Cela ne répond pas à la question :(

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X