Aller au contenu
Développement medium

Git subtree : intégrer un dépôt sans submodule

9 min de lecture

git subtree fusionne un dépôt externe dans un sous-dossier de votre projet, sans métadonnées séparées. Contrairement aux submodules, les collaborateurs n'ont rien de spécial à faire au clone, le code est directement dans le repo. Ce guide couvre l'ajout, la mise à jour, la contribution en retour et le comparatif avec les submodules.

Prérequis : Submodules et Remotes fondamentaux.

  • Ajouter un dépôt externe avec git subtree add sans fichier de configuration
  • Mettre à jour et contribuer des modifications vers le dépôt source
  • Comprendre les différences entre subtree et submodule
  • Choisir la stratégie adaptée selon votre organisation et vos équipes

Les submodules demandent à chaque collaborateur de connaître les commandes submodule. Un subtree est transparent :

  • Le code est dans le repo, pas de référence externe
  • git clone récupère tout, rien à initialiser
  • Pas de .gitmodules, pas de HEAD détaché
  • Les outils CI/CD fonctionnent sans configuration spéciale

Le compromis : l'historique est mélangé et les mises à jour sont plus verbeuses.

L'opération se fait en deux temps : déclarer le dépôt source comme remote, puis demander à Git de greffer sa branche dans un sous-dossier. Le remote n'est pas strictement obligatoire, git subtree add accepte une URL complète, mais il évite de retaper l'adresse à chaque mise à jour et rend les commandes suivantes beaucoup plus courtes.

Fenêtre de terminal
# Ajouter le remote pour simplifier les futures commandes
git remote add lib-utils https://github.com/org/lib-utils.git
git fetch lib-utils
# Intégrer dans un sous-dossier
git subtree add --prefix=libs/utils lib-utils main --squash
  • --prefix=libs/utils : dossier de destination
  • lib-utils main : remote et branche source
  • --squash : compresse l'historique en un seul commit (recommandé pour garder un historique lisible)

La commande affiche Added dir 'libs/utils' et pose deux commits dans votre historique. Attention à la lecture de git log --oneline, qui trie du plus récent au plus ancien : le commit de merge arrive en tête, tandis que le commit Squashed se retrouve plus bas car il porte la date du dépôt source, pas celle de votre intégration.

2057758 Merge commit '5e5ab16f05587744d24ee8c2fd3c187f99b45039' as 'libs/utils'
dbf1bc3 chore: init
5e5ab16 Squashed 'libs/utils/' content from commit a4afd38

Le dépôt source continue de vivre de son côté. Pour récupérer ses nouveautés, vous rejouez un subtree pull sur le même préfixe. Il n'y a pas de pointeur de version à faire avancer comme avec un submodule : Git fusionne réellement les fichiers amont dans votre arbre de travail, donc un conflit se résout ici avec les outils de merge habituels.

Fenêtre de terminal
git fetch lib-utils
git subtree pull --prefix=libs/utils lib-utils main --squash

Git fusionne les nouveautés de lib-utils/main dans votre sous-dossier. Avec --squash, un seul commit de merge apparaît dans votre historique.

Vous avez modifié du code dans libs/utils/ et voulez renvoyer les changements au dépôt d'origine :

Fenêtre de terminal
git subtree push --prefix=libs/utils lib-utils fix/correction

Git extrait les commits qui touchent libs/utils/, réécrit les chemins et les pousse vers la branche fix/correction du remote lib-utils.

Créez ensuite une pull request sur le dépôt d'origine.

split extrait un historique nettoyé sans pousser :

Fenêtre de terminal
git subtree split --prefix=libs/utils -b subtree-branch
git push lib-utils subtree-branch:fix/correction

Utile si vous voulez inspecter les commits avant de pousser.

Voici l'enchaînement sur un cycle réel : intégration, travail quotidien, synchronisation, remontée. Le point à retenir, c'est que seules les étapes 1, 3 et 4 emploient git subtree. Entre les deux, vous travaillez avec les commandes Git habituelles, sans rien de spécifique au sous-dépôt, et c'est précisément ce qui rend l'approche transparente pour le reste de l'équipe.

Fenêtre de terminal
# 1. Ajouter le remote et le subtree
git remote add lib-utils https://github.com/org/lib-utils.git
git subtree add --prefix=libs/utils lib-utils main --squash
# 2. Travailler normalement : les fichiers sont dans libs/utils/
git add libs/utils/src/helper.py
git commit -m "fix: corriger le helper dans lib-utils"
# 3. Mettre à jour depuis l'amont
git subtree pull --prefix=libs/utils lib-utils main --squash
# 4. Contribuer en retour
git subtree push --prefix=libs/utils lib-utils fix/correction

Deux lignes de ce tableau pèsent plus lourd que les autres dans la décision : Clone et Historique. La première décide de ce que vos collègues devront apprendre avant de pouvoir contribuer, la seconde de la lisibilité du dépôt parent sur le long terme. Les autres critères se contournent avec un alias ou un script, ces deux-là non.

CritèreSubmoduleSubtree
Métadonnées.gitmodules + ref dans l'indexAucune, code dans le repo
Clone--recurse-submodules nécessaireClone standard
HistoriqueSéparé (chaque repo le sien)Fusionné dans le parent
Mise à joursubmodule updatesubtree pull
ContributionPush dans le submodule directementsubtree push (extraction)
ComplexitéMoyenne-élevéeMoyenne
Taille du repoPetite (référence seulement)Plus grande (code intégré)
CI/CDConfiguration nécessaireTransparent
Detached HEADOui (après update)Non

Le subtree est gagnant quand le coût d'apprentissage de l'équipe compte plus que la propreté de l'historique. Un seul de ces critères suffit rarement à trancher, mais si vous en cochez trois sur quatre, le choix est fait.

  • Le sous-dépôt est rarement mis à jour
  • Les collaborateurs ne connaissent pas les submodules
  • L'intégration CI/CD doit être simple
  • Le code du sous-dépôt est petit

Le submodule reprend l'avantage dès que la référence de version devient un sujet en soi : audit, reproductibilité d'un build, ou plusieurs projets qui doivent consommer exactement la même révision d'une bibliothèque partagée.

  • Le sous-dépôt évolue souvent et indépendamment
  • Plusieurs projets référencent le même sous-dépôt
  • Vous voulez une version figée précise
  • Le sous-dépôt est volumineux

Presque tous les incidents de git subtree remontent au même point : la cohérence du mode --squash entre le add initial et les pull suivants. Les messages de Git ne le disent jamais explicitement, d'où la traduction ci-dessous. Lisez la colonne « Cause probable » avant de toucher à l'historique : la mauvaise réaction, ici, consiste à forcer un merge qui n'a rien à faire là.

SymptômeCause probableSolution
Conflits au subtree pullMélange --squash / pas --squashRestez cohérent : toujours --squash ou jamais
subtree push est lentHistorique volumineux à parcourirUtilisez split + push séparés
Working tree has modificationsFichiers non commitésCommitez ou stashez avant le subtree pull
refusing to merge unrelated historiesadd fait avec --squash, pull lancé sansRejouez le pull avec --squash : git subtree pull n'accepte pas --allow-unrelated-histories
  • git subtree add intègre un dépôt dans un sous-dossier, aucune métadonnée externe
  • git subtree pull met à jour, push renvoie les modifications
  • --squash compresse l'historique, gardez la cohérence
  • Subtree = transparence et simplicité, submodule = séparation et contrôle
  • Les collaborateurs n'ont rien de spécial à faire au clone

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn