Fusion de dépôts Git

Comment fusionner de multiples dépôts Git en un seul ?

What ?!

Récemment j’ai eu besoin de fusionner tout un tas de dépôts Git en un seul. Une collection d’applications et de socles métier répartis sur 6 dépôts. Bien sûr des dépendances binaires entre tout ce petit monde et toujours sur la dernière version en cours de développement. Observant que le cycle de vie des projets étaient toujours le même on a voulu simplifier nos processus (branches, gestion de dépendances, build, release, etc.) sans aller jusqu’à la pratique du mono dépôt (comme chez Google par exemple).

Mise en place

On veut bien entendu garder l’historique de tous nos projets, il n’est pas question de repartir de zéro 😅.

Il existe un projet, git-merge-repos qui permet de faire le travail en essayant de préserver les branches et les tags.

Le résultat attendu est un dépôt unique contenant tous les projets désirés :

new-repo
├── project-1
├── project-2
├── project-3
└── project-4

On peut utiliser deux stratégies pour faire ce merge :

  1. Réaliser un merge (avec n parents) et déplacer chaque projet dans son sous-dossier dans le commit de merge
  2. Réécrire l’histoire de chaque dépôt pour déplacer chaque contenu de commit dans un sous-dossier et réaliser le merge à la fin.

J’ai d’abord testé avec la première option, le problème est qu’on ne voit plus l’historique d’une ressource après le commit de merge global.

Par exemple :

> git log -- project-1

ne retourne qu’une seule entrée, le commit de merge.

La solution 2 est donc vendue !

Par contre comme on doit réécrire l’historique de chaque dépôt, il faut trouver un moment où une seule branche est en cours de développement (dans mon cas master).

Pour chaque dépôt :

    > git clone --mirror git@example.com:project-1.git
    > cd project-1
    > git filter-branch --index-filter \
  'tab=$(printf "\t") && git ls-files -s --error-unmatch . >/dev/null 2>&1; [ $? != 0 ] || (git ls-files -s | sed "s-$tab\"*-&project1/-" | GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info && mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE")' \
  --tag-name-filter cat \
  -- --all

project1 doit être remplacé par le sous-dossier dans lequel on veut transférer le projet.

J’en ai profité aussi pour renommer les tags, git-merge-repos s’occupe aussi de recréer les tags dans le dépôt final sur la base de leurs noms. Dans mon cas je ne le voulais pas vraiment, des tags sur différents dépôts portaient le même nom mais n’étaient pas forcément équivalents.

> git tag | xargs -I '{}' sh -c 'git tag project-1-{} {} && git tag -d {}'

L’opération finale est le lancement la fusion :

> git clone git@github.com:robinst/git-merge-repos.git
> cd git-merge-repos
> ./run.sh /absolute/path/to/project-1:. /absolute/path/to/project-2:.

Le résultat se trouve dans un dossier merged à la racine de git-merge-repos.

Avantages

  • L’intendance est plus facile (création de branches, merge, etc)
  • On a aujourd’hui de vrais commits atomiques pour la réalisation d’une fonctionnalité. Avant on se retrouvait tout le temps avec des commits sur différents dépôts et ce n’était pas toujours facile à tracer ou pour investiguer sur des régressions
  • La gestion des dépendances est plus simple, tout le code est dans le même dépôt
  • Maintenant qu’on est capable de builder nos projets sans dépendances binaires sur des SNAPSHOT, on a mis en place le build de nos Pull Request avec GH Pull Request Builder pour Jenkins

Dans la case inconvénient on peut noter :

  • Allongement du temps de build, même si ce n’est pas totalement vrai, mais avant avec les dépôts séparés on ne reconstruisait pas forcément tous les projets pour valider nos changements
  • On utilise Maven pour construire nos projets, notre projet multi-modules géant demande un peu plus de gymnastique intellectuelle pour lancer un build. Une fois qu’on a assimilé l’utilisation des options -pl et -am(d) c’est tout de même faisable 😇
comments powered by Disqus