Tech • Craftsmanship

Déplacer des fichiers entre deux repositories en gardant l'historique Git

Publié le , 1 minute

Il arrive des fois où la mise en place de micro-services ou tout refactoring sur du code legacy entraine le déplacement de fichiers d’un repository vers un autre. Et quelle tristesse si ce déplacement entraine la perte de l’historique des fichiers ! Imaginez-vous si vous avez à comprendre pourquoi tel ou tel choix a été fait dans le fichier mais vous n’avez pas son histoire Git. Pour ma part je me sentirai obligé de passer en mode archéologue : prendre du temps à décrypter, comprendre, imaginer ce que d’autres développeurs ont voulu faire.

Pour éviter ça, nous avons essayé pas mal de choses sans grand succès jusqu’à trouver une solution que nous vous présentons en dessous !

Préparer un espace de travail

cd /where/you/want/to/create/the/workspace
mkdir workspace
cd workspace

Réécrire l’histoire du repository source

Le but est d’avoir seulement l’historique Git des fichiers que nous voulons migrer.

Pour ça, nous commençons avec un clone du repository source qui contient les fichiers à migrer.

git clone --branch master --origin origin --progress -v https://github.com/Reacteev/IllustrateMoveFileSourceRepo.git
cd IllustrateMoveFileSourceRepo

Ensuite, nous enlevons la référence à origin car il ne faudrait pas accidentellement pousser l’histoire réécrite !

git remote rm origin

Et nous enlevons tous les tags, qui n’ont pas besoin d’être migrer.

git tag -l | xargs git tag -d

Enfin, nous réécrivons l’histoire.

git filter-branch --index-filter '
    git read-tree --empty
    git reset $GIT_COMMIT -- $path_to_the_files_you_want_to_migrate
  ' \
  -- --all -- $path_to_the_files_you_want_to_migrate

Quelques notes sur la commande :

  • filter-branch fixe $GIT_COMMIT avec le SHA1 de chaque commit qui sera réécrit,
  • Le --index-filter <command> permet de filtrer les fichiers au niveau de l’index (donc pas de checkout de chaque commit et est ainsi plus rapide),
  • git read-tree --empty vide l’index pour permettre le remplacement du contenu de l’index avec ce qui est référencé par HEAD,
  • Le git reset re-initialise les fichiers spécifiés dans l’état du commit spécifié par $GIT_COMMIT,
  • Le --all -- $path_to_the_files_you_want_to_migrate à la dernière ligne est transmis à git rev-list en tant qu’options, que filter-branch exécute. filter-branch réécrit donc seulement les commits qui sont spécifiés par les options.

Voici un exemple :

$ git filter-branch --index-filter '
>    git read-tree --empty
>    git reset $GIT_COMMIT -- FileToMove.md folder/FileToMove.md
>  ' \
>  -- --all -- FileToMove.md folder/FileToMove.md

WARNING: git-filter-branch has a glut of gotchas generating mangled history
         rewrites.  Hit Ctrl-C before proceeding to abort, then use an
         alternative filtering tool such as 'git filter-repo'
         (https://github.com/newren/git-filter-repo/) instead.  See the
         filter-branch manual page for more details; to squelch this warning,
         set FILTER_BRANCH_SQUELCH_WARNING=1.
Proceeding with filter-branch...

Rewrite 802c4eb0346d755c32ab3744dfdb6e5ed1d756cc (1/3) (0 seconds passed, remaining 0 predicted)    Unstaged changes after reset:
D       FileToMove.md
D       folder/FileToMove.md
Rewrite 8d53e470684bfecf4bbcb534db936add5d9ef6bb (2/3) (1 seconds passed, remaining 0 predicted)    Unstaged changes after reset:
D       FileToMove.md
D       folder/FileToMove.md
Rewrite 610f3dedddec705c1f44570e328626b1d6cb8918 (2/3) (1 seconds passed, remaining 0 predicted)    Unstaged changes after reset:
D       FileToMove.md
D       folder/FileToMove.md

Ref 'refs/heads/master' was rewritten
Ref 'refs/remotes/origin/master' was rewritten

A ce point, une nouvelle histoire a été réécrite.

Repository source réécrit
Repository source réécrit

Et juste avant la suite, retour à la racine de l’espace de travail

cd ..

Récupérer l’histoire du repository source dans le repository cible

Nous commençons par récupérer le repository cible.

git clone https://github.com/Reacteev/IllustrateMoveFileTargetRepo.git
cd IllustrateMoveFileTargetRepo

Optionnellement, vous pouvez choisir de changer de branche de départ et d’en créer une avant la migration des fichiers.

git checkout your-base-branch
git checkout -b gitMigrateFiles

Ensuite, nous ramenons l’historique recréé dans le repository cible.

git remote add repo-source ../repo-source
git fetch repo-source master

Enfin, il suffit de merger le tout avec le repository cible pour créer un commit qui contiendra alors deux arbres sources.

git merge repo-source/master --allow-unrelated-histories

Et voici les deux arbres mergés 🙂

Repository destination mergé
Repository destination mergé

Puis nous finalisons et publions.

git remote rm repo-source
git push -u origin gitMigrateFiles
cd ..

Nettoyer l’espace de travail

cd ..
rm -rf workspace

Voilà !

Contact

Reacteev

5 rue du Mail

75002 Paris, France

contact@reacteev.com