Guida

Git submodule: come aggiungere, aggiornare e rimuovere i sottomoduli

Aggiornato: · Skillaria.Top

Un Git submodule è un altro repository annidato nel vostro e fissato su un commit preciso. Si aggiunge con git submodule add <url> <percorso>, si ottiene al momento del clone con git clone --recurse-submodules, si aggiorna con git submodule update --remote e si rimuove con git submodule deinit e git rm.

Git submodule (sottomodulo) è un repository Git separato annidato in una cartella del vostro progetto. La sua caratteristica principale: il repository padre non conserva i file del sottomodulo, ma un riferimento a un commit preciso al suo interno. Per questo chiunque cloni il progetto avrà il sottomodulo nella stessa identica versione, finché non siete voi a cambiarla esplicitamente.

I sottomoduli si usano quando bisogna includere una libreria di terzi senza package manager, condividere codice comune tra più progetti, spostare un tema grafico o un firmware in un repository separato con una storia propria.

Come sono fatti i git submodules

Un sottomodulo è composto da tre parti, e capire come sono collegate elimina gran parte dei problemi:

  • Il file .gitmodules nella radice del progetto: viene committato e descrive quale percorso viene preso da quale URL.
  • La voce gitlink nell'albero del commit: un elemento speciale con modalità 160000 che, al posto del contenuto, contiene l'hash del commit del sottomodulo.
  • Il repository del sottomodulo stesso: i suoi dati sono conservati in .git/modules/<nome> del padre, mentre nella cartella del sottomodulo c'è un file .git che vi rimanda.
cat .gitmodules
[submodule "libs/ui-kit"]
	path = libs/ui-kit
	url = https://github.com/example/ui-kit.git

git ls-tree HEAD libs/
160000 commit 8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c	libs/ui-kit

Notate bene: il padre registra proprio un commit, non un branch. È questa la differenza fondamentale tra un submodule git e una dipendenza del tipo «ultima versione di main».

Come aggiungere un sottomodulo

git submodule add https://github.com/example/ui-kit.git libs/ui-kit
Cloning into '/home/dev/shop/libs/ui-kit'...
done.

git status
	new file:   .gitmodules
	new file:   libs/ui-kit

git commit -m "Aggiungi ui-kit come sottomodulo"

Il comando clona il repository, crea o integra .gitmodules e aggiunge il gitlink all'index. Resta da fare il commit. Se non indicate il percorso, la cartella prenderà il nome del repository.

Perché il sottomodulo segua un branch specifico durante gli aggiornamenti, indicatelo subito: git submodule add -b develop <url> <percorso>. Il branch verrà scritto in .gitmodules come branch = develop.

URL relativo

Se il sottomodulo si trova sullo stesso server, potete indicare un percorso relativo: git submodule add ../ui-kit.git libs/ui-kit. Viene risolto rispetto all'URL del repository remoto del padre, quindi funziona sia via SSH sia via HTTPS, a seconda di come è stato clonato il progetto.

Come clonare un progetto con i sottomoduli

La lamentela più frequente: «ho clonato, ma la cartella del sottomodulo è vuota». Per impostazione predefinita git clone non scarica i sottomoduli. Il modo corretto è questo:

git clone --recurse-submodules https://github.com/example/shop.git

Se il progetto è già stato clonato senza sottomoduli:

git submodule update --init --recursive

--init registra i sottomoduli di .gitmodules nel .git/config locale, update li scarica e li porta sul commit fissato, --recursive fa lo stesso per i sottomoduli dentro i sottomoduli.

Come aggiornare un sottomodulo

Qui è importante distinguere due compiti: «ottenere la versione committata dai colleghi» e «passare a una nuova versione della libreria».

Ottenere la versione fissata nel progetto

Dopo un git pull nel padre il gitlink può essere cambiato, ma i file del sottomodulo non si aggiornano da soli: git status mostrerà modified: libs/ui-kit (new commits). Sincronizzate:

git pull
git submodule update --init --recursive

Per non farlo a mano, attivate l'automatismo: git config submodule.recurse true. Così pull, checkout e switch aggiorneranno i sottomoduli da soli. L'alternativa per una singola chiamata è git pull --recurse-submodules.

