Git Subtree
Introduzione a Git subtree: vantaggi, svantaggi e differenze rispetto ai submoduli. Scopri come aggiungere, aggiornare e contribuire a un subtree.
Come descritto nella pagina precedente, Git Submodule è utile in casi specifici. Per tracciare le dipendenze software all'interno di un singolo repository, molti sviluppatori preferiscono Git Subtree.
Questa pagina spiega cos'è un Git subtree, quando preferirlo a un submodulo, e come aggiungere, aggiornare e contribuire le modifiche a un subtree. Tratta inoltre il rebasing di un repository che contiene subtree e le opzioni dei comandi più utilizzate.
Cos'è Git Subtree
Git Subtree è un'alternativa a Git Submodule. Permette di annidare un repository all'interno di un altro come sottodirectory, conservando la cronologia del progetto incorporato ("sub"). È uno dei modi per tracciare la cronologia delle dipendenze software.
La differenza fondamentale rispetto ai submoduli è che un subtree è solo una directory. A differenza dei submoduli, i subtree non necessitano di un file .gitmodules né di gitlink speciali nel repository. I file risiedono direttamente nell'albero di lavoro, vengono committati insieme a tutto il resto e viaggiano automaticamente con ogni clone, branch e merge. Chiunque cloni il tuo repository ottiene il codice della dipendenza senza eseguire comandi aggiuntivi.
Internamente, git subtree è un comando porcelain (un wrapper) attorno a comandi plumbing standard come git merge, git read-tree e git filter-branch/split. Non è necessario imparare un nuovo formato di archiviazione — solo pochi nuovi comandi.
Con i submoduli, un clone produce una directory vuota finché non si esegue git submodule update --init. Con i subtree, i file sono già presenti. Questa singola differenza determina la maggior parte dei vantaggi e svantaggi descritti di seguito.
Subtree vs. Submodule
Entrambi gli approcci incorporano un repository all'interno di un altro, ma con compromessi opposti. La scelta dipende da chi utilizza il repository e dalla frequenza con cui il codice viene inviato upstream.
| Aspetto | Git Subtree | Git Submodule |
|---|---|---|
File dopo clone | Già presenti | Vuoti fino a submodule update --init |
| Metadati aggiuntivi | Nessuno | .gitmodules + gitlink |
| Fissa un commit upstream esatto | No (la cronologia viene unita) | Sì (registra uno SHA) |
| Contribuire upstream | Manuale (git subtree split + push) | Naturale (commit dentro il submodulo) |
| Dimensione del repository | Maggiore (la cronologia del sottoprogetto viene copiata) | Minore (solo un puntatore) |
| Curva di apprendimento per i consumer | Nessuna | Necessario imparare i comandi submodule |
Una regola empirica: preferisci i subtree quando consumi principalmente una dipendenza e vuoi che ogni clone funzioni subito; preferisci i submoduli quando il sottoprogetto è sviluppato attivamente in parallelo e hai bisogno di fissare o inviare a un commit upstream preciso.
Perché usare Git Subtree
Vantaggi
- Supportato da Git 1.7.10 e versioni successive (il comando
subtreeè incluso in Git stesso). - Flusso di lavoro semplice per chi clona il repository — nessun comando aggiuntivo da imparare.
- Il codice del sottoprogetto è disponibile immediatamente dopo aver clonato il super-progetto.
- Non aggiunge nuovi file di metadati (es.
.gitmodules). - Consente di modificare la dipendenza direttamente, senza un checkout separato del repository.
Svantaggi
- Richiede di imparare una nuova strategia di merge e alcuni comandi specifici di
subtree. - Contribuire il codice upstream è un processo in più passaggi (
git subtree split, poi push). - Il codice del super-progetto e del sottoprogetto sono mescolati nello stesso repository, il che ne aumenta le dimensioni e può rendere la cronologia più caotica.
Come aggiungere un Subtree
Supponiamo che esista un progetto esterno e si voglia aggiungerlo al proprio repository in una directory specifica.
Ad esempio, per aggiungere un'estensione Vim in un repository che contiene la configurazione Vim, eseguire:
git subtree add --prefix .vim/bundle/example https://github.com/Example/vim-example.git master --squashLe parti di questo comando:
--prefix .vim/bundle/example— la directory in cui vivrà il sottoprogetto. Questa opzione è obbligatoria per ogni comandosubtree.https://github.com/Example/vim-example.git— il repository sorgente (un URL o, successivamente, un remote con nome).master— il branch (o commit/tag) da cui prelevare.--squash— comprime l'intera cronologia del sottoprogetto in un unico commit, evitando di inquinare il log. Omettere se si desidera unire la cronologia upstream completa.
Con --squash, Git registra lo SHA-1 di master in quel momento come riferimento futuro e produce due commit — l'import compresso e il merge:
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: b999b09cd9d69f359fa5668e81b09dcfde455ccaAggiornare un Subtree
Per aggiornare la sottocartella all'ultima versione del repository figlio, eseguire un subtree pull con lo stesso prefix e sorgente:
git subtree pull --prefix .vim/bundle/example https://github.com/Example/vim-example.git master --squashQuesto recupera il branch upstream e lo unisce nella directory del subtree, creando un nuovo commit di merge. Utilizzare sempre lo stesso --prefix e la stessa scelta --squash/no---squash usata in subtree add, altrimenti Git non allineerà correttamente le cronologie.
Nota che git subtree memorizza gli ID commit del sottoprogetto nei metadati del messaggio di commit, non come riferimenti simbolici. Per trovare il nome del branch o del tag collegato a un commit memorizzato, interrogare il remote:
git ls-remote https://github.com/Example/vim-example.git | grep <commit-sha>Sostituire <commit-sha> con l'hash effettivo del commit dalla riga git-subtree-split: del commit di importazione.
Rebasing dopo Git Subtree
Per eseguire il rebase di un repository che contiene subtree, utilizzare la modalità --interactive di git rebase:
git rebase --interactive HEAD~5Nell'editor è possibile eliminare o comprimere i commit di merge del subtree, quindi salvare ed eseguire:
git rebase --continuePoiché il rebasing riscrive la cronologia, i commit di merge del subtree potrebbero essere rimossi o rimodellati. Dopo tale riscrittura è generalmente necessario ristabilire il subtree rieseguendo git subtree add o git subtree pull. Tieni presente che la struttura modificata dei commit può anche generare conflitti di merge durante il rebase, quindi è preferibile eseguire il rebase su branch non ancora pubblicati e condivisi.
Opzioni Comuni
| Opzione | Descrizione |
|---|---|
-q, --quiet | Sopprime i messaggi di risultato non necessari su stderr. |
-d, --debug | Produce messaggi di debug aggiuntivi su stderr. |
-P <prefix>, --prefix=<prefix> | Definisce il percorso nel repository verso il subtree da manipolare. Obbligatorio per tutti i comandi. |
-m <message>, --message=<message> | Specifica <message> come messaggio di commit per il commit di merge. Valido per add, merge e pull. |
--squash | Importa il sottoprogetto come un unico commit anziché unire l'intera cronologia. |
Usare Git Subtree Senza Remote Tracking
Aggiungere il git subtree in una cartella con il prefix specificato. Utilizzare il flag --squash per conservare l'intera cronologia del sottoprogetto nel repository principale:
git subtree add --prefix .vim/bundle/vim-double-upon https://hostname.org/example/vim-plugins.git master --squashIl comando esegue un fetch e comprime la cronologia. L'output mostra tipicamente il progresso del fetch seguito dalla conferma dell'aggiunta:
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'Questo crea un commit di merge comprimendo l'intera cronologia del sottoprogetto in un singolo 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]Per aggiornare il codice del plugin dal repository upstream, eseguire git subtree pull:
git subtree pull --prefix .vim/bundle/vim-double-upon https://hostname.org/example/vim-plugins.git master --squashContribuire le Modifiche Upstream
Poiché il codice del sottoprogetto è mescolato nel repository, non è possibile inviarlo direttamente upstream. Prima è necessario estrarre le modifiche che hanno coinvolto la directory del subtree in un branch autonomo con git subtree split:
git subtree split --prefix .vim/bundle/vim-double-upon -b split-branchQuesto riscrive solo i commit che riguardano .vim/bundle/vim-double-upon in un nuovo branch (split-branch) i cui percorsi sono relativi alla radice del sottoprogetto. Puoi quindi fare il push di quel branch nel repository upstream:
git push https://hostname.org/example/vim-plugins.git split-branch:masterQuesta estrazione unidirezionale è il motivo per cui "contribuire upstream" è elencato come uno svantaggio — non esiste una sincronizzazione bidirezionale integrata.
Per rendere più brevi i comandi add/pull/push quotidiani, aggiungere il sottoprogetto come remote.
Aggiungere il Sottoprogetto come Remote
Registrare la sorgente come remote con nome accorcia tutti i comandi successivi — si usa vim-double-upon invece dell'URL completo. Il flag -f esegue immediatamente il fetch:
git remote add -f vim-double-upon https://hostname.org/example/vim-plugins.gitAggiungere il subtree:
git subtree add --prefix .vim/bundle/vim-double-upon vim-double-upon master --squashAggiornare il sottoprogetto in questo modo:
git fetch vim-double-upon master
git subtree pull --prefix .vim/bundle/vim-double-upon vim-double-upon master --squashRiepilogo
Git Subtree è un'alternativa a Git Submodule. Mentre un submodulo mantiene il sottoprogetto come puntatore separato che deve essere inizializzato, un subtree mantiene il sottoprogetto come directory ordinaria presente in ogni clone. Usa git subtree add per importare una dipendenza, git subtree pull per aggiornarla, e git subtree split + push per inviare le modifiche upstream — non esiste sincronizzazione bidirezionale nativa. Per approfondire, esplora Git Branch e Git Merge, che alimentano i comandi subtree internamente.