W3docs

Git Subtree

Introduction à Git Subtree : avantages, inconvénients et différences avec les sous-modules Git.

Comme mentionné sur la page précédente, Git Submodule est utile dans des cas spécifiques. Pour suivre les dépendances logicielles à l'intérieur d'un seul dépôt, de nombreux développeurs préfèrent utiliser Git Subtree à la place.

Cette page explique ce qu'est un Git subtree, quand l'utiliser à la place d'un sous-module, et comment ajouter, mettre à jour et contribuer des modifications vers un subtree. Elle couvre également le rebase d'un dépôt contenant des subtrees ainsi que les options de commande les plus couramment utilisées.

Qu'est-ce que Git Subtree

Git Subtree est une alternative à Git Submodule. Il vous permet d'imbriquer un dépôt dans un autre sous la forme d'un sous-répertoire tout en conservant l'historique du projet intégré (le « sous-projet »). C'est l'une des façons de suivre l'historique des dépendances logicielles.

La différence essentielle avec les sous-modules est qu'un subtree est simplement un répertoire. Contrairement aux sous-modules, les subtrees ne nécessitent ni fichier .gitmodules ni gitlinks spéciaux dans votre dépôt. Les fichiers résident directement dans votre arbre de travail, sont commités avec tout le reste et voyagent automatiquement avec chaque clone, branch et merge. Toute personne qui clone votre dépôt obtient le code de la dépendance sans exécuter de commande supplémentaire.

En coulisses, git subtree est une commande de haut niveau (un wrapper) autour des commandes de bas niveau standard telles que git merge, git read-tree et git filter-branch/split. Vous n'avez pas besoin d'apprendre un nouveau format de stockage — seulement quelques nouvelles commandes.

Info

Avec les sous-modules, un clone vous donne un répertoire vide jusqu'à ce que vous exécutiez git submodule update --init. Avec les subtrees, les fichiers sont déjà présents. Cette seule différence est à l'origine de la plupart des avantages et inconvénients ci-dessous.

Subtree vs. Submodule

Les deux approches intègrent un dépôt dans un autre, mais elles font des compromis opposés. Choisissez en fonction de qui consomme le dépôt et de la fréquence à laquelle le code remonte vers l'amont.

