656 votes

Comment déplacer des fichiers d'un repo git à un autre (pas un clone), en préservant l'historique

Au départ, nos dépôts Git étaient des parties d'un seul dépôt SVN monstre où les projets individuels avaient chacun leur propre arbre, comme suit :

project1/branches
        /tags
        /trunk
project2/branches
        /tags
        /trunk

Évidemment, il était assez facile de déplacer des fichiers de l'un à l'autre avec svn mv . Mais dans Git, chaque projet est dans son propre dépôt, et aujourd'hui on m'a demandé de déplacer un sous-répertoire de project2 à project1 . J'ai fait quelque chose comme ça :

$ git clone project2 
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin  # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do 
>  git mv $f deeply/buried/different/java/source/directory/B
>  done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push

Mais ça semble assez alambiqué. Existe-t-il une meilleure façon de faire ce genre de choses en général ? Ou ai-je adopté la bonne approche ?

Notez que cela implique de fusionner l'historique dans un référentiel existant, plutôt que de simplement créer un nouveau référentiel autonome à partir d'une partie d'un autre référentiel ( comme dans une question précédente ).

1 votes

Cela me semble être une approche raisonnable ; je ne vois pas de moyen évident d'améliorer significativement votre méthode. C'est bien que Git rende cela facile (je ne voudrais pas essayer de déplacer un répertoire de fichiers entre différents référentiels dans Subversion, par exemple).

0 votes

Je ne voudrais pas non plus essayer de déplacer un répertoire entre deux dépôts svn différents ! (J'imagine un cauchemar impliquant svnadmin dump et svn dumpfilter, bleah).

1 votes

@ebneter - J'ai fait cela (déplacer l'historique d'un repo svn à un autre) manuellement, en utilisant des scripts shell. En gros, j'ai rejoué l'historique (diffs, messages de commit logs) de fichiers/dossiers particuliers dans un second dépôt.

442voto

Smar Points 2420

Si votre historique est sain, vous pouvez sortir les commits en tant que patch et les appliquer dans le nouveau dépôt :

cd repository
git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch 

Ou en une ligne

git log --pretty=email --patch-with-stat --reverse -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)

