Aggiorna il sottomodulo Git all'ultimo commit sull'origine


853

Ho un progetto con un sottomodulo Git. Viene da un URL ssh: // ... ed è in commit A. Commit B è stato inviato a tale URL e voglio che il sottomodulo recuperi il commit e lo cambi.

Ora, la mia comprensione è che git submodule updatedovrebbe farlo, ma non lo fa. Non fa nulla (nessun output, codice di uscita di successo). Ecco un esempio:

$ mkdir foo
$ cd foo
$ git init .
Initialized empty Git repository in /.../foo/.git/
$ git submodule add ssh://user@host/git/mod mod
Cloning into mod...
user@host's password: hunter2
remote: Counting objects: 131, done.
remote: Compressing objects: 100% (115/115), done.
remote: Total 131 (delta 54), reused 0 (delta 0)
Receiving objects: 100% (131/131), 16.16 KiB, done.
Resolving deltas: 100% (54/54), done.
$ git commit -m "Hello world."
[master (root-commit) 565b235] Hello world.
 2 files changed, 4 insertions(+), 0 deletions(-)
 create mode 100644 .gitmodules
 create mode 160000 mod
# At this point, ssh://user@host/git/mod changes; submodule needs to change too.
$ git submodule init
Submodule 'mod' (ssh://user@host/git/mod) registered for path 'mod'
$ git submodule update
$ git submodule sync
Synchronizing submodule url for 'mod'
$ git submodule update
$ man git-submodule 
$ git submodule update --rebase
$ git submodule update
$ echo $?
0
$ git status
# On branch master
nothing to commit (working directory clean)
$ git submodule update mod
$ ...

Ho anche provato git fetch mod, che sembra fare un fetch (ma non può assolutamente, perché non è che richiede una password!), Ma git loge git shownegare l'esistenza di nuovi commit. Finora ho appena rmaggiunto il modulo e aggiunto di nuovo, ma questo è sia sbagliato in linea di principio che noioso in pratica.


5
La risposta di David Z sembra il modo migliore per farlo - ora che Git ha le funzionalità di cui hai bisogno tramite l' --remoteopzione, forse sarebbe utile contrassegnarlo come la risposta accettata piuttosto che l'approccio "a mano" nella risposta di Jason?
Mark Amery,

1
Sono pienamente d'accordo con @MarkAmery. Mentre Jason ha fornito una soluzione funzionante, non è il modo previsto per farlo, poiché lascia il puntatore di commit del sottomodulo sull'identificatore di commit errato. Il nuovo --remoteè definitivamente una soluzione migliore in questo momento, e poiché questa domanda è stata collegata a un Github Gist sui sottomoduli, penso che sarebbe meglio per i lettori in arrivo vedere la nuova risposta.
MutantOctopus

Bel tocco con la hunter2password: o)
lfarroco

Risposte:


1458

Il git submodule updatecomando in realtà dice a Git che vuoi che i tuoi sottomoduli controllino ciascuno il commit già specificato nell'indice del superprogetto. Se desideri aggiornare i tuoi sottomoduli all'ultimo commit disponibile dal loro telecomando, dovrai farlo direttamente nei sottomoduli.

Quindi in sintesi:

# Get the submodule initially
git submodule add ssh://bla submodule_dir
git submodule init

# Time passes, submodule upstream is updated
# and you now want to update

# Change to the submodule directory
cd submodule_dir

# Checkout desired branch
git checkout master

# Update
git pull

# Get back to your project root
cd ..

# Now the submodules are in the state you want, so
git commit -am "Pulled down update to submodule_dir"

Oppure, se sei una persona impegnata:

git submodule foreach git pull origin master

335
git submodule foreach git pull
Mathias Bynens,

87
@Nicklas In tal caso, utilizzare git submodule foreach git pull origin master.
Mathias Bynens,

54
A questo punto, con tutte queste correzioni alle correzioni, ho bisogno di qualcuno che scriva un post sul blog esplicativo e mi indichi. Per favore.
Suz

25
un lieve miglioramento dell'approccio "foreach" - potresti voler aggiungere - ricorsivo nel caso in cui tu abbia dei sottomoduli all'interno dei sottomoduli. così: git submodule foreach --recursive git pull origin master.
Orion Elenzil,

4
@Abdull L'opzione -aper git commit"Indica a [s] il comando di mettere in scena automaticamente i file che sono stati modificati ed eliminati, ma i nuovi file di cui non hai parlato a Git non sono interessati."
Godfrzero,

473

