Git 🌙
Chapters ▾ 2nd Edition

7.11 Orodja Git - Podmoduli

Podmoduli

Pogostokrat se zgodi, da med delom na enem projektu potrebujete uporabo drugega projekta znotraj tega. Morda gre za knjižnico, ki jo je razvil tretji ponudnik, ali pa jo razvijate ločeno in jo uporabljate v več nadrejenih projektih. V teh primerih se pogosto pojavijo težave: želite, da se lahko dva projekta obravnavata ločeno, hkrati pa lahko uporabite enega znotraj drugega.

Tukaj je primer. Predpostavimo, da razvijate spletno stran in ustvarjate vire Atom. Namesto da bi pisali svojo lastno kodo za generiranje Atomov, se odločite za uporabo knjižnice. Verjetno boste morali vključiti to kodo iz skupne knjižnice, kot je namestitev CPAN ali Ruby gem, ali pa kopirati izvorno kodo v svoje projektno drevo. Težava pri vključevanju knjižnice je, da jo je težko prilagoditi na kakršen koli način in pogosto jo je težje implementirati, saj morate poskrbeti, da je knjižnica na voljo vsakemu odjemalcu. Težava pri kopiranju kode v lasten projekt pa je, da so kakršne koli prilagojene spremembe, ki jih naredite, težke za združevanje, ko postanejo na voljo spremembe izvornega projekta.

Git naslavlja to težavo s pomočjo podmodulov. Podmoduli vam omogočajo, da ohranite repozitorij Git kot podmapo drugega repozitorija Git. To vam omogoča, da klonirate drug repozitorij v svoj projekt in ohranite svoje potrditve ločene.

Začetek s podmoduli

Pojasnili bomo, kako razviti preprost projekt, ki je razdeljen na glavni projekt in nekaj podprojektov.

Začnimo tako, da dodamo obstoječi repozitorij Git kot podmodul repozitorija, na katerem delamo. Za dodajanje novega podmodula uporabite ukaz git submodule add z absolutnim ali relativnim URL-jem projekta, ki ga želite začeti slediti. V tem primeru bomo dodali knjižnico, imenovano »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.

Privzeto bodo podmoduli dodani v mapo imenovano enako kot repozitorij, v tem primeru »DbConnector«. Če želite, da gre kam drugam, lahko na koncu ukaza določite drugačno pot.

Če zdaj zaženete ukaz git status, boste opazili nekaj stvari.

$ 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

Najprej bi morali opaziti novo datoteko .gitmodules. To je konfiguracijska datoteka, ki hrani preslikavo med URL-jem projekta in lokalno podmapo, v katero ste ga povlekli:

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

Če imate več podmodulov, boste imeli več vnosov v tej datoteki. Pomembno je opozoriti, da je ta datoteka nadzorovana z različicami skupaj z drugimi datotekami, kot je vaša .gitignore datoteka. Ta se potiska in vleče skupaj s preostalim projektom. To je način, kako drugi ljudje, ki klonirajo ta projekt, vedo, kje dobiti projekte podmodulov.

Opomba

Ker je URL v datoteki .gitmodules tisto, kar bodo drugi najprej poskusili klonirati/pridobiti, poskrbite, da boste uporabili URL, ki je drugim dostopen, če je to mogoče. Na primer, če uporabljate drugačen URL za potiskanje, kot ga drugi uporabljajo za pridobivanje, uporabite tistega, do katerega imajo drugi dostop. To vrednost lahko lokalno prepišete z git config submodule.DbConnector.url PRIVATE_URL za lastno uporabo. V določenih primerih je lahko koristen relativen URL.

Drugi vnos v izpisu git status je vnos mape projekta. Če na tem zaženete git diff, boste videli nekaj zanimivega:

$ 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

Čeprav je DbConnector podmapa v vašem delovnem imeniku, jo Git vidi kot podmodul in ne spremlja njenih vsebin, ko se ne nahajate v tej mapi. Namesto tega Git vidi to kot določeno potrditev iz tega repozitorija.

Če želite malo lepši izpis diff, lahko ukazu git diff predložite možnost --submodule.

$ 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)

Ko potrdite, boste videli nekaj takega:

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

Opazite način 160000 za vnos DbConnector. To je poseben način v Gitu, ki v bistvu pomeni, da zapisujete potrditev kot vnos v mapi namesto podmape ali datoteke.

Na koncu potisnite te spremembe:

$ git push origin master

Kloniranje projekta s podmoduli

Tu bomo klonirali projekt, ki vsebuje podmodul. Ko klonirate tak projekt, boste privzeto dobili direktorije, ki vsebujejo podmodule, vendar znotraj njih ni še nobene datoteke:

$ 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
$

Mapa DbConnector obstaja, vendar je prazna. Zagnati morate dve ukaza: git submodule init, da inicializirate lokalno konfiguracijsko datoteko, in git submodule update, da pridobite vse podatke iz tega projekta in izvlečete ustrezno potrditev v nadrejenem projektu:

$ 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'

Sedaj je vaša podmapa DbConnector v natančno enakem stanju kot ob vašem prejšnjem potrjevanju.

Obstaja še drug način, ki pa je nekoliko preprostejši. Če ukazu git clone dodate --recurse-submodules, se bodo vsi podmoduli v repozitoriju samodejno inicializirali in posodobili, vključno z vgrajenimi podmoduli, če ima kateri od podmodulov v repozitoriju svoje podmodule.

$ 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'

Če ste že klonirali projekt in pozabili na --recurse-submodules, lahko koraka git submodule init in git submodule update združite tako, da zaženete git submodule update --init. Če želite inicializirati, pridobiti in izvleči tudi vse vgrajene podmodule, lahko uporabite enostaven ukaz git submodule update --init --recursive.

Delo na projektu s podmoduli

Zdaj imamo kopijo projekta z vgrajenimi podmoduli in sodelovali bomo s svojimi sodelavci na glavnem projektu in podprojektu.

Povlek zgornjih sprememb iz daljave podmodula

Najenostavnejši model uporabe podmodulov v projektu bi bil, če bi enostavno uporabljali podprojekt in občasno želeli dobiti njegove posodobitve, vendar ga dejansko ne bi nikakor spreminjali pri vašem izvleku. Pojdimo tu skozi enostaven primer.

Če želite preveriti novo delo v podmodulu, lahko greste v direktorij in poženete git fetch in git merge, da posodobite vejo povratnega toka in posodobite svojo lokalno kodo.

$ 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(+)

Če greste sedaj nazaj v svoj glavni projekt in poženete git diff --submodule, lahko vidite, da je bil podmodul posodobljen in dobite seznam vseh potrditev, ki so mu bile dodane. Če ne želite vsakič vpisovati --submodule, ko poženete git diff, lahko možnost nastavite kot privzeti format z nastavitvijo konfiguracijske vrednosti diff.submodule na »log«.

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

Če v tem trenutku potrdite, boste podmodul zaklenili, da ima novo kodo, ko bodo drugi posodabljali.

Da to naredite, obstaja enostavnejša pot, če imate raje ročno prenašanje in združevanje v poddirektoriju. Če poženete git submodule update --remote, bo šel Git v vaše podmodule in vam jih prenesel ter posodobil.

$ 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'

Ta ukaz bo privzeto predvideval, da želite posodobiti izvlek na privzeto vejo oddaljenega podmodula (tistega, na katerega kaže HEAD na oddaljenem mestu). Vendar če želite, lahko to nastavite na nekaj drugega. Na primer, če želite, da podmodul DbConnector sledi veji »stable« tega repozitorija, lahko to nastavite v datoteki .gitmodules (tako, da jo spremljajo tudi drugi), ali pa v lokalni datoteki .git/config. Nastavimo jo v datoteki .gitmodules:

$ 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'

Če izpustite -f .gitmodules, bo naredil samo spremembo za vas, vendar je morda bolj smotrno, da sledite tej informaciji z repozitorijem, da lahko to naredi tudi vsakdo drug.

Ko v tem trenutku poženemo git status, nam bo Git pokazal, da imamo v podmodulu »nove potrditve«.

$ 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")

Če nastavite konfiguracijsko nastavitev status.submodulesummary, vam bo Git tudi pokazal kratek povzetek sprememb na vaših podmodulih:

$ 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

Če v tem trenutku poženete git diff, lahko vidimo, da imamo spremenjeno tako našo datoteko .gitmodules kot tudi, da je veliko potrditev, ki smo jih povlekli, in so pripravljene za potrditev v našem projektu podmodula.

$ 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

To je kar odlično, saj lahko dejansko vidimo dnevnik potrditev, ki jih bomo ravno potrdili v našem podmodulu. Ko je enkrat potrjeno, lahko vidimo te informacije tudi po tem, kot tudi, ko poženete 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

Ko poženete git submodule update --remote, bo Git privzeto poskusil posodobiti vse vaše podmodule. Če jih imate veliko, boste morda želeli podati tudi ime samo tistega podmodula, ki ga želite posodobiti.