(Tiré de Docs d'Exherbo )

40 votes

Pour les trois ou quatre fichiers que je devais déplacer, c'était une solution beaucoup plus simple que la réponse acceptée. J'ai fini par couper les chemins dans le fichier patch avec find-replace pour qu'il s'adapte à la structure de répertoire de mon nouveau dépôt.

10 votes

J'ai ajouté des options pour que les fichiers binaires (comme les images) soient aussi correctement migrés : git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch . Fonctionne sans problèmes AFAICT.

5 votes

Voici une autre méthode similaire que j'ai utilisée : blog.neutrino.es/2012/

94voto

mcarans Points 35

Après avoir essayé plusieurs approches pour déplacer un fichier ou un dossier d'un dépôt Git à un autre, la seule qui semble fonctionner de manière fiable est décrite ci-dessous.

Cela implique de cloner le dépôt d'où vous voulez déplacer le fichier ou le dossier, de déplacer ce fichier ou ce dossier vers la racine, de réécrire l'historique de Git, de cloner le dépôt cible et de tirer le fichier ou le dossier avec l'historique directement dans ce dépôt cible.

Première étape

  1. Faites une copie du référentiel A, car les étapes suivantes apportent des changements modifications majeures à cette copie que vous ne devez pas pousser !

    git clone --branch <branch> --origin origin --progress \
      -v <git repository A url>
    # eg. git clone --branch master --origin origin --progress \
    #   -v https://username@giturl/scm/projects/myprojects.git
    # (assuming myprojects is the repository you want to copy from)
  2. cd en elle

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
  3. Supprimez le lien vers le référentiel d'origine pour éviter de faire accidentellement d'effectuer des modifications à distance (par exemple, en poussant).

    git remote rm origin
  4. Parcourez votre historique et vos fichiers, en supprimant tout ce qui n'est pas dans le répertoire 1. Le résultat est le contenu du répertoire 1 craché dans la base du référentiel A.

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
  5. Pour le déplacement d'un seul fichier : passez par ce qui reste et supprimez tout sauf le fichier souhaité. (Vous devrez peut-être supprimer les fichiers que vous ne voulez pas avec le même nom et le même commit).

    git filter-branch -f --index-filter \
    'git ls-files -s | grep $'\t'FILE_TO_KEEP$ |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
    git update-index --index-info && \
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all
    # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from FOLDER_TO_KEEP

Deuxième étape

  1. Étape de nettoyage

    git reset --hard
  2. Étape de nettoyage

    git gc --aggressive
  3. Étape de nettoyage

    git prune

Vous pouvez importer ces fichiers dans le référentiel B dans un répertoire autre que le répertoire racine :

  1. Faites ce répertoire

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
  2. Déplacer les fichiers dans ce répertoire

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
  3. Ajouter des fichiers dans ce répertoire

    git add .
  4. Validez vos changements et nous sommes prêts à fusionner ces fichiers dans le nouveau dépôt

    git commit

Troisième étape

  1. Faites une copie du référentiel B si vous n'en avez pas déjà une.

    git clone <git repository B url>
    # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git

    (en supposant que FOLDER_TO_KEEP est le nom du nouveau dépôt vers lequel vous copiez)

  2. cd en elle

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
  3. Créer une connexion distante au référentiel A en tant que branche du référentiel B

    git remote add repo-A-branch <git repository A directory>
    # (repo-A-branch can be anything - it's just an arbitrary name)
    
    # eg. git remote add repo-A-branch /c/Working/GIT/myprojects
  4. Tirez de cette branche (contenant seulement le répertoire que vous voulez déplacer) dans le référentiel B.

    git pull repo-A-branch master --allow-unrelated-histories

    L'extraction copie à la fois les fichiers et l'historique. Remarque : vous pouvez utiliser une fusion au lieu d'un tirage, mais le tirage fonctionne mieux.

  5. Enfin, vous voulez probablement faire un peu de ménage en supprimant la connexion distante au référentiel A

    git remote rm repo-A-branch
  6. Poussez et vous êtes prêt.

    git push

1 votes

J'ai suivi la plupart des étapes décrites ici, mais il semble que le système ne copie que l'historique des livraisons du fichier ou du répertoire depuis le master (et non depuis les autres branches). Est-ce bien le cas ?

0 votes

Je pense que c'est exact et que vous devez passer par des étapes similaires pour toutes les branches à partir desquelles vous voulez déplacer des fichiers ou des dossiers, c'est-à-dire passer à la branche, par exemple MyBranch dans le référentiel A, filtrer la branche, etc. Vous devriez ensuite "git pull repo-A-branch MyBranch" dans le dépôt B.

0 votes

Merci pour la réponse. Savez-vous si les balises sur les branches seront également migrées ?

63voto

Jefromi Points 127932

Yep, en frappant sur le --subdirectory-filter de filter-branch était la clé. Le fait que vous l'ayez utilisé prouve essentiellement qu'il n'y a pas de moyen plus simple - vous n'aviez pas d'autre choix que de réécrire l'histoire, puisque vous vouliez vous retrouver avec seulement un sous-ensemble (renommé) des fichiers, et cela change par définition les hachages. Puisqu'aucune des commandes standard (e.g. pull ) réécrire l'histoire, il n'y a aucun moyen de les utiliser pour accomplir cela.

Vous pourriez affiner les détails, bien sûr - certains de vos clonages et branchements n'étaient pas strictement nécessaires - mais l'approche globale est bonne ! C'est dommage que ce soit compliqué, mais bien sûr, le but de git n'est pas de rendre facile la réécriture de l'histoire.

2 votes

Que se passe-t-il si votre fichier a été déplacé dans plusieurs répertoires et qu'il se trouve maintenant dans un seul ? le filtre de sous-répertoire fonctionnera-t-il toujours ? (Je suppose que si je ne veux déplacer qu'un seul fichier, je peux le déplacer dans son propre sous-répertoire et cela fonctionnera-t-il ?)

1 votes

@rogerdpack : Non, cela ne suivra pas le fichier à travers les renommages. Je pense qu'il apparaîtra comme ayant été créé au moment où il a été déplacé dans le sous-répertoire sélectionné. Si vous voulez sélectionner un seul fichier, jetez un coup d'oeil à --index-filter dans le filter-branch page de manuel.

11 votes

Y a-t-il une recette qui me permette de suivre les renommages ?

20voto

Jörg W Mittag Points 153275

Jetez un coup d'œil à la " la fusion la plus cool du monde ". L'interface graphique gitk qui est incluse dans Git a été développée à l'origine de manière indépendante, puis fusionnée dans Git, préservant ainsi toute son histoire. Je relis régulièrement comment elle est faite, et je ne la comprends toujours pas complètement, mais c'est un excellent exemple de la puissance de Git.

1voto

Ibrahim Points 627

C'est un sujet qui m'intéresse également. Mon git-fu n'est pas aussi bon que le vôtre cependant, et filter-branch me fait toujours peur. Je l'aurais fait en générant un ensemble de patches et en les appliquant ensuite, en utilisant l'argument --directory de git apply. Mais cette méthode est beaucoup plus pénible que ce que vous avez fait.

Oh, avez-vous envisagé de regarder dans les submodules ? C'est peut-être ce que vous voulez, même si je ne les ai jamais utilisés moi-même.

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