Git 1.8.2 presenta una nuova opzione --remote, che abiliterà esattamente questo comportamento. In esecuzione

git submodule update --remote --merge

recupererà le ultime modifiche dall'upstream in ciascun sottomodulo, le unirà e verificherà l'ultima revisione del sottomodulo. Come dice la documentazione :

--a distanza

Questa opzione è valida solo per il comando di aggiornamento. Invece di utilizzare lo SHA-1 registrato del superprogetto per aggiornare il sottomodulo, utilizzare lo stato del ramo di tracciamento remoto del sottomodulo.

Ciò equivale a funzionare git pullin ogni sottomodulo, che è generalmente esattamente quello che vuoi.


4
"equivalente a correre git pullin ogni sottomodulo" Per chiarire, non vi è alcuna differenza (dal punto di vista dell'utente) tra la tua risposta e git submodule foreach git pull?
Dennis,

3
@Dennis fa essenzialmente la stessa cosa, ma non sono sicuro che la funzionalità sia esattamente la stessa. Potrebbero esserci alcune piccole differenze che non conosco, ad esempio nel modo in cui i due comandi rispondono ad alcune impostazioni di configurazione.
David Z,

5
Vorrei poter votare questo 10.000X. Perché questo non è mostrato nella documentazione di Git da nessuna parte? Enorme supervisione.
serraosays,

4
Per me in realtà differivano in modo abbastanza significativo; foreach git pullli ha solo estratti, ma non ha aggiornato il puntatore del repository principale per indicare il commit più recente del sottomodulo. Solo con --remoteesso ha fatto riferimento all'ultimo commit.
Ela782,

5
perché l'opzione --merge? Che differenza fa?
mFeinstein,

127

Nella directory principale del progetto, eseguire:

git submodule update --init

O se si eseguono sottomoduli ricorsivi:

git submodule update --init --recursive

A volte questo non funziona ancora, perché in qualche modo sono presenti modifiche locali nella directory del sottomodulo locale durante l'aggiornamento del sottomodulo.

Il più delle volte la modifica locale potrebbe non essere quella che desideri impegnare. Può succedere a causa della cancellazione di un file nel tuo sottomodulo, ecc. In tal caso, esegui un ripristino nella directory del sottomodulo locale e nella directory padre del progetto, esegui di nuovo:

git submodule update --init --recursive

5
questa è la vera risposta. posso inviarlo al mio repository remoto in qualche modo?
MonsterMMORPG,

Funziona con nuovi sottomoduli! Potrei aggiornare tutti gli altri ma la cartella dei nuovi sottomoduli rimarrebbe vuota fino a quando non avessi eseguito questo comando.
Alexis Wilke,

1
Non introduce modifiche per i sottomoduli esistenti
Sergey G.

73

Il tuo progetto principale punta a un impegno particolare a cui dovrebbe trovarsi il sottomodulo. git submodule updateprova a verificare quel commit in ogni sottomodulo che è stato inizializzato. Il sottomodulo è in realtà un repository indipendente: basta creare un nuovo commit nel sottomodulo e spingerlo non è sufficiente. È inoltre necessario aggiungere esplicitamente la nuova versione del sottomodulo nel progetto principale.

Quindi, nel tuo caso, dovresti trovare il commit giusto nel sottomodulo - supponiamo che sia la punta di master:

cd mod
git checkout master
git pull origin master

Ora torna al progetto principale, metti in scena il sottomodulo e commetti che:

cd ..
git add mod
git commit -m "Updating the submodule 'mod' to the latest version"

Ora spingi la tua nuova versione del progetto principale:

git push origin master

Da questo punto in poi, se qualcun altro aggiorna il proprio progetto principale, allora git submodule updateper loro aggiornerà il sottomodulo, supponendo che sia stato inizializzato.


24

Sembra che in questa discussione si stiano mescolando due diversi scenari:

scenario 1

Utilizzando i puntatori del mio repository genitore ai sottomoduli, desidero controllare il commit in ciascun sottomodulo a cui punta il repository padre, possibilmente dopo aver prima iterato tutti i sottomoduli e aver aggiornato / estratto questi da remoto.

Questo, come sottolineato, è terminato

git submodule foreach git pull origin BRANCH
git submodule update

Scenario 2, che ritengo sia l'obiettivo del PO

Sono successe nuove cose in uno o più sottomoduli, e voglio 1) estrarre queste modifiche e 2) aggiornare il repository padre in modo da puntare al commit HEAD (più recente) di questo / questi sottomoduli.

Questo sarebbe fatto da