Povlek zgornjih sprememb iz daljave projekta

Postavimo se sedaj v vašega sodelavca, ki ima svoj lokalni klon repozitorija MainProject. Enostaven pogon git pull, da dobite svoje nove potrjene spremembe, ni dovolj:

$ 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")

Privzeto ukaz git pull rekurzivno pridobi spremembe v podmodulih, kar lahko vidimo v izpisu prvega zgoraj navedenega ukaza. Vendar ne posodobi podmodulov. To kaže izpis ukaza git status, ki prikazuje, da je podmodul »spremenjen« in ima »nove potrditve«. Poleg tega oklepaji, ki kažejo nove potrditve, kažejo levo (<), kar kaže, da so te potrditve zabeležene v glavnem projektu, vendar niso prisotne v lokalnem izvleku DbConnector. Da dokončate posodobitev, morate zagnati 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

Bodite pozorni, da ste na varni strani, saj bi morali zagnati ukaz git submodule update z zastavico --init, če ste pravkar pridobili spremembe v glavnem projektu, ki so dodale nove podmodule, in z zastavico --recursive, če imajo podmoduli vdelane podmodule.

Če želite ta postopek avtomatizirati, lahko ukazu git pull dodate zastavico --recurse-submodules (od Git 2.14 dalje). To bo Gitu omogočilo, da takoj po potegu zažene ukaz git submodule update in podmodule postavi v pravilno stanje. Če želite vedno uporabljati --recurse-submodules za git pull, lahko nastavite konfiguracijsko možnost submodule.recurse na true (to deluje za git pull od verzije Git 2.15 dalje). Ta možnost bo Gitu omogočila, da uporabi zastavico --recurse-submodules za vse ukaze, ki to podpirajo (razen clone).

Obstaja posebna situacija, ki se lahko zgodi med posodabljanjem nadrejenega projekta: lahko se zgodi, da je oddaljeni repozitorij spremenil URL podmodula v datoteki .gitmodules v eni od potrditev, ki ste jo povlekli. To se lahko zgodi, na primer, če se projekt podmodula spremeni na drugo gostiteljsko platformo. V takem primeru lahko git pull --recurse-submodules ali git submodule update spodleti, če se nadrejeni projekt sklicuje na potrditev podmodula, ki je ni mogoče najti v oddaljenem podmodulu, ki je lokalno konfiguriran v vašem repozitoriju. Da bi rešili to situacijo, je potreben ukaz 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

Delo na podmodulu

Verjetno uporabljate podmodule, ker resnično želite delati na kodi v podmodulu istočasno, ko delate na glavni kodi (ali med več podmoduli). Drugače bi verjetno namesto tega uporabljali preprostejši sistem upravljanja odvisnosti (kot sta Maven ali Rubygems).

Pojdimo skozi primer spreminjanja podmodula istočasno z glavnim projektom ter hkratnega potrjevanja in objavljanja teh sprememb.

Do sedaj, ko smo zagnali ukaz git submodule update, da dobimo spremembe iz podmodulov, Git dobi spremembe in posodobi datoteke v poddirektoriju, vendar pusti podrepozitorij v »stanju ločene glave« (angl. detached HEAD state). To pomeni, da ni lokalne delovne veje (kot je na primer master), ki bi sledila spremembam. Če ni delovne veje, ki spremlja spremembe, to pomeni, da bodo spremembe, ki jih potrdite v podmodulu, verjetno izgubljene, ko naslednjič zaženete git submodule update. Za sledenje spremembam v podmodulu morate opraviti nekaj dodatnih korakov.

Da bi lažje začeli urejati podmodul, morate storiti dve stvari. Morate iti v vsak podmodul in izbrati vejo, na kateri boste delali. Nato morate Gitu povedati, kaj naj naredi, če ste naredili spremembe, in git submodule update --remote povleče nove spremembe od povratnega toka. Možnosti so, da jih lahko združite s svojim lokalnim delom, ali pa lahko poskusite postaviti svoje lokalno delo na vrh novih sprememb.

Najprej pojdimo v svoj imenik podmodula in si izvlecimo vejo.

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

Poskusimo posodobiti naš podmodul z možnostjo »združevanja«. Da ga ročno določimo, lahko enostavno dodamo možnost --merge našemu klicu update. Tu bomo videli, da je bila za ta podmodul sprememba na strežniku in da je združena.

$ 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'

Če gremo v direktorij DbConnector, imamo nove spremembe že združene v našo lokalno vejo stable. Sedaj poglejmo, kaj se zgodi, ko naredimo na knjižnici svoje lastne lokalne spremembe in nekdo drug istočasno potisne drugo spremembo na povratni tok.

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

Če sedaj posodobimo naše podmodule, lahko vidimo, kaj se zgodi, če imamo tako lokalno kot tudi zgornjo spremembo, ki jo moramo vdelati.

$ 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'

Če pozabite --rebase ali --merge, bo Git posodobil samo podmodul na karkoli je na strežniku in ponastavil vaš projekt na »stanje ločene glave« (angl. detached HEAD state).

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

Če se to zgodi, ne skrbite, enostavno lahko greste nazaj v direktorij in ponovno izvlečete svojo vejo (ki bo še vedno vsebovala vaše delo) ter ročno združite, ali ponovno bazirate glede na origin/stable (ali katero koli oddaljeno vejo želite).

Če niste potrdili vaših sprememb v vašem podmodulu in poženete submodule update, ki bi povzročil težave, bo Git prenesel spremembe, vendar ne bo prepisal neshranjenega dela v vašem direktoriju podmodula.

$ 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'

Če ste naredili spremembe, ki so v konfliktu z nečim iz povratnega toka, vas bo Git obvestil, da poženete posodobitev.

$ 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'

Lahko greste v direktorij podmodula in popravite konflikt, kot bi to običajno storili.

Objavljanje sprememb podmodulov

Sedaj imamo nekaj sprememb v našem direktoriju podmodula. Nekatere od teh so bile prinesene iz povratnega toka z našimi posodobitvami, ostale pa so bile narejene lokalno in niso še nikomur na voljo, saj jih še nismo potisnili.

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

Če naredimo potrditev v glavnem projektu in jo potisnemo navzgor, ne da bi hkrati potisnili tudi spremembe podmodulov, bodo imeli drugi ljudje, ki poskušajo prenesti naše spremembe, težave, saj ne bodo imeli načina, da dobijo spremembe podmodulov, od katerih so odvisni. Te spremembe bodo obstajale samo v naši lokalni kopiji.

Da se to ne zgodi, lahko Git zaprosite, da preveri, ali so bili vsi podmoduli pravilno potisnjeni, preden potisnete glavni projekt. Ukaz git push sprejme argument --recurse-submodules, ki ga lahko nastavimo na »check« ali »on-demand«. Možnost »check« bo naredila, da bo ukaz push preprosto spodletel, če niso bile potisnjene vse potrjene spremembe podmodulov.

$ 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.

Kot vidite, nam prav tako ponuja nekaj koristnih nasvetov o tem, kaj bi morda želeli storiti naslednje. Preprosta možnost je, da vstopite v vsak podmodul in ročno potisnete na oddaljene strežnike, da se prepričate, da so na voljo od zunaj, nato pa poskusite ta potisk znova. Če želite, da se ta »check« zgodi za vse potiske, lahko to obnašanje nastavite kot privzeto z ukazom git config push.recurseSubmodules check.

Druga možnost je uporaba vrednosti »on-demand«, ki bo to poskušala storiti namesto vas.

$ 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

Kot vidite, je šel Git v modul DbConnector in ga potisnil, preden je potisnil glavni projekt. Če ta poskus potiska podmodula zaradi nekega razloga spodleti, bo spodletelo tudi potiskanje glavnega projekta. To obnašanje lahko nastavite kot privzeto z ukazom git config push.recurseSubmodules on-demand.

Združevanje sprememb podmodulov

Če spremenite referenco podmodula istočasno kot nekdo drug, lahko naletite na nekaj težav. To je, če so se zgodovine podmodulov razšle in so bile potrjene v različnih vejah v nadrejenem projektu, bo morda potrebno nekaj dela, da jih popravite.

Če je ena od oddaj neposreden prednik druge (združitev fast-forward), bo Git preprosto izbral slednjo za združitev, zato to deluje brez težav.

Git vam vendarle ne bo poskušal narediti niti trivialnega združevanja. Če se potrditve podmodulov razlikujejo in jih je treba združiti, boste dobili nekaj takega:

$ 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.

Torej, kar se je tu zgodilo, je, da je Git ugotovil, da se točke v zgodovini podmodula v dveh vejah razhajajo in da jih je treba združiti. Pojasni, kot »združitve naslednjih potrditev niso bile najdene«, kar je zmedeno, vendar bomo to pojasnili malo kasneje.

Za rešitev problema morate ugotoviti, v katerem stanju naj bo podmodul. Presenetljivo Git ne ponuja veliko informacij, ki bi pomagale pri tem, niti SHA-1 potrditev obeh strani zgodovine. Na srečo je to preprosto ugotoviti. Če zaženete git diff, lahko dobite SHA-1 potrditve, posnete v obeh vejah, ki jih poskušate združiti.

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

Torej v tem primeru je potrditev eb41d76 v našem podmodulu in c771610 je potrditev, ki jo ima zgornji nivo. Če gremo v podmapo podmodula, bi ta že morala biti na eb41d76, saj združevanje ni vplivalo nanj. Če ni, lahko preprosto ustvarite in preklopite na vejo, da kaže nanj.

Pomembno je, da uporabimo SHA-1 potrditve na drugi strani. To je potrditev, ki jo bomo morali združiti in rešiti morebitne konflikte. Lahko poskusite neposredno združiti s SHA-1, ali pa ustvarite novo vejo in poskusite združiti z njo. Priporočljivo je slednje, tudi če samo zaradi boljše opisne sporočilnosti potrditve združitve.

Zato bomo šli v podmapo podmodula, ustvarili vejo z imenom »try-merge« na podlagi drugega SHA-1 zapisa iz git diff in jo ročno združili.

$ 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.

Tu dobimo dejansko konflikt pri združevanju, torej če ga rešimo in potrdimo, lahko potem enostavno posodobimo glavni projekt z rezultatom.

$ 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. Najprej rešimo konflikt.

  2. Nato se vrnemo v glavno mapo projekta.

  3. Ponovno lahko preverimo vrednosti SHA-1.

  4. Rešimo konflikt v vnosu podmodula.

  5. Izvedemo potrditev našega združevanja.

Morda je malo zmedeno, vendar ni zelo težko.

Zanimivo je, da Git obravnava še en primer. Če v podmapi obstaja potrditev združitve, ki vsebuje obe zgodovinski potrditvi, jih Git predlaga kot možno rešitev. Vidi, da je nekdo v podmapi projektov združil veji s tema dvema potrditvama, zato je morda to tisto, kar želite.

Zato je sporočilo o napaki od prej »združitve naslednjih potrditev niso bile najdene«, ker tega ni mogel storiti. Zmeda, saj kdo bi pričakoval, da bo poskusil narediti to?

Če najde eno samo potrditev združitve, ki je sprejemljiva, boste videli nekaj takega:

$ 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.

Predlagan ukaz, ki ga Git ponuja, bo posodobil indeks tako, kot bi to storili z ukazom git add (ki odpravlja konflikt), nato bo naredil potrditev. Verjetno tega ne bi smeli storiti. Prav tako lahko enostavno vstopite v imenik podmodula, preverite, kakšna je razlika, hitro previjete naprej (fast-forward) na to potrditev, jo ustrezno preizkusite in nato potrdite.

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

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

To doseže enako stvar, vendar tako vsaj lahko preverite, da deluje in da imate kodo v vašem direktoriju podmodula, ko končate.

Nasveti za podmodule

Obstaja nekaj stvari, ki jih lahko naredite za malo enostavnejše delo s podmoduli.

Foreach za podmodule

Obstaja ukaz podmodulov foreach, ki izvede neki poljuben ukaz v vsakem podmodulu. To je lahko koristno, če imate veliko podmodulov v istem projektu.

Na primer, recimo, da želimo začeti novo lastnost ali popraviti hrošča, in imamo delo v teku na nekaj podmodulih. Enostavno lahko damo vso delo v vseh naših podmodulih v shrambo.

$ 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

Nato lahko ustvarimo novo vejo in preklopimo nanjo v vseh naših podmodulih.

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

To je razumljivo. Ena izmed resnično uporabnih stvari, ki jo lahko naredite, je, da izdelate dobro poenoteno razliko tega, kar se je spremenilo v vašem glavnem projektu in tudi v vseh vaših podprojektih.

$ 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;

Tu lahko vidimo, da smo definirali funkcijo v podmodulu in jo kličemo v glavnem projektu. To je očitno poenostavljen primer, vendar upamo, da vam da idejo, kako je lahko to koristno.

Uporabni aliasi

Morda želite nastaviti nekatere bližnjice za nekatere od teh ukazov, saj so lahko precej dolgi in večine od njih ne morete nastaviti kot privzete možnosti konfiguracije. Nastavljanje bližnjic za Git smo že obravnavali v Aliasi Git, vendar vam tukaj predstavljamo primer, kaj lahko nastavite, če nameravate veliko delati s podmoduli v Gitu.