Passare a una nuova versione della libreria

# Opzione 1: prendere l'ultimo commit del branch seguito
git submodule update --remote libs/ui-kit

# Opzione 2: scegliere la versione a mano
cd libs/ui-kit
git fetch
git checkout v3.2.0
cd ../..

# In entrambi i casi: registrare la nuova versione nel padre
git add libs/ui-kit
git commit -m "Aggiorna ui-kit alla v3.2.0"

--remote prende il branch dal parametro branch in .gitmodules e, se manca, il branch a cui punta l'HEAD del repository remoto del sottomodulo (di solito main). Senza l'ultimo commit nel padre i colleghi non riceveranno la nuova versione: aggiornare un sottomodulo significa sempre modificare il gitlink, e questa modifica va committata.

Per vedere cosa è cambiato nei sottomoduli è comodo fare così:

git diff --submodule=log
git config diff.submodule log    # rendere questo output il formato predefinito
git submodule status
 8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c libs/ui-kit (v3.1.0)
+1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b libs/charts (v1.4.0-3-g1a2b3c4)

Il prefisso in git submodule status: - indica un sottomodulo non inizializzato, + che il suo commit attuale differisce da quello fissato nel padre, U un conflitto di merge.

Come modificare il codice di un sottomodulo

Dopo git submodule update il sottomodulo è in stato detached HEAD: su un commit preciso, non su un branch. Se iniziate a committare così, rischiate di perdere i commit al prossimo update. L'ordine di lavoro:

cd libs/ui-kit
git switch main                  # posizionarsi su un branch
# ... modifiche ...
git commit -am "Correggi i margini del pulsante"
git push                         # prima il push del sottomodulo!
cd ../..
git add libs/ui-kit
git commit -m "ui-kit: corretti i margini del pulsante"
git push

L'ordine dei push è fondamentale. Se inviate il padre prima del sottomodulo, sul server il progetto farà riferimento a un commit che nessuno tranne voi può scaricare, e ai colleghi git submodule update fallirà con l'errore fatal: remote error: upload-pack: not our ref o did not contain. Per proteggervi c'è git push --recurse-submodules=check (rifiuta il push se il sottomodulo non è stato inviato) oppure =on-demand (invia per primi i sottomoduli da solo).

Come rimuovere un sottomodulo

Un tempo la rimozione richiedeva di modificare a mano diversi file. Nelle versioni moderne di Git bastano tre passaggi:

git submodule deinit -f libs/ui-kit   # togliere da .git/config e svuotare la cartella
git rm -f libs/ui-kit                 # togliere il gitlink e la voce da .gitmodules
rm -rf .git/modules/libs/ui-kit       # eliminare i dati salvati del sottomodulo
git commit -m "Rimuovi il sottomodulo ui-kit"

L'ultimo passaggio non è indispensabile per la correttezza, ma senza di esso, se riaggiungete un sottomodulo nello stesso percorso, Git potrebbe lamentarsi che la cartella locale esiste già (A git directory for 'libs/ui-kit' is found locally).

Se è cambiato l'URL del sottomodulo

Il repository si è spostato: correggete .gitmodules e sincronizzate la configurazione locale:

git submodule set-url libs/ui-kit https://gitlab.com/example/ui-kit.git
git submodule sync --recursive
git commit -am "Nuovo indirizzo di ui-kit"

Anche i colleghi, dopo il pull, devono eseguire git submodule sync: l'URL di .gitmodules viene copiato nel loro .git/config solo con init o sync.

Comandi utili con più sottomoduli

CompitoComando
Elenco e statogit submodule status --recursive
Eseguire un comando in ciascunogit submodule foreach 'git switch main && git pull'
Velocizzare il downloadgit submodule update --init --jobs 4
Scaricare senza storiagit submodule update --init --depth 1
Riepilogo sintetico in statusgit config status.submoduleSummary true