git submodule foreach git pull origin BRANCH
git add module_1_name
git add module_2_name
......
git add module_n_name
git push origin BRANCH

Non molto pratico, dal momento che dovresti hardcodificare n percorsi a tutti gli n sottomoduli in ad esempio uno script per aggiornare i puntatori di commit del repository principale.

Sarebbe bello avere un'iterazione automatica attraverso ogni sottomodulo, aggiornando il puntatore del repository padre (usando git add ) per puntare alla testa del sottomodulo / i.

Per questo, ho realizzato questo piccolo script Bash:

git-update-submodules.sh

#!/bin/bash

APP_PATH=$1
shift

if [ -z $APP_PATH ]; then
  echo "Missing 1st argument: should be path to folder of a git repo";
  exit 1;
fi

BRANCH=$1
shift

if [ -z $BRANCH ]; then
  echo "Missing 2nd argument (branch name)";
  exit 1;
fi

echo "Working in: $APP_PATH"
cd $APP_PATH

git checkout $BRANCH && git pull --ff origin $BRANCH

git submodule sync
git submodule init
git submodule update
git submodule foreach "(git checkout $BRANCH && git pull --ff origin $BRANCH && git push origin $BRANCH) || true"

for i in $(git submodule foreach --quiet 'echo $path')
do
  echo "Adding $i to root repo"
  git add "$i"
done

git commit -m "Updated $BRANCH branch of deployment repo to point to latest head of submodules"
git push origin $BRANCH

Per eseguirlo, esegui

git-update-submodules.sh /path/to/base/repo BRANCH_NAME

Elaborazione

Prima di tutto, presumo che il ramo con nome $ BRANCH (secondo argomento) esista in tutti i repository. Sentiti libero di renderlo ancora più complesso.

La prima coppia di sezioni sta verificando che gli argomenti siano presenti. Quindi estraggo le ultime cose del repository principale (preferisco usare --ff (avanzamento rapido) ogni volta che sto solo facendo pull. Ho rebase off, BTW).

git checkout $BRANCH && git pull --ff origin $BRANCH

Quindi potrebbe essere necessario inizializzare un sottomodulo, se sono stati aggiunti nuovi sottomoduli o non sono ancora inizializzati:

git submodule sync
git submodule init
git submodule update

Quindi aggiorno / estraggo tutti i sottomoduli:

git submodule foreach "(git checkout $BRANCH && git pull --ff origin $BRANCH && git push origin $BRANCH) || true"

Nota alcune cose: prima di tutto, sto concatenando alcuni comandi Git usando &&- il che significa che il comando precedente deve essere eseguito senza errori.

Dopo un possibile pull riuscito (se sul telecomando sono state trovate nuove cose), faccio una spinta per garantire che un possibile merge-commit non venga lasciato indietro sul client. Ancora una volta, succede solo se un pull ha effettivamente portato nuove cose.

Infine, il finale || truesta assicurando che lo script continui sugli errori. Per far funzionare tutto ciò, nell'iterazione deve essere racchiuso tra virgolette doppie e i comandi Git sono racchiusi tra parentesi (precedenza dell'operatore).

La mia parte preferita:

for i in $(git submodule foreach --quiet 'echo $path')
do
  echo "Adding $i to root repo"
  git add "$i"
done

Iterare tutti i sottomoduli - con --quiet, che rimuove l'output 'Entering MODULE_PATH'. Utilizzando 'echo $path'(deve essere tra virgolette singole), il percorso del sottomodulo viene scritto nell'output.

Questo elenco di percorsi relativi al sottomodulo viene acquisito in un array ( $(...)) - infine iterare questo e fare git add $iper aggiornare il repository padre.

Infine, un commit con qualche messaggio che spiega che il repository padre è stato aggiornato. Questo commit verrà ignorato per impostazione predefinita, se non è stato fatto nulla. Spingilo all'origine e il gioco è fatto.

Ho uno script che esegue questo in un lavoro Jenkins che in seguito si collega a una distribuzione automatica pianificata e funziona come un incantesimo.

Spero che questo possa essere d'aiuto a qualcuno.


2
! @ # $% SO Stiamo usando script simili ai tuoi; una nota: invece di `` git submodule foreach --quiet 'echo $ path' '`` usiamo `` git submodule foreach --recursive --quiet pwd `` all'interno dei for loop. Il pwdcomando stampa il "percorso assoluto" corretto per ciascun sottomodulo presente; --recursiveci assicura di visitare tutti i sottomoduli, compresi i sottomoduli-entro-sottomoduli -... che possono essere presenti in un grande progetto. Entrambi i metodi causano problemi con le directory che includono spazi, ad es. /c/Users/Ger/Project\ Files/...Quindi la politica è di non usare mai gli spazi bianchi nei nostri progetti.
Ger Hobbelt,

2
Questo è carino, e hai ragione che c'è un malinteso in alcune risposte su quale sia persino la domanda, ma come sottolineato dall'eccellente risposta di David Z, la tua sceneggiatura non è necessaria poiché la funzionalità è stata integrata in Git da metà 2013 quando hanno aggiunto l' --remoteopzione. git submodule update --remotesi comporta approssimativamente come fa il tuo script.
Mark Amery,

@GerHobbelt Grazie. Hai ragione, abbiamo solo 1 livello di sottomoduli, quindi non ho mai pensato di renderlo ricorsivo. Non aggiornerò lo script, prima di aver avuto la possibilità di verificare che funzioni come previsto, ma sicuramente il mio script importerebbe sub-moduli secondari. Per quanto riguarda gli spazi nelle cartelle, sembra decisamente qualcosa da evitare! : S
Frederik Struck-Schøning,

@MarkAmery Grazie per il tuo feedback. Vedo 1 problema, tuttavia: non per argomento è possibile specificare il ramo per i sottomoduli. Dal manuale di git: The remote branch used defaults to master, but the branch name may be overridden by setting the submodule.<name>.branch option in either .gitmodules or .git/config (with .git/config taking precedence).non voglio modificare .gitmodules né .git / config ogni volta che voglio farlo su un ramo diverso da master. Ma forse mi sono perso qualcosa? Inoltre, il metodo sembra imporre fusioni ricorsive (mancando così la possibilità di un avanzamento rapido).
Frederik Struck-Schøning,

Ultima cosa: ho provato il metodo di @ DavidZ, e non sembra fare esattamente la cosa, ho deciso di fare (e quale op stava chiedendo): Aggiungere il commit HEAD dei sottomoduli al genitore (cioè "aggiornare il puntatore" ). Sembra, tuttavia, fare l'unico lavoro molto bene (e più veloce) nel recuperare e unire le ultime modifiche in tutti i sottomoduli. Ahimè, per impostazione predefinita solo dal ramo principale (a meno che non modifichi il file .gitmodules (vedi sopra)).
Frederik Struck-Schøning,

19

Chiaro e semplice, per recuperare i sottomoduli:

git submodule update --init --recursive

E ora procedi ad aggiornarli all'ultimo ramo master (ad esempio):

git submodule foreach git pull origin master

12

Nota, mentre la forma moderna di aggiornamento del commit del sottomodulo sarebbe:

git submodule update --recursive --remote --merge --force

La forma più vecchia era:

git submodule foreach --quiet git pull --quiet origin

Tranne ... questa seconda forma non è davvero "silenziosa".

Vedi commit a282f5a (12 apr 2019) di Nguyễn Thái Ngọc Duy ( pclouds) .
(Unita da Junio ​​C Hamano - gitster- in commit f1c9f6c , 25 apr 2019)

submodule foreach: correzione " <command> --quiet" non rispettata

Robin l'ha riferito

git submodule foreach --quiet git pull --quiet origin

non è più veramente silenzioso.
Dovrebbe essere silenzioso prima di fc1b924 ( submodule: submodulesottocomando di porta ' foreach' dalla shell a C, 2018-05-10, Git v2.19.0-rc0) perché parseoptnon può accidentalmente mangiare opzioni allora.

" git pull" si comporta come se--quiet non fosse dato.

Ciò accade perché parseoptin submodule--helpertenterà di analizzare entrambe le --quietopzioni come se fossero opzioni di foreach, non di opzioni git-pull.
Le opzioni analizzate vengono rimosse dalla riga di comando. Quindi, quando lo tiriamo più tardi, eseguiamo proprio questo

git pull origin

Quando si chiama l'helper del sottomodulo, l'aggiunta di " --" davanti a " git pull" si interromperà parseoptper le opzioni di analisi che non appartengono realmente submodule--helper foreach.

PARSE_OPT_KEEP_UNKNOWNviene rimosso come misura di sicurezza. parseoptnon dovrebbe mai vedere opzioni sconosciute o qualcosa è andato storto. Ci sono anche un paio di aggiornamenti sulla stringa di utilizzo mentre li sto guardando.

