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é parHEAD
,- 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, quefilter-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.
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 🙂
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à !