Chapters ▾ 2nd Edition

7.11 Git-verktyg - Undermoduler

Undermoduler

Ofta händer det att du, medan du arbetar i ett projekt, behöver använda ett annat projekt inifrån det. Kanske är det ett bibliotek som en tredjepart utvecklade, eller som du utvecklar separat och använder i flera överordnade projekt. Ett vanligt problem uppstår i dessa scenarier: du vill kunna behandla de två projekten som separata men ändå använda det ena inifrån det andra.

Här är ett exempel. Anta att du utvecklar en webbplats och skapar Atom-flöden. I stället för att skriva din egen kod som genererar Atom bestämmer du dig för att använda ett bibliotek. Du måste då antingen inkludera koden från ett delat bibliotek, som en CPAN‑installation eller ett Ruby‑gem, eller kopiera källkoden in i ditt eget projektträd. Problemet med att inkludera biblioteket är att det är svårt att anpassa det och ofta ännu svårare att driftsätta det, eftersom du måste se till att varje klient har biblioteket tillgängligt. Problemet med att kopiera koden in i ditt eget projekt är att egna anpassningar blir svåra att sammanfoga när uppströmsändringar blir tillgängliga.

Git hanterar detta med undermoduler. Undermoduler låter dig behålla ett Git-kodförråd som en underkatalog till ett annat Git-kodförråd. Det låter dig klona ett annat kodförråd in i ditt projekt och hålla dina incheckningar separata.

Komma igång med undermoduler

Vi går igenom utvecklingen av ett enkelt projekt som har delats upp i ett huvudprojekt och några underprojekt.

Låt oss börja med att lägga till ett befintligt Git-kodförråd som en undermodul till kodförrådet vi arbetar i. För att lägga till en ny undermodul använder du kommandot git submodule add med den absoluta eller relativa URL:en till projektet du vill börja spåra. I det här exemplet lägger vi till ett bibliotek som heter “DbConnector”.

$ git submodule add https://github.com/chaconinc/DbConnector
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.

Som standard lägger undermoduler in underprojektet i en katalog med samma namn som kodförrådet, i det här fallet “DbConnector”. Du kan lägga till en annan sökväg i slutet av kommandot om du vill att det ska hamna någon annanstans.

Om du kör git status vid det här laget märker du några saker.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   .gitmodules
	new file:   DbConnector

Först bör du lägga märke till den nya filen .gitmodules. Det är en konfigurationsfil som lagrar kopplingen mellan projektets URL och den lokala underkatalog du drog in det i:

[submodule "DbConnector"]
	path = DbConnector
	url = https://github.com/chaconinc/DbConnector

Om du har flera undermoduler får du flera poster i den här filen. Det är viktigt att notera att den här filen är versionshanterad tillsammans med dina övriga filer, som din .gitignore‑fil. Den skickas upp och dras tillsammans med resten av projektet. Så vet andra som klonar projektet var undermodulsprojekten ska uppdateras ifrån.

Notera

Eftersom URL:en i .gitmodules‑filen är den som andra först försöker klona eller uppdatera från, se till att använda en URL de har åtkomst till om möjligt. Till exempel, om du använder en annan URL för att skicka än andra skulle använda för att dra från, använd den som andra kommer åt. Du kan skriva över detta värde lokalt med git config submodule.DbConnector.url PRIVAT_URL för eget bruk. När det är tillämpligt kan en relativ URL vara hjälpsam.

Den andra posten i git status‑utdata är projektkatalogen. Om du kör git diff på den ser du något intressant:

$ git diff --cached DbConnector
diff --git a/DbConnector b/DbConnector
new file mode 160000
index 0000000..c3f01dc
--- /dev/null
+++ b/DbConnector
@@ -0,0 +1 @@
+Subproject commit c3f01dc8862123d317dd46284b05b6892c7b29bc

Även om DbConnector är en underkatalog i din arbetskatalog ser Git den som en undermodul och spårar inte dess innehåll när du inte är i den katalogen. I stället ser Git den som en viss incheckning från det kodförrådet.

Om du vill ha lite trevligare diffutdata kan du skicka flaggan --submodule till git diff.

$ git diff --cached --submodule
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..71fc376
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "DbConnector"]
+	path = DbConnector
+	url = https://github.com/chaconinc/DbConnector
Submodule DbConnector 0000000...c3f01dc (new submodule)

När du checkar in ser du något i stil med detta:

$ git commit -am 'Add DbConnector module'
[master fb9093c] Add DbConnector module
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 DbConnector

Lägg märke till läget 160000 för posten DbConnector. Det är ett specialläge i Git som i praktiken betyder att du registrerar en incheckning som en katalogpost i stället för en underkatalog eller fil.

Till sist skickar du upp dessa ändringar:

$ git push origin master

Klona ett projekt med undermoduler

Här klonar vi ett projekt som innehåller en undermodul. När du klonar ett sådant projekt får du som standard katalogerna som innehåller undermodulerna, men inga av filerna i dem ännu:

$ git clone https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
$ cd MainProject
$ ls -la
total 16
drwxr-xr-x   9 schacon  staff  306 Sep 17 15:21 .
drwxr-xr-x   7 schacon  staff  238 Sep 17 15:21 ..
drwxr-xr-x  13 schacon  staff  442 Sep 17 15:21 .git
-rw-r--r--   1 schacon  staff   92 Sep 17 15:21 .gitmodules
drwxr-xr-x   2 schacon  staff   68 Sep 17 15:21 DbConnector
-rw-r--r--   1 schacon  staff  756 Sep 17 15:21 Makefile
drwxr-xr-x   3 schacon  staff  102 Sep 17 15:21 includes
drwxr-xr-x   4 schacon  staff  136 Sep 17 15:21 scripts
drwxr-xr-x   4 schacon  staff  136 Sep 17 15:21 src
$ cd DbConnector/
$ ls
$

Katalogen DbConnector finns där, men är tom. Du måste köra två kommandon från huvudprojektet: git submodule init för att initiera din lokala konfigurationsfil och git submodule update för att uppdatera all data från det projektet och växla till den incheckning som anges i ditt överprojekt:

$ git submodule init
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
$ git submodule update
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'

Nu är din undermodulkatalog DbConnector exakt i det läge den var när du checkade in tidigare.

Det finns ett annat sätt att göra detta som är lite enklare. Om du skickar --recurse-submodules till git clone kommer den automatiskt att initiera och uppdatera varje undermodul i kodförrådet, inklusive inbäddade undermoduler om någon undermodul i kodförrådet själv har undermoduler.

$ git clone --recurse-submodules https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'

Om du redan klonade projektet och glömde --recurse-submodules kan du sammanfoga stegen git submodule init och git submodule update genom att köra git submodule update --init. För att även initiera, uppdatera och växla till eventuella inbäddade undermoduler kan du använda det säkra git submodule update --init --recursive.

Arbeta i ett projekt med undermoduler

Nu har vi en kopia av ett projekt med undermoduler och kommer att samarbeta med våra lagkamrater både i huvudprojektet och i undermodulen.

Hämta uppströmsändringar från undermodulens fjärrkodförråd

Den enklaste modellen för att använda undermoduler i ett projekt är när du bara konsumerar ett underprojekt och vill få uppdateringar från det då och då, utan att själv ändra något i din utläggning. Låt oss gå igenom ett enkelt exempel.

Om du vill kontrollera om det finns nytt arbete i en undermodul kan du gå in i katalogen och köra git fetch och git merge mot uppströmsgrenen för att uppdatera den lokala koden.

$ git fetch
From https://github.com/chaconinc/DbConnector
   c3f01dc..d0354fc  master     -> origin/master
$ git merge origin/master
Updating c3f01dc..d0354fc
Fast-forward
 scripts/connect.sh | 1 +
 src/db.c           | 1 +
 2 files changed, 2 insertions(+)

Om du nu går tillbaka till huvudprojektet och kör git diff --submodule kan du se att undermodulen uppdaterades och få en lista över incheckningarna som lades till. Om du inte vill skriva --submodule varje gång du kör git diff kan du sätta det som standardformat genom att sätta konfigurationsvärdet diff.submodule till “log”.

$ git config --global diff.submodule log
$ git diff
Submodule DbConnector c3f01dc..d0354fc:
  > more efficient db routine
  > better connection routine

Om du checkar in vid det här laget kommer du att låsa undermodulen till den nya koden när andra uppdaterar.

Det finns även ett enklare sätt om du föredrar att inte uppdatera och sammanfoga manuellt i underkatalogen. Om du kör git submodule update --remote kommer Git att gå in i dina undermoduler, uppdatera och växla åt dig.

$ git submodule update --remote DbConnector
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
   3f19983..d0354fc  master     -> origin/master
Submodule path 'DbConnector': checked out 'd0354fc054692d3906c85c3af05ddce39a1c0644'

Det här kommandot antar som standard att du vill uppdatera utläggningen till standardgrenen på fjärrkodförrådet för undermodulen (den som pekas ut av HEAD i fjärrkodförrådet). Du kan dock ställa in detta till något annat om du vill. Om du till exempel vill att undermodulen DbConnector ska spåra kodförrådets “stable”‑gren kan du ställa in det antingen i .gitmodules‑filen (så att alla andra också spårar det) eller bara i din lokala .git/config‑fil. Låt oss ställa in det i .gitmodules‑filen:

$ git config -f .gitmodules submodule.DbConnector.branch stable

$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
   27cf5d3..c87d55d  stable -> origin/stable
Submodule path 'DbConnector': checked out 'c87d55d4c6d4b05ee34fbc8cb6f7bf4585ae6687'

Om du lämnar bort -f .gitmodules görs ändringen bara lokalt, men det är oftast rimligare att spåra den informationen i kodförrådet så att alla andra gör det också.

När vi kör git status vid det här laget visar Git att vi har “new commits” i undermodulen.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

  modified:   .gitmodules
  modified:   DbConnector (new commits)

no changes added to commit (use "git add" and/or "git commit -a")

Om du ställer in konfigurationen status.submodulesummary kommer Git också att visa en kort sammanfattning av ändringarna i dina undermoduler:

$ git config status.submodulesummary 1

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   .gitmodules
	modified:   DbConnector (new commits)

Submodules changed but not updated:

* DbConnector c3f01dc...c87d55d (4):
  > catch non-null terminated lines

Om du kör git diff nu ser vi både att .gitmodules har ändrats och att det finns flera incheckningar som vi har uppdaterat och är redo att checka in i vårt undermodulsprojekt.

$ git diff
diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
 [submodule "DbConnector"]
        path = DbConnector
        url = https://github.com/chaconinc/DbConnector
+       branch = stable
 Submodule DbConnector c3f01dc..c87d55d:
  > catch non-null terminated lines
  > more robust error handling
  > more efficient db routine
  > better connection routine

Det här är rätt kul eftersom vi faktiskt kan se loggen över incheckningarna som vi håller på att checka in i vår undermodul. När det är checkat in kan du se informationen i efterhand också när du kör git log -p.

$ git log -p --submodule
commit 0a24cfc121a8a3c118e0105ae4ae4c00281cf7ae
Author: Scott Chacon <schacon@gmail.com>
Date:   Wed Sep 17 16:37:02 2014 +0200

    updating DbConnector for bug fixes

diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
 [submodule "DbConnector"]
        path = DbConnector
        url = https://github.com/chaconinc/DbConnector
+       branch = stable
Submodule DbConnector c3f01dc..c87d55d:
  > catch non-null terminated lines
  > more robust error handling
  > more efficient db routine
  > better connection routine

Git försöker som standard uppdatera alla dina undermoduler när du kör git submodule update --remote. Om du har många kan du vilja skicka namnet på just den undermodul du vill uppdatera.

Dra uppströmsändringar från projektets fjärrkodförråd

Låt oss nu kliva in i din kollegas skor, som har sin egen lokala klon av MainProject‑kodförrådet. Att bara köra git pull för att få dina nyincheckade ändringar räcker inte:

$ git pull
From https://github.com/chaconinc/MainProject
   fb9093c..0a24cfc  master     -> origin/master
Fetching submodule DbConnector
From https://github.com/chaconinc/DbConnector
   c3f01dc..c87d55d  stable     -> origin/stable
Updating fb9093c..0a24cfc
Fast-forward
 .gitmodules         | 2 +-
 DbConnector         | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

$ git status
 On branch master
Your branch is up-to-date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   DbConnector (new commits)

Submodules changed but not updated:

* DbConnector c87d55d...c3f01dc (4):
  < catch non-null terminated lines
  < more robust error handling
  < more efficient db routine
  < better connection routine

no changes added to commit (use "git add" and/or "git commit -a")

Som standard uppdaterar kommandot git pull undermoduländringar rekursivt, vilket vi ser i utdata från det första kommandot ovan. Det uppdaterar däremot inte undermodulerna. Detta visas av git status, som visar att undermodulen är “modified” och har “new commits”. Dessutom pekar klamrarna som visar nya incheckningar åt vänster (<), vilket betyder att dessa incheckningar är registrerade i MainProject men saknas i den lokala utläggningen av DbConnector. För att slutföra uppdateringen måste du köra git submodule update:

$ git submodule update --init --recursive
Submodule path 'vendor/plugins/demo': checked out '48679c6302815f6c76f1fe30625d795d9e55fc56'

$ git status
 On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean

Observera att du för säkerhets skull bör köra git submodule update med flaggan --init ifall MainProject‑incheckningarna du just drog in lade till nya undermoduler, och med flaggan --recursive om några undermoduler har inbäddade undermoduler.

Om du vill automatisera processen kan du lägga till flaggan --recurse-submodules till git pull (sedan Git 2.14). Det gör att Git kör git submodule update direkt efter uppdatering, så att undermodulerna hamnar i rätt läge. Dessutom, om du vill att Git alltid ska dra med --recurse-submodules, kan du sätta konfigurationsalternativet submodule.recurse till true (detta fungerar för git pull sedan Git 2.15). Alternativet får Git att använda flaggan --recurse-submodules för alla kommandon som stöder det (utom clone).

Det finns en särskild situation som kan inträffa när du drar uppdateringar i överprojektet: det kan vara så att uppströmskodförrådet har ändrat URL:en till undermodulen i .gitmodules‑filen i någon av incheckningarna du drar in. Det kan till exempel hända om undermodulsprojektet byter värdplattform. I så fall är det möjligt att git pull --recurse-submodules, eller git submodule update, misslyckas om överprojektet refererar till en undermoduls‑incheckning som inte finns i det fjärrkodförråd som är konfigurerat lokalt i ditt kodförråd. För att rätta till detta krävs kommandot git submodule sync:

# copy the new URL to your local config
$ git submodule sync --recursive
# update the submodule from the new URL
$ git submodule update --init --recursive

Arbeta i en undermodul

Det är ganska sannolikt att om du använder undermoduler så gör du det för att du verkligen vill arbeta med koden i undermodulen samtidigt som du arbetar med koden i huvudprojektet (eller över flera undermoduler). Annars skulle du förmodligen använda ett enklare beroendehanteringssystem (som Maven eller Rubygems).

Låt oss nu gå igenom ett exempel där vi gör ändringar i undermodulen samtidigt som huvudprojektet och checkar in och publicerar dessa ändringar samtidigt.

Hittills, när vi kört git submodule update för att uppdatera ändringar från undermodulkodförråden, har Git uppdaterat ändringarna och uppdaterat filerna i underkatalogen men lämnat underkodförrådet i ett så kallat "frikopplat HEAD"‑läge. Det betyder att det inte finns någon lokal arbetsgren (som till exempel master) som spårar ändringar. Utan en arbetsgren som spårar ändringar betyder det att även om du checkar in ändringar i undermodulen kan de mycket väl gå förlorade nästa gång du kör git submodule update. Du behöver ta några extra steg om du vill att ändringar i en undermodul ska spåras.

För att göra undermodulen lättare att gå in och hacka i behöver du göra två saker. Du behöver gå in i varje undermodul och växla till en gren att arbeta på. Sedan behöver du tala om för Git vad den ska göra om du har gjort ändringar och senare kör git submodule update --remote som drar in nytt arbete från uppströms. Alternativen är att du kan sammanfoga dem med ditt lokala arbete, eller försöka ombasera ditt lokala arbete ovanpå de nya ändringarna.

Låt oss först gå in i vår undermodulkatalog och växla till en gren.

$ cd DbConnector/
$ git checkout stable
Switched to branch 'stable'

Låt oss försöka uppdatera vår undermodul med “merge”‑alternativet. För att ange det manuellt kan vi helt enkelt lägga till flaggan --merge till vårt update‑anrop. Här ser vi att det fanns en ändring på servern för undermodulen och den sammanfogas in.

$ cd ..
$ git submodule update --remote --merge
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
   c87d55d..92c7337  stable     -> origin/stable
Updating c87d55d..92c7337
Fast-forward
 src/main.c | 1 +
 1 file changed, 1 insertion(+)
Submodule path 'DbConnector': merged in '92c7337b30ef9e0893e758dac2459d07362ab5ea'

Om vi går in i katalogen DbConnector har vi de nya ändringarna redan sammanfogade i vår lokala stable‑gren. Låt oss nu se vad som händer när vi gör en egen lokal ändring i biblioteket och någon annan samtidigt skickar upp en annan ändring uppströms.

$ cd DbConnector/
$ vim src/db.c
$ git commit -am 'Unicode support'
[stable f906e16] Unicode support
 1 file changed, 1 insertion(+)

Om vi nu uppdaterar vår undermodul kan vi se vad som händer när vi har gjort en lokal ändring och uppströms också har en ändring som vi behöver ta in.

$ cd ..
$ git submodule update --remote --rebase
First, rewinding head to replay your work on top of it...
Applying: Unicode support
Submodule path 'DbConnector': rebased into '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'

Om du glömmer --rebase eller --merge kommer Git bara uppdatera undermodulen till det som finns på servern och återställa ditt projekt till ett frikopplat HEAD‑läge.

$ git submodule update --remote
Submodule path 'DbConnector': checked out '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'

Om detta händer behöver du inte oroa dig; du kan helt enkelt gå tillbaka in i katalogen och växla till din gren igen (som fortfarande innehåller ditt arbete) och sammanfoga eller ombasera origin/stable (eller vilken fjärrgren du vill) manuellt.

Om du inte har checkat in dina ändringar i undermodulen och kör en submodule update som skulle orsaka problem kommer Git att uppdatera ändringarna men inte skriva över osparat arbete i undermodulens katalog.

$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 4 (delta 0)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
   5d60ef9..c75e92a  stable     -> origin/stable
error: Your local changes to the following files would be overwritten by checkout:
	scripts/setup.sh
Please, commit your changes or stash them before you can switch branches.
Aborting
Unable to checkout 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'

Om du gjorde ändringar som kolliderar med något som ändrats uppströms kommer Git att säga till när du kör uppdateringen.

$ git submodule update --remote --merge
Auto-merging scripts/setup.sh
CONFLICT (content): Merge conflict in scripts/setup.sh
Recorded preimage for 'scripts/setup.sh'
Automatic merge failed; fix conflicts and then commit the result.
Unable to merge 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'

Du kan gå in i undermodulkatalogen och lösa konflikten precis som vanligt.

Publicera ändringar i undermoduler

Nu har vi några ändringar i undermodulkatalogen. Vissa av dessa drogs in uppströms via våra uppdateringar och andra gjordes lokalt och är inte tillgängliga för någon annan ännu eftersom vi inte har skickat upp dem.

$ git diff
Submodule DbConnector c87d55d..82d2ad3:
  > Merge from origin/stable
  > Update setup script
  > Unicode support
  > Remove unnecessary method
  > Add new option for conn pooling

Om vi checkar in i huvudprojektet och skickar upp det utan att också skicka undermoduländringarna hamnar andra som försöker växla till våra ändringar i problem, eftersom de inte har något sätt att få tagg i undermoduländringarna som de är beroende av. Dessa ändringar finns bara i vår lokala kopia.

För att vara säker på att detta inte händer kan du be Git kontrollera att alla dina undermoduler har skickats upp ordentligt innan du skickar upp huvudprojektet. Kommandot git push tar argumentet --recurse-submodules, som kan sättas till antingen “check” eller “on-demand”. Alternativet “check” gör att uppskickningen helt enkelt misslyckas om någon av de checkade in undermoduländringarna inte har skickats upp.

$ git push --recurse-submodules=check
The following submodule paths contain changes that can
not be found on any remote:
  DbConnector

Please try

	git push --recurse-submodules=on-demand

or cd to the path and use

	git push

to push them to a remote.

Som du ser ger den också råd om vad du kan göra härnäst. Det enkla alternativet är att gå in i varje undermodul och manuellt skicka till fjärrkodförråden för att se till att de är externt tillgängliga och sedan försöka skicka igen. Om du vill att beteendet “check” ska gälla för alla uppskick kan du göra detta till standard genom git config push.recurseSubmodules check.

Det andra alternativet är att använda värdet “on-demand”, som försöker göra detta åt dig.

$ git push --recurse-submodules=on-demand
Pushing submodule 'DbConnector'
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 3), reused 0 (delta 0)
To https://github.com/chaconinc/DbConnector
   c75e92a..82d2ad3  stable -> stable
Counting objects: 2, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 266 bytes | 0 bytes/s, done.
Total 2 (delta 1), reused 0 (delta 0)
To https://github.com/chaconinc/MainProject
   3d6d338..9a377d1  master -> master

Som du ser gick Git in i DbConnector‑modulen och skickade upp den innan den skickade upp huvudprojektet. Om uppskicket av undermodulen misslyckas av någon anledning kommer uppskicket av huvudprojektet också att misslyckas. Du kan göra detta till standardbeteende genom git config push.recurseSubmodules on-demand.

Sammanfoga undermoduländringar

Om du ändrar en undermodulreferens samtidigt som någon annan kan du stöta på problem. Det vill säga, om undermodulhistorikerna har glidit isär och är checkade in till divergerande grenar i ett överprojekt kan det krävas lite arbete för att rätta till.

Om en av incheckningarna är en direkt anfader till den andra (en snabbspolningssammanfogning) väljer Git helt enkelt den senare, så det fungerar fint.

Git försöker dock inte ens göra en trivial sammanslagning åt dig. Om undermodulsincheckningarna glider isär och behöver sammanfogas får du något som ser ut så här:

$ git pull
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 2 (delta 1), reused 2 (delta 1)
Unpacking objects: 100% (2/2), done.
From https://github.com/chaconinc/MainProject
   9a377d1..eb974f8  master     -> origin/master
Fetching submodule DbConnector
warning: Failed to merge submodule DbConnector (merge following commits not found)
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.

I praktiken har Git alltså listat ut att de två grenarna pekar på punkter i undermodulens historik som har glidit isär och behöver sammanfogas. Den beskriver det som “merge following commits not found”, vilket är förvirrande, men vi förklarar varför om en stund.

För att lösa problemet behöver du ta reda på vilket läge undermodulen ska hamna i. Konstigt nog ger Git dig inte så mycket information här, inte ens SHA‑1‑värdena för incheckningarna på båda sidor. Lyckligtvis är det enkelt att ta reda på. Om du kör git diff kan du få SHA‑1‑värdena för incheckningarna som är registrerade i båda grenarna du försökte sammanfoga.

$ git diff
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector

Så, i det här fallet är eb41d76 incheckningen i vår undermodul som vi hade och c771610 är incheckningen som uppströms hade. Om vi går in i undermodulkatalogen ska den redan stå på eb41d76 eftersom sammanslagningen inte rörde den. Om den av någon anledning inte gör det kan du helt enkelt skapa och växla till en gren som pekar på den.

Det viktiga är SHA‑1‑värdet för incheckningen på den andra sidan. Det är det du måste sammanfoga in och lösa. Du kan antingen försöka sammanfoga med SHA‑1:an direkt, eller skapa en gren för den och sedan försöka sammanfoga den. Vi föreslår det senare, om inte annat för att få ett trevligare sammanslagningsmeddelande.

Så vi går in i undermodulkatalogen, skapar en gren som heter “try-merge” baserad på den andra SHA‑1:an från git diff, och sammanfogar manuellt.

$ cd DbConnector

$ git rev-parse HEAD
eb41d764bccf88be77aced643c13a7fa86714135

$ git branch try-merge c771610

$ git merge try-merge
Auto-merging src/main.c
CONFLICT (content): Merge conflict in src/main.c
Recorded preimage for 'src/main.c'
Automatic merge failed; fix conflicts and then commit the result.

Vi fick en riktig sammanslagningskonflikt här, så om vi löser den och checkar in den kan vi helt enkelt uppdatera huvudprojektet med resultatet.

$ vim src/main.c (1)
$ git add src/main.c
$ git commit -am 'merged our changes'
Recorded resolution for 'src/main.c'.
[master 9fd905e] merged our changes

$ cd .. (2)
$ git diff (3)
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
@@@ -1,1 -1,1 +1,1 @@@
- Subproject commit eb41d764bccf88be77aced643c13a7fa86714135
 -Subproject commit c77161012afbbe1f58b5053316ead08f4b7e6d1d
++Subproject commit 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a
$ git add DbConnector (4)

$ git commit -m "Merge Tom's Changes" (5)
[master 10d2c60] Merge Tom's Changes
  1. Först löser vi konflikten.

  2. Sedan går vi tillbaka till huvudprojektets katalog.

  3. Vi kan kontrollera SHA‑1‑värdena igen.

  4. Lös den konfliktande undermodulposten.

  5. Checka in vår sammanslagning.

Det kan vara lite förvirrande, men det är egentligen inte så svårt.

Intressant nog finns det ett annat fall som Git hanterar. Om det finns en sammanslagningsincheckning i undermodulkatalogen som innehåller båda incheckningarna i sin historik kommer Git att föreslå den som en möjlig lösning. Den ser att någon vid något tillfälle i undermodulsprojektet sammanfogade grenar som innehåller de två incheckningarna, så kanske vill du ha den.

Det är därför felmeddelandet från tidigare var “merge following commits not found”, eftersom den inte kunde göra detta. Det är förvirrande eftersom vem skulle förvänta sig att den ens försöker göra detta?

Om den hittar en enda godtagbar sammanslagningsincheckning ser du något som detta:

$ git merge origin/master
warning: Failed to merge submodule DbConnector (not fast-forward)
Found a possible merge resolution for the submodule:
  9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a: > merged our changes
If this is correct simply add it to the index for example
by using:

  git update-index --cacheinfo 160000 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a "DbConnector"

which will accept this suggestion.
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.

Det föreslagna kommandot som Git ger uppdaterar indexet som om du hade kört git add (vilket rensar konflikten) och checkar in sedan. Du borde dock förmodligen inte göra det. Du kan lika gärna gå in i undermodulkatalogen, se vad skillnaden är, snabbspola till den incheckningen, testa det ordentligt och sedan checka in.

$ cd DbConnector/
$ git merge 9fd905e
Updating eb41d76..9fd905e
Fast-forward

$ cd ..
$ git add DbConnector
$ git commit -am 'Fast forward to a common submodule child'

Detta åstadkommer samma sak, men på det här sättet kan du åtminstone verifiera att det fungerar och att du har koden i undermodulkatalogen när du är klar.

Tips för undermoduler

Det finns några saker du kan göra för att göra arbetet med undermoduler lite enklare.

Undermodul‑foreach

Det finns ett foreach‑kommando för undermoduler som kör ett godtyckligt kommando i varje undermodul. Det kan vara riktigt användbart om du har flera undermoduler i samma projekt.

Till exempel, säg att vi vill starta en ny funktion eller åtgärda ett programfel och att vi har arbete på gång i flera undermoduler. Vi kan enkelt lägga undan allt arbete i alla våra undermoduler.

$ git submodule foreach 'git stash'
Entering 'CryptoLibrary'
No local changes to save
Entering 'DbConnector'
Saved working directory and index state WIP on stable: 82d2ad3 Merge from origin/stable
HEAD is now at 82d2ad3 Merge from origin/stable

Sedan kan vi skapa en ny gren och byta till den i alla våra undermoduler.

$ git submodule foreach 'git checkout -b featureA'
Entering 'CryptoLibrary'
Switched to a new branch 'featureA'
Entering 'DbConnector'
Switched to a new branch 'featureA'

Du fattar. En riktigt användbar sak du kan göra är att ta fram en snygg, sammanfogad diff av det som har ändrats i ditt huvudprojekt och alla underprojekt också.

$ git diff; git submodule foreach 'git diff'
Submodule DbConnector contains modified content
diff --git a/src/main.c b/src/main.c
index 210f1ae..1f0acdc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -245,6 +245,8 @@ static int handle_alias(int *argcp, const char ***argv)

      commit_pager_choice();

+     url = url_decode(url_orig);
+
      /* build alias_argv */
      alias_argv = xmalloc(sizeof(*alias_argv) * (argc + 1));
      alias_argv[0] = alias_string + 1;
Entering 'DbConnector'
diff --git a/src/db.c b/src/db.c
index 1aaefb6..5297645 100644
--- a/src/db.c
+++ b/src/db.c
@@ -93,6 +93,11 @@ char *url_decode_mem(const char *url, int len)
        return url_decode_internal(&url, len, NULL, &out, 0);
 }

+char *url_decode(const char *url)
+{
+      return url_decode_mem(url, strlen(url));
+}
+
 char *url_decode_parameter_name(const char **query)
 {
        struct strbuf out = STRBUF_INIT;

Här kan vi se att vi definierar en funktion i en undermodul och anropar den i huvudprojektet. Det här är förstås ett förenklat exempel, men förhoppningsvis ger det en bild av hur det kan vara användbart.

Nyttiga alias

Du kanske vill sätta upp alias för några av dessa kommandon eftersom de kan vara ganska långa och du kan inte ställa in konfigurationsalternativ för de flesta av dem för att göra dem till standard. Vi tog upp hur man sätter upp Git‑alias i Git-alias, men här är ett exempel på vad du kan vilja ställa in om du planerar att arbeta mycket med undermoduler i Git.

$ git config alias.sdiff '!"git diff && git submodule foreach 'git diff'"
$ git config alias.spush 'push --recurse-submodules=on-demand'
$ git config alias.supdate 'submodule update --remote --merge'

På så sätt kan du helt enkelt köra git supdate när du vill uppdatera dina undermoduler, eller git spush för att skicka med kontroll av undermodulsberoenden.

Problem med undermoduler

Att använda undermoduler är dock inte helt friktionsfritt.

Byta gren

Till exempel kan det vara knepigt att byta gren med undermoduler i Git‑versioner äldre än Git 2.13. Om du skapar en ny gren, lägger till en undermodul där och sedan byter tillbaka till en gren utan den undermodulen har du fortfarande undermodulkatalogen som en ospårad katalog:

$ git --version
git version 2.12.2

$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'

$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...

$ git commit -am 'Add crypto library'
[add-crypto 4445836] Add crypto library
 2 files changed, 4 insertions(+)
 create mode 160000 CryptoLibrary

$ git checkout master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	CryptoLibrary/

nothing added to commit but untracked files present (use "git add" to track)

Att ta bort katalogen är inte svårt, men det kan vara lite förvirrande att ha den där. Om du tar bort den och sedan byter tillbaka till grenen som har undermodulen behöver du köra submodule update --init för att fylla den igen.

$ git clean -ffdx
Removing CryptoLibrary/

$ git checkout add-crypto
Switched to branch 'add-crypto'

$ ls CryptoLibrary/

$ git submodule update --init
Submodule path 'CryptoLibrary': checked out 'b8dda6aa182ea4464f3f3264b11e0268545172af'

$ ls CryptoLibrary/
Makefile	includes	scripts		src

Återigen, inte särskilt svårt, men det kan vara lite förvirrande.

Nyare Git‑versioner (Git >= 2.13) förenklar allt detta genom att lägga till flaggan --recurse-submodules till git checkout, som ser till att undermodulerna hamnar i rätt läge för grenen vi byter till.

$ git --version
git version 2.13.3

$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'

$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...

$ git commit -am 'Add crypto library'
[add-crypto 4445836] Add crypto library
 2 files changed, 4 insertions(+)
 create mode 160000 CryptoLibrary

$ git checkout --recurse-submodules master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working tree clean

Att använda flaggan --recurse-submodules i git checkout kan också vara användbart när du arbetar på flera grenar i överprojektet, där varje gren har undermodulen pekande mot olika incheckningar. När du byter mellan grenar som registrerar undermodulen vid olika incheckningar kommer undermodulen att visas som “modified” och indikera “new commits” när du kör git status. Det beror på att undermodulens läge som standard inte följer med när du byter gren.

Det kan vara riktigt förvirrande, så det är en bra idé att alltid köra git checkout --recurse-submodules när ditt projekt har undermoduler. För äldre Git‑versioner som saknar flaggan --recurse-submodules kan du efter utcheckning köra git submodule update --init --recursive för att få undermodulerna i rätt läge.

Lyckligtvis kan du tala om för Git (>=2.14) att alltid använda flaggan --recurse-submodules genom att sätta konfigurationsalternativet submodule.recurse: git config submodule.recurse true. Som nämnts ovan gör detta också att Git går ner i undermoduler för varje kommando som har ett --recurse-submodules‑alternativ (utom git clone).

Byta från underkataloger till undermoduler

Den andra stora fallgropen som många stöter på gäller att byta från underkataloger till undermoduler. Om du har spårat filer i ditt projekt och vill flytta ut dem till en undermodul måste du vara försiktig, annars blir Git irriterad. Anta att du har filer i en underkatalog i ditt projekt och vill byta den till en undermodul. Om du tar bort underkatalogen och sedan kör submodule add skäller Git på dig:

$ rm -Rf CryptoLibrary/
$ git submodule add https://github.com/chaconinc/CryptoLibrary
'CryptoLibrary' already exists in the index

Du måste avköa katalogen CryptoLibrary först. Sedan kan du lägga till undermodulen:

$ git rm -r CryptoLibrary
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.

Anta nu att du gjorde detta i en gren. Om du försöker byta tillbaka till en gren där filerna fortfarande ligger i det faktiska trädet i stället för en undermodul får du detta fel:

$ git checkout master
error: The following untracked working tree files would be overwritten by checkout:
  CryptoLibrary/Makefile
  CryptoLibrary/includes/crypto.h
  ...
Please move or remove them before you can switch branches.
Aborting

Du kan tvinga fram bytet med checkout -f, men var försiktig så att du inte har osparade ändringar där, eftersom de kan skrivas över.

$ git checkout -f master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'

Sedan, när du byter tillbaka, får du av någon anledning en tom CryptoLibrary‑katalog och git submodule update kanske inte heller fixar det. Du kan behöva gå in i undermodulkatalogen och köra git checkout . för att få tillbaka alla filer. Du kan köra detta i ett submodule foreach‑skript för att göra det på flera undermoduler.

Det är viktigt att notera att undermoduler numera håller all sin Git‑data i överprojektets .git‑katalog, så till skillnad från mycket äldre Git‑versioner förlorar du inga incheckningar eller grenar genom att radera en undermodulkatalog.

Med dessa verktyg kan undermoduler vara en ganska enkel och effektiv metod för att utveckla flera relaterade men fortfarande separata projekt samtidigt.