Vous entendez parler de monorepo partout, ça a l’air ésotérique, mais en vrai c’est pas si compliqué.
Dans ce billet, on va voir à quoi ça sert, pourquoi c’est souvent mystifié, ce qu’est Git subtree (et en quoi c’est différent de submodules), puis comment rapatrier proprement des dépôts existants dans un monorepo.
git subtree
et git filter-repo
suffisent dans la majorité des cas.subtree
garde une arborescence propre (un dossier par projet) et l’historique de chaque dépôt importé. Pas de dépendance comme avec les submodules.Il y a vraiment de nombreux avantages à fusionner vos projets dans un seul dépôt Git :
La simplicité de ne faire qu’une seule Pull Request à un seul endroit est vraiment un plaisir au quotidien.
Par exemple : si vous travaillez sur un projet avec un dépôt pour le front et un autre pour le back. Si jusqu’ici vous deviez faire deux Pull Requests, attendre que la première soit mergée pour accepter la seconde… désormais vous pouvez faire une seule PR pour les deux.
subtree
n’a rien de mystiqueOn confonds les deux, mais les subtree
et les submodule
n’ont pas grand chose en commun.
Si vous avez déjà essayé de faire un monorepo avec les submodules
, vous savez que c’est vraiment galère.
Utiliser des submodules
revient à ajouter une dépendance externe dans un repo interne. On se tire rarement une balle dans le pied en 2025 avec des submodules si on peut l’éviter.
Non (sauf si vous choisissez délibérement de le faire avec l’option --squash
)
git subtree add
C’est rapide et rapide et natif :
git remote add my-origin git@github.com:acme/my-repository.git
git subtree add --prefix=packages/my-repository my-origin main --squash
Ici:
--prefix
: le dossier cible dans votre monorepo.--squash
: optionnel, compresse l’historique si vous ne voulez pas tout garder (perso je préfère conserver l’historique).git filter-repo
+ merge (plus flexible)Vous clonez le dépôt source, réécrivez son historique pour le placer sous un sous-dossier, puis merge dans le monorepo.
Vous avez plus de contrôle sur le résultat final, vous pouvez renommer, nettoyer…
C’est cette 2ᵉ approche que j’automatise ci-dessous.
En général, on place les repositories importés dans un dossier packages
. Dumoins, c’est ce que j’ai toujours rencontré, j’en déduis que c’est un standard.
Pré-requis :
git-filter-repo
installé (successeur moderne de git filter-branch
)Usage :
./import-into-monorepo.sh <adresse-du-repository> [branche] <dossier-destination>
# exemple:
./import-into-monorepo.sh git@github.com:acme/my-repository.git main packages/my-repository
Et le code de import-into-monorepo.sh
#!/usr/bin/env bash
set -euo pipefail
MONOREPO_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
REPOSITORY="${1:-}"
BRANCH="${2:-main}"
FOLDER="${3:-}"
if [[ -z $REPOSITORY ]]; then
echo "Usage: $0 <repository> [branch] [folder]"
echo "Example: $0 git@github.com:acme/my-repository.git main packages/my-repository"
exit 1
fi
if [[ -z ${FOLDER} ]]; then
echo "Error: <folder> (destination in monorepo) is required"
exit 1
fi
if ! command -v git-filter-repo >/dev/null 2>&1; then
echo "Error: git-filter-repo not found. Install it: https://github.com/newren/git-filter-repo"
exit 1
fi
if [[ -d $FOLDER ]]; then
echo "Folder $FOLDER already exists in the monorepo"
exit 1
fi
# Clean temp workdir
if [[ -d /tmp/workdir ]]; then
rm -Rf /tmp/workdir
fi
# Clone source repo (single branch)
cd /tmp
git clone --branch "$BRANCH" --single-branch "$REPOSITORY" /tmp/workdir
# Rewrite history to move repo into $FOLDER
cd /tmp/workdir
git filter-repo --to-subdirectory-filter "$FOLDER"
# Merge rewritten history into monorepo
cd "$MONOREPO_DIR"
git remote -v | grep -q "repo-to-import" && git remote remove repo-to-import || true
git remote add repo-to-import /tmp/workdir
git fetch repo-to-import
# If you want to allow unrelated histories (typical case)
git merge --allow-unrelated-histories "repo-to-import/$BRANCH" -m "Import $REPOSITORY into $FOLDER"
# Optional: cleanup remote
git remote remove repo-to-import || true
echo "✅ Imported $REPOSITORY into monorepo at $FOLDER (branch: $BRANCH)."
Maintenant, vous pourriez avoir envie de garder l’ancien dépôt actif. Par exemple, si vous avez des Github actions qui tournent dessus, ou si vous souhaitez garder des PR ouvertes…
Vous pouvez automatiser ce processus avec GitHub Actions.
Créer un fichier .github/workflows/push-subtree.yml
avec le contenu suivant :
name: Push subtrees
on:
push:
branches: [ main ]
paths:
- 'packages/my-repository/**'
jobs:
push-subtree:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Push subtree to legacy repo
run: |
git remote add legacy git@github.com:acme/my-repository.git
git subtree push --prefix=packages/my-repository legacy main
Cette action va se déclencher à chaque push sur le branch main
, s’il y a eu des modificationsdans le dossier packages/my-repository
, et
va renvoyer les dernières modifications sur le dépôt d’origine.
© Jean-François Lépine, 2010 - 2025