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.
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.
| Aspect | Git Subtree | Git Submodule |
|---|---|---|
Fichiers après clone | Déjà présents | Vides jusqu'à submodule update --init |
| Métadonnées supplémentaires | Aucune | .gitmodules + gitlinks |
| Épingle un commit amont exact | Non (l'historique est fusionné) | Oui (enregistre un SHA) |
| Contribution vers l'amont | Manuelle (git subtree split + push) | Naturelle (commit dans le sous-module) |
| Taille du dépôt | Plus grande (l'historique du sous-projet est copié) | Plus petite (seulement un pointeur) |
| Courbe d'apprentissage pour les utilisateurs | Aucune | Doivent 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
subtreeest 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 --squashLes éléments de cette commande :
--prefix .vim/bundle/example— le répertoire où vivra le sous-projet. Cette option est obligatoire pour chaque commandesubtree.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: b999b09cd9d69f359fa5668e81b09dcfde455ccaMettre à 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 --squashCela 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~5Dans l'éditeur, vous pouvez supprimer ou squasher les commits de fusion du subtree, puis sauvegarder et exécuter :
git rebase --continueComme 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
| Option | Description |
|---|---|
-q, --quiet | Supprime les messages de résultat inutiles sur stderr. |
-d, --debug | Produit 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. |
--squash | Importe 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 --squashLa 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 --squashContribuer 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-branchCela 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:masterCette 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.gitAjoutez le subtree :
git subtree add --prefix .vim/bundle/vim-double-upon vim-double-upon master --squashMettez à 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 --squashRé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.