Mentre ci sono, aggiungo anche " --" ad altri sottocomandi che passano " $@" a submodule--helper. " $@" in questi casi sono percorsi e meno probabilità di esserlo --something-like-this.
Ma il punto è ancora valido, git-submoduleha analizzato e classificato quali sono le opzioni, quali sono i percorsi.
submodule--helpernon dovrebbero mai considerare i percorsi passati git-submodulecome opzioni anche se sembrano simili.


E Git 2.23 (3 ° trimestre 2019) risolve un altro problema: " git submodule foreach" non ha protetto le opzioni della riga di comando passate al comando per essere eseguite correttamente in ciascun sottomodulo, quando il "--recursive era in uso l'opzione ".

Vedi commit 30db18b (24 giu 2019) di Morian Sonnet ( momoson) .
(Unita da Junio ​​C Hamano - gitster- in commit 968eecb , 09 lug 2019)

submodule foreach: risolve il problema della ricorsione delle opzioni

Calling:

git submodule foreach --recursive <subcommand> --<option>

porta a un errore che indica che l'opzione --<option>è sconosciuta submodule--helper.
Questo è ovviamente solo quando <option>non è un'opzione valida per git submodule foreach.

Il motivo di ciò è che la chiamata sopra è tradotta internamente in una chiamata a sottomodulo - helper:

git submodule--helper foreach --recursive \
    -- <subcommand> --<option>

Questa chiamata inizia eseguendo il sottocomando con la sua opzione all'interno del sottomodulo di primo livello e continua chiamando la successiva iterazione della submodule foreachchiamata

git --super-prefix <submodulepath> submodule--helper \
   foreach --recursive <subcommand> --<option>

all'interno del sottomodulo di primo livello. Si noti che manca il doppio trattino davanti al sottocomando.

Questo problema inizia a manifestarsi solo di recente, poiché il PARSE_OPT_KEEP_UNKNOWNflag per l'analisi dell'argomento è git submodule foreachstato rimosso nel commit a282f5a .
Quindi, l'opzione sconosciuta è ora contestata, poiché l'argomento dell'analisi non è terminato correttamente dal doppio trattino.

Questo commit risolve il problema aggiungendo il doppio trattino davanti al sottocomando durante la ricorsione.


7
git pull --recurse-submodules

Questo tirerà tutti gli ultimi commit.


4

Nel mio caso, volevo gitaggiornare all'ultimo e allo stesso tempo ripopolare tutti i file mancanti.

Di seguito sono stati ripristinati i file mancanti (grazie ai --forcequali non sembra essere stato menzionato qui), ma non è stato eseguito alcun nuovo commit:

git submodule update --init --recursive --force

Questo ha fatto:

git submodule update --recursive --remote --merge --force


3

@Jason è corretto in un certo senso, ma non del tutto.

aggiornare

Aggiorna i sottomoduli registrati, ovvero clona i sottomoduli mancanti e verifica il commit specificato nell'indice del repository contenente. Questo farà staccare i sottomoduli HEAD a meno che non venga specificato --rebase o --merge o il sottomodulo chiave. $ Name.update è impostato su rebase o merge.

Quindi, git submodule updatefa il checkout, ma è per il commit nell'indice del repository contenente. Non è ancora a conoscenza del nuovo commit a monte. Quindi vai al tuo sottomodulo, ottieni il commit che desideri e impegna lo stato del sottomodulo aggiornato nel repository principale e quindi esegui git submodule update.


1
Sembra che se sposto il sottomodulo su un commit diverso, e poi eseguo git submodule update, l'aggiornamento sposta il sottomodulo sul commit specificato nell'attuale HEAD del superprogetto. (qualunque sia il commit più recente nel superprogetto dice che dovrebbe esserci il sottoprogetto - questo comportamento, dopo la spiegazione nel post di Jason, mi sembra logico) Sembra anche recuperare, ma solo nel caso in cui il sottoprogetto stia commettendo male , che si stava aggiungendo alla mia confusione.
Thanatos,

2

Ecco un fantastico one-liner per aggiornare tutto all'ultimo sul master:

git submodule foreach 'git fetch origin --tags; git checkout master; git pull' && git pull && git submodule update --init --recursive

Grazie a Mark Jaquith


2

Se non conosci il ramo host, procedi come segue:

git submodule foreach git pull origin $(git rev-parse --abbrev-ref HEAD)

Otterrà un ramo del repository Git principale e quindi per ogni sottomodulo farà un pull dello stesso ramo.


0

Se stai cercando di verificare il masterramo per ciascun sottomodulo, puoi utilizzare il seguente comando a tale scopo:

git submodule foreach git checkout master
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.