$ 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'

Na ta način lahko enostavno poženete git supdate, ko želite posodobiti vaše podmodule, ali git spush, da jih potisnete s preverjanjem odvisnosti podmodula.

Težave s podmoduli

Vendar pa uporaba podmodulov ni brez težav.

Preklapljanje vej

Na primer, preklapljanje vej s podmoduli v njimi je lahko malo zapleteno z različicami Gita starejšimi od 2.13. Če ustvarite novo vejo, dodate tja podmodul in nato preklopite nazaj na vejo brez podmodula, boste še vedno imeli podmodul v direktoriju kot nesledeni direktorij:

$ 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)

Odstranjevanje direktorija ni težavno, vendar je lahko malo nerazumljivo imeti to tam. Če ga odstranite in nato preklopite nazaj na vejo, ki ima ta podmodul, boste morali pognati submodule update --init, da ga ponovno zapolnite.

$ 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

Ponovno, ni resnično težavno, vendar je lahko malenkost nerazumljivo.

Novejše različice Git (Git >= 2.13) vse to poenostavijo z dodatkom zastavice --recurse-submodules pri ukazu git checkout, kar poskrbi za podajanje podmodulov v pravo stanje za vsako vejo, na katero preklapljate.

$ 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

Uporaba zastavice --recurse-submodules ukaza git checkout lahko prav tako pride prav, ko delate na več vejah v nadrejenem projektu in ima vsaka vaš podmodul usmerjen na različne potrditve. Če torej preklopite med vejami, ki beležijo podmodul na različnih potrditvah, se bo ob izvajanju ukaza git status podmodul pojavil kot »spremenjen« (angl. modified) in bo označeval »nove potrditve« (angl. new commits). To je zato, ker privzeto stanje podmodula ni preneseno med preklopi vej.

To vas lahko zelo zmede, zato je vedno dobra ideja, da uporabite git checkout --recurse-submodules, kadar vaš projekt vsebuje podmodule. Za starejše različice Git, ki nimajo zastavice --recurse-submodules, lahko po izvleku uporabite git submodule update --init --recursive, da postavite podmodule v pravilno stanje.

Na srečo lahko Git (>=2.14) vedno uporablja zastavico --recurse-submodules, če nastavite konfiguracijsko možnost submodule.recurse: git config submodule.recurse true. Kot je omenjeno zgoraj, bo to povzročilo, da se bo Git ponovno pogreznil v podmodule za vsak ukaz, ki ima možnost --recurse-submodules (razen git clone).

Prehod iz podimenikov v podmodule

Druga velika težava, s katero se srečuje veliko ljudi, se nanaša na prehajanje iz podimenikov v podmodule. Če ste spremljali datoteke v svojem projektu in jih želite premakniti v podmodul, morate biti previdni, saj vam Git ne bo naklonjen. Predpostavimo, da imate datoteke v podmapi svojega projekta in jih želite preklopiti v podmodul. Če izbrišete podmapo in nato zaženete submodule add, vam Git javi napako:

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

Direktorij CryptoLibrary morate najprej odstraniti iz področja priprave. Nato lahko dodate podmodul:

$ 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.

Recimo, da ste to storili v veji. Če poskušate preklopiti nazaj v vejo, kjer so te datoteke še vedno v dejanskem drevesu namesto v podmodulu, boste prejeli to napako:

$ 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

Spremembo lahko prisilite s checkout -f, vendar bodite previdni, da v tej mapi nimate neshranjenih sprememb, saj bi jih lahko ta ukaz prepisal.

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

Nato, ko se vrnete nazaj, boste iz neznanega razloga dobili prazno mapo CryptoLibrary in tudi git submodule update morda ne bo pomagal. Morda boste morali iti v podmapo podmodula in zagnati git checkout ., da dobite vse datoteke nazaj. To lahko za več podmodulov zaženete v skriptu submodule foreach.

Pomembno je opozoriti, da podmoduli dandanes ohranjajo vse svoje podatke Git v direktoriju .git glavnega projekta, tako da v primerjavi z veliko starejšimi različicami Git izbris podmodula ne bo izgubil nobenih oddanih potrditev ali vej.

Z uporabo teh orodij lahko podmoduli predstavljajo precej preprost in učinkovit način za razvoj več povezanih, a še vedno ločenih projektov hkrati.

scroll-to-top