Problemi tipici con i sottomoduli

  • Cartella vuota dopo il clone: non è stato eseguito l'init. Soluzione: git submodule update --init --recursive.
  • «modified: (new commits)» subito dopo il pull: il padre punta a un altro commit, ma i file sono vecchi. Soluzione: git submodule update.
  • Sottomodulo riportato indietro per errore: avete eseguito git add . quando il sottomodulo era su un commit vecchio e avete committato il ritorno alla versione precedente. Controllate sempre git diff --submodule prima del commit.
  • Conflitto sul gitlink durante un merge: in due branch il sottomodulo è stato aggiornato a commit diversi. Entrate nel sottomodulo, scegliete il commit giusto (spesso il più recente, che include entrambe le modifiche), poi git add percorso e completate il merge.
  • Sottomodulo privato in CI: la build non riesce a scaricare il sottomodulo senza accesso. Bisogna fornire alla CI una chiave o un token per il repository del sottomodulo; gli URL relativi semplificano la configurazione.

Sottomoduli nel team: le regole condivise

La maggior parte dei problemi con i sottomoduli non nasce da Git, ma dal fatto che i membri del team agiscono in modo diverso. Alcune regole che conviene fissare nel README del progetto:

  1. Clonare solo con --recurse-submodules e attivare submodule.recurse true per tutti.
  2. Aggiornare il sottomodulo con un commit separato e un messaggio chiaro: quale versione e perché.
  3. Non modificare il codice del sottomodulo nel progetto padre senza un commit e un push separati nel sottomodulo stesso.
  4. In CI eseguire esplicitamente git submodule update --init --recursive e verificare che la build non dipenda da modifiche locali.

Per verificare in CI che tutti i commit fissati dei sottomoduli siano davvero disponibili basta un clone pulito: se qualcuno ha dimenticato di inviare il sottomodulo, la build fallirà al passo di update, e non a un collega una settimana dopo.

git clone --recurse-submodules --jobs 4 "$REPO_URL" build
cd build
git submodule status --recursive

Submodule o alternative

I sottomoduli offrono un controllo preciso della versione e storie separate, ma aggiungono passaggi manuali per ogni membro del team. Se il codice è una normale libreria, è più semplice un package manager (npm, pip, Composer, Go modules). Se conta che il codice esterno stia semplicemente dentro il progetto e si cloni senza comandi aggiuntivi, valutate git subtree: copia la storia nel repository principale e i colleghi non devono sapere nulla. Per molti servizi collegati i team scelgono spesso un monorepo. Il sottomodulo è giustificato quando il codice annidato ha una vita propria, rilasci propri e un team proprio.

In sintesi

Un Git submodule è un riferimento a un commit preciso di un altro repository. Si aggiunge con git submodule add, si ottiene con --recurse-submodules o update --init, si aggiorna a una nuova versione con update --remote più un commit nel padre, si rimuove con deinit, git rm e la pulizia di .git/modules. Ricordate il detached HEAD dentro il sottomodulo e l'ordine dei push. Gli strumenti avanzati di Git si affrontano nella pratica nel corso su Git.

Domande frequenti

Perché il sottomodulo è sempre in detached HEAD?

Il padre conserva l'hash di un commit, non un branch, e git submodule update porta il sottomodulo proprio su quel commit. Per modificarlo passate a un branch a mano o usate git submodule update --remote --merge.

Si può fissare un sottomodulo su un tag?

Non direttamente, ma potete passare al tag nel sottomodulo con git checkout e committare nel padre il commit risultante; in git submodule status il tag sarà visibile tra parentesi.

GitHub include i sottomoduli nell'archivio ZIP?

No, gli archivi dei sorgenti di GitHub non includono il contenuto dei sottomoduli, le cartelle saranno vuote. Usate git clone --recurse-submodules.

Che differenza c'è tra git submodule e git subtree?

Submodule conserva un riferimento a un commit del repository esterno e richiede comandi separati per ottenere il codice, mentre subtree copia file e storia direttamente nel repository principale.

Si può spostare un sottomodulo in un'altra cartella?

Sì, con il comando git mv old/path new/path: Git aggiornerà da solo .gitmodules e i riferimenti interni, resta solo da fare il commit.

Impara l’argomento in video

Questo argomento è trattato in modo approfondito, con pratica, nel corso «Git da zero a sviluppatore sicuro».

Vai al corso