AspectGit SubtreeGit Submodule
Fichiers après cloneDéjà présentsVides jusqu'à submodule update --init
Métadonnées supplémentairesAucune.gitmodules + gitlinks
Épingle un commit amont exactNon (l'historique est fusionné)Oui (enregistre un SHA)
Contribution vers l'amontManuelle (git subtree split + push)Naturelle (commit dans le sous-module)
Taille du dépôtPlus grande (l'historique du sous-projet est copié)Plus petite (seulement un pointeur)
Courbe d'apprentissage pour les utilisateursAucuneDoivent apprendre les commandes de sous-module

Une règle approximative : préférez les subtrees quand vous consommez principalement une dépendance et souhaitez que chaque clone fonctionne immédiatement ; préférez les submodules quand le sous-projet est activement co-développé et que vous devez épingler ou pousser vers un commit amont précis.

Pourquoi utiliser Git Subtree

Avantages

  • Pris en charge par Git 1.7.10 et versions ultérieures (la commande subtree est intégrée à Git).
  • Workflow simple pour les personnes qui clonent votre dépôt — aucune commande supplémentaire à apprendre.
  • Le code du sous-projet est disponible immédiatement après le clonage du super-projet.
  • N'ajoute pas de nouveaux fichiers de métadonnées (par exemple, .gitmodules).
  • Permet de modifier la dépendance en place sans checkout d'un dépôt séparé.

Inconvénients

  • Nécessite d'apprendre une nouvelle stratégie de fusion et quelques commandes spécifiques à subtree.
  • Contribuer du code vers l'amont est un processus en plusieurs étapes (git subtree split, puis push).
  • Le code du super-projet et du sous-projet sont mélangés dans le même dépôt, ce qui en augmente la taille et peut encombrer l'historique.

Comment ajouter un Subtree

Supposons qu'il existe un projet externe et que vous souhaitez l'ajouter à votre dépôt sous un répertoire spécifique.

Par exemple, pour ajouter une extension Vim dans un dépôt qui stocke votre configuration Vim, exécutez :

git subtree add --prefix .vim/bundle/example https://github.com/Example/vim-example.git master --squash

Les éléments de cette commande :

  • --prefix .vim/bundle/example — le répertoire où vivra le sous-projet. Cette option est obligatoire pour chaque commande subtree.
  • https://github.com/Example/vim-example.git — le dépôt source (une URL ou, ultérieurement, un remote nommé).
  • master — la branche (ou commit/tag) depuis laquelle tirer.
  • --squash — réduit l'historique complet du sous-projet en un seul commit, afin de ne pas polluer votre log. Omettez-le si vous souhaitez que l'historique complet de l'amont soit fusionné.

Avec --squash, Git enregistre le SHA-1 de master à ce moment pour référence future et produit deux commits — l'import squashé et la fusion :

commit 6d7054b3acea64e2e31f4d6fb2e3be12e5865e87
Merge: 87fa91e ef86deb
Author: Ann Smith<[email protected]m>
Date:   Tue Jun 10 13:37:03 2016 +0200
    Merge commit 'fe67ddf158faccff4082d78a25c45d8cd93e8ba8' as '.vim/bundle/example'
commit fe67ddf158faccff4082d78a25c45d8cd93e8ba8
Author: Ann Smith<[email protected]m>
Date:   Tue May 12 13:37:03 2015 +0200
    Squashed '.vim/bundle/example/' content from commit b999b09
    git-subtree-dir: .vim/bundle/example
    git-subtree-split: b999b09cd9d69f359fa5668e81b09dcfde455cca

Mettre à jour un Subtree

Pour mettre à jour le sous-dossier vers la dernière version du dépôt enfant, exécutez un subtree pull avec le même préfixe et la même source :

git subtree pull --prefix .vim/bundle/example https://github.com/Example/vim-example.git master --squash

Cela récupère la branche amont et la fusionne dans votre répertoire subtree, créant un nouveau commit de fusion. Utilisez toujours le même --prefix et le même choix --squash/sans---squash que lors de subtree add, sinon Git n'alignera pas correctement les historiques.

Notez que git subtree stocke les IDs de commit du sous-projet dans les métadonnées du message de commit, et non des références symboliques. Pour trouver le nom de branche ou de tag associé à un commit stocké, interrogez le remote :

git ls-remote https://github.com/Example/vim-example.git | grep <commit-sha>

Remplacez <commit-sha> par le hash de commit réel figurant dans la ligne git-subtree-split: de votre commit d'import.

Rebase après Git Subtree

Pour rebaser un dépôt contenant des subtrees, utilisez le mode --interactive de git rebase :

git rebase --interactive HEAD~5

Dans l'éditeur, vous pouvez supprimer ou squasher les commits de fusion du subtree, puis sauvegarder et exécuter :

git rebase --continue

Comme le rebase réécrit l'historique, les commits de fusion du subtree peuvent être supprimés ou remaniés. Après une telle réécriture, vous devrez généralement rétablir le subtree en réexécutant git subtree add ou git subtree pull. Sachez que la structure de commit modifiée peut également déclencher des conflits de fusion pendant le rebase, il est donc préférable de rebaser des branches qui n'ont pas encore été poussées et partagées.

Options courantes

OptionDescription
-q, --quietSupprime les messages de résultat inutiles sur stderr.
-d, --debugProduit des messages de débogage supplémentaires sur stderr.
-P <prefix>, --prefix=<prefix>Définit le chemin dans le dépôt vers le subtree que vous souhaitez manipuler. Obligatoire pour toutes les commandes.
-m <message>, --message=<message>Spécifie <message> comme message de commit pour le commit de fusion. Valide pour add, merge et pull.
--squashImporte le sous-projet sous la forme d'un seul commit au lieu de fusionner son historique complet.

Utiliser Git Subtree sans suivi de remote

Ajoutez le git subtree dans un dossier de préfixe spécifié. Utilisez l'option --squash pour conserver l'historique complet du sous-projet dans votre dépôt principal :

git subtree add --prefix .vim/bundle/vim-double-upon https://hostname.org/example/vim-plugins.git master --squash

La commande effectue un fetch et squashe l'historique. La sortie affiche généralement la progression du fetch suivie de la confirmation d'ajout :

git fetch https://hostname.org/example/vim-plugins.git  master
warning: no common commits
remote: Counting objects: 325, done.
remote: Compressing objects: 100% (145/145), done.
remote: Total 325 (delta 101), reused 313 (delta 89)
Receiving objects: 100% (325/325), 61.47 KiB, done.
Resolving deltas: 100% (110/110), done.
From https://hostname.org/vim-plugins.git
* branch master -> FETCH_HEAD
Added dir '.vim/bundle/vim-double-upon'

Cela crée un commit de fusion en squashant l'historique entier du sous-projet en un seul commit :

3bca0ad [4 minutes ago] (HEAD, stree) Merge commit 'fa2f5dc4f1b94356bca8a440c786a94f75dc0a45' as '.vim/bundle/vim-double-upon' [John Brown]
fa2f5dc [4 minutes ago] Squashed '.vim/bundle/vim-double-upon/' content from commit 13189ec [John Brown]

Pour mettre à jour le code du plugin depuis le dépôt amont, effectuez un git subtree pull :

git subtree pull --prefix .vim/bundle/vim-double-upon https://hostname.org/example/vim-plugins.git master --squash

Contribuer des modifications vers l'amont

Comme le code du sous-projet est mélangé dans votre dépôt, vous ne pouvez pas simplement le pousser directement. Vous devez d'abord extraire les modifications touchant le répertoire du subtree dans une branche autonome avec git subtree split :

git subtree split --prefix .vim/bundle/vim-double-upon -b split-branch

Cela réécrit uniquement les commits affectant .vim/bundle/vim-double-upon dans une nouvelle branche (split-branch) dont les chemins sont relatifs à la racine du sous-projet. Vous pouvez ensuite pousser cette branche vers le dépôt amont :

git push https://hostname.org/example/vim-plugins.git split-branch:master

Cette extraction unidirectionnelle explique pourquoi « contribuer vers l'amont » est listé comme un inconvénient ci-dessus — il n'existe pas de synchronisation bidirectionnelle intégrée.

Pour raccourcir les commandes add/pull/push du quotidien, ajoutez le sous-projet en tant que remote.

Ajouter le sous-projet en tant que remote

Enregistrer la source comme remote nommé raccourcit chaque commande ultérieure — vous référencez vim-double-upon plutôt que l'URL complète. L'option -f le récupère immédiatement :

git remote add -f vim-double-upon https://hostname.org/example/vim-plugins.git

Ajoutez le subtree :

git subtree add --prefix .vim/bundle/vim-double-upon vim-double-upon master --squash

Mettez à jour le sous-projet de cette façon :

git fetch vim-double-upon master
git subtree pull --prefix .vim/bundle/vim-double-upon vim-double-upon master --squash

Résumé

Git Subtree est une alternative à Git Submodule. Alors qu'un sous-module conserve le sous-projet comme un pointeur séparé devant être initialisé, un subtree conserve le sous-projet comme un répertoire ordinaire présent dans chaque clone. Utilisez git subtree add pour importer une dépendance, git subtree pull pour la mettre à jour, et git subtree split + push pour envoyer des modifications vers l'amont — il n'existe pas de synchronisation bidirectionnelle native. Pour en savoir plus, explorez Git Branch et Git Merge, qui alimentent les commandes subtree sous le capot.

Pratique

Pratique
Quelles sont les fonctionnalités et l'utilisation de Git subtree ?
Quelles sont les fonctionnalités et l'utilisation de Git subtree ?
Was this page helpful?