Articles

Mestre Git submodules

Submodules, trinn for trinn

Vi vil nå utforske alle trinn for å bruke submodules i et samarbeidsprosjekt, noe som gjør at vi fremheve standard atferd, feller og tilgjengelig forbedringer.

for å legge til rette for å følge sammen, jeg har satt sammen et par eksempel repos med sine «fjernkontroller» (faktisk bare kataloger)., Du kan dekomprimere arkivet hvor du vil, og deretter åpne et skall (eller Git Bash, hvis du er på Windows) i git-subs-katalogen, det skaper:

Last ned eksempel repos

Du vil finne tre kataloger i det:

  • main fungerer som den beholder repo, lokale til det første samarbeidspartner,
  • plugin fungerer som den sentrale vedlikehold repo for modulen, og
  • fjernkontroller inneholder filsystemet fjernkontroller for de to foregående repos.

I eksempelet kommandoene nedenfor, ledeteksten vises alltid som repo vi er i.,

Legge til en submodule

La oss starte med å legge til våre plugin som en submodule inne i våre container (som er i main). Plugin selv har en enkel struktur:

.
├── README.md
├── lib
│ └── index.js
└── plugin-config.json

Så la oss gå inn i hovedmenyen, og bruk git submodule legge til kommandoen. Det tar den eksterne URL-adresse og en undermappe som å «instantiate» den submodule.

Fordi vi bruker stier i stedet for Nettadresser her for våre fjernkontroller, traff vi en merkelig, men kjente ulempe: relative stier for fjernkontroller kan tolkes i forhold til våre viktigste eksterne, ikke til våre repo ‘ s root directory., Dette er super merkelig, ikke beskrevet noe sted, men jeg har sett det skje hver gang. Så i stedet for å si ../remotes/plugin, vi bare sier ../programtillegg.

main (master u=) $ git submodule add ../plugin vendor/plugins/demo
Cloning into 'vendor/plugins/demo'…
done.
main (master + u=) $

lagt til noen innstillinger i vår lokale konfigurasjon:

main (master + u=) $ cat .git/config


url = ../remotes/plugin

Og dette er også arrangert to filer:

Huh?! Hva er dette .gitmodules fil? La oss se på det:

main (master + u=) $ cat .gitmodules

path = vendor/plugins/demo
url = ../plugin

Dette rasende ligner våre lokale config… Så hvorfor duplisering? Vel, nettopp fordi vår lokale config er… lokale., Våre samarbeidspartnere vil ikke se det (noe som er helt normalt), så de trenger en mekanisme for å få definisjoner av alle submodules de trenger for å sette opp i deres egne depot. Dette er hva .gitmodules er for, og det vil bli lese senere av git submodule init-kommandoen, som vi vil se i en liten stund.

Mens vi er på status, legg merke til hvordan minimalistisk det er når det kommer til våre submodule: det går bare med en altfor generisk ny fil i stedet for å fortelle oss mer om hva som skjer inni den., Våre submodule var faktisk injisert i underkatalogen:


└── vendor
└── plugins
└── demo
├── .git
├── README.md
├── lib
│ └── index.js
└── plugin-config.json

Status, som logger og diff, er begrenset til den aktive repo (akkurat nå, beholderen), for ikke å submodules, som er nestet repos. Dette er ofte problematisk (det er super lett å gå glipp av en regresjon er da begrenset til dette bildet), så jeg anbefaler deg å sette opp en submodule-klar-status en gang for alle:

git config --global status.submoduleSummary true

det er nå:

Aaaah, dette er vesentlig bedre., Status utvider sin base informasjon å legge til at submodule til stede på leverandør/plugins/demo fikk 3 nyheter begår i (som vi akkurat har opprettet det, betyr det at den eksterne grenen hadde bare tre begår), den siste blir et tillegg (merk høyre peker vinkelparentes, >) med en første begå melding linjen «Fikse repo navn…».

for å virkelig bringe hjem som vi har avtale med to separate repos her, la oss komme inn i submodule er katalogen:

Den aktive repo har endret seg, fordi en ny .,git tar over: i den gjeldende katalogen (demo, submodule er katalogen), en .git finnes faktisk en enkelt fil for, ikke en katalog. La oss se på innsiden:

demo (master u=) $ cat .git
gitdir: ../../../.git/modules/vendor/plugins/demo

Igjen, siden Git 1.7.8, Git ikke la repo kataloger inne i containeren er arbeidsmappe, men sentraliserer disse i beholderen er .git-katalogen (innsiden .git/moduler), og bruker en gitdir referanse i submodules.,

begrunnelsen bak dette er enkel: det gjør det mulig beholderen repo å ha submodule-mindre grener, uten å måtte kvitte seg med den submodule ‘ s repo fra arbeidsmappe og gjenopprette det senere.

Naturlig, når du legger den submodule, kan du velge å bruke en bestemt gren, eller enda en bestemt begå, å bruke-b CLI-alternativet (som per vanlig, standard er master). Merk at vi ikke er, akkurat nå, på en enebolig på hodet, i motsetning til hva som vil skje senere: dette er fordi Git sjekket ut master, ikke en bestemt SHA1. Vi ville ha hatt for å angi en SHA1-til-b for å få en enebolig hodet fra get-go.,

Så, tilbake til beholderen repo, og la oss avslutte submodule er tillegg og skyv det til den eksterne:

demo (master u=) $ cd -
main (master + u=) $ git commit -m "Ajout submodule plugin demo"
main (master u+1) $ git push

Gripe en repo som bruker submodules

for å illustrere problemene med å samarbeide om en repo som bruker submodules, vil vi delt personligheter og fungere som vår kollega, som kloner container-ens fjernkontroll til å begynne å jobbe med oss. Vi vil klone som i en kollega katalogen, så vi kan med en gang fortelle hvilke personlighet cap vi har på til enhver tid.,

Den første til å legge merke til er at våre submodule mangler fra arbeidsmappen; bare sin base katalogen er her:

vendor
└── plugins
└── demo

Hvordan gikk det skje? Dette er bare på grunn av det faktum at, så langt, har våre nye repo (kollega) er ikke klar over vår submodule ennå: informasjon for det er langt i sin lokale konfigurasjon (sjekk sine .git/config hvis du ikke tror meg). Vi trenger å fylle i, basert på hva .gitmodules har å si, som er nøyaktig hva git submodule init gjør:

Vår .git/config er nå klar for vår submodule., Imidlertid har vi fortsatt ikke har hentet den fra den eksterne, for ikke å si ha det til stede i vår arbeidsmappe. Og likevel, vår status viser seg som rene!

Se, vi trenger å ta tak i relevante begår manuelt. Det er ikke noe våre første klone gjorde det, vi må gjøre det på hvert trekk. Vi vil komme tilbake til det i et minutt, så dette er en atferd klone kan faktisk automatisere, når de er riktig kalles.,

I praksis, når du arbeider med submodule-med repos, vi vanligvis gruppe de to kommandoer (init og oppdatering) i ett:

colleague (master u=) $ git submodule update --init

Det er fortsatt en skam at Git er du gjør alt selv. Tenk, på større FLOSS prosjekter, når submodules har sine egne submodules, og så videre og så videre… Dette ville fort bli et mareritt.

Det skjer, slik at Git gir en CLI muligheten for å klone automatisk git submodule update — init undermapper rett etter kloning: det ganske treffende navn — rekursive alternativ.,

Så la oss prøve hele greia igjen:

Nå som er bedre! Merk at vi nå på en enebolig hodet inne i submodule (som vi skal fra nå av):

git-subs $ cd colleague/vendor/plugins/demo
demo ((master)) $

Se dobbelt sett med parenteser i min ledeteksten, i stedet for et enkelt sett?, Hvis meldingen ikke er konfigurert som min, for å vise enebolig hodet som beskriver (med Git er bygget-i be-skript, vil du har til å definere GIT_PS1_DESCRIBE_STYLE=gren miljø-variabelen), vil du heller se noe som dette:

demo ((fe64799...)) $

I alle fall, status bekrefter hvor vi er på:

demo ((master)) $ git status
HEAD detached at fe64799
nothing to commit, working directory clean

Få en oppdatering fra submodule-ens fjernkontroll

OK, nå som vi har vårt eget repo (main) og vår «kollega» (kollega) er alle satt opp til å samarbeide, la oss gå inn i skoene til en tredje person: den som opprettholder plugin., Her, la oss gå til det:

Nå, la oss legge to pseudo-forplikter og publisere disse til ekstern:

til Slutt, la oss sette vår «første-utvikler» lokket på igjen:

plugin (master u=) $ cd ../main
main (master u=) $

la oss Anta at vi nå ønsker å få disse to forplikter seg inne i våre submodule. For å oppnå dette trenger vi å oppdatere sin lokale repo, starter ved å flytte inn i sin arbeidsmappe, så det blir vår aktive repo.

På en side note, jeg vil ikke anbefale å bruke trekk for denne typen oppdatering., For å riktig få oppdateringer i arbeidsmappen, denne kommandoen krever at du er på riktig aktiv gren, som du vanligvis ikke (du er på en enebolig hodet mesteparten av tiden). Ville du har å starte med en sluttføring av den grenen. Men, viktigst av alt, den eksterne gren kunne godt ha flyttet videre fremover siden begå du ønsker å sette på, og en trekk ville injisere forplikter du ønsker kanskje ikke i din lokale codebase.,

Derfor, jeg anbefaler å splitte prosessen manuelt: første git hent for å få alle nye data fra den eksterne i lokal cache, logg deg til å bekrefte det du har og kassa på ønsket SHA1. I tillegg til finere detaljert kontroll, denne tilnærmingen har den ekstra fordelen av å jobbe uavhengig av din nåværende tilstand (aktiv gren eller frittstående hodet).

OK, så vi er godt, ingen unødvendig begå., Være at siden det kan, la oss eksplisitt på den vi er interessert i (selvfølgelig du har en annen SHA1):

demo (master u-2) $ git checkout -q 0e90143

(Den -q er det bare å spare oss Git blabbering om hvordan vi ender opp på en enebolig hodet. Vanligvis ville dette være en sunn påminnelse, men om dette vet vi hva vi gjør.)

Nå som vår submodule er oppdatert, kan vi se resultatet i beholderen repo status:

I den «klassiske» en del av status, ser vi en ny begår endre type, noe som betyr at den refererte begå endret., En annen mulighet (som kan settes sammen til dette) er det nye innholdet, noe som ville bety at vi har gjort lokale endringer til submodule er arbeidsmappe.

Den nedre delen, aktivert av vår status.submoduleSummary = true innstillingen tidligere, uttrykkelig sier det innført begår (som de bruker en høyre-peker vinkelparentes, >) siden vår siste container forplikte som hadde rørt submodule.

I «forferdelig standard atferd» familie, git diff etterlater mye å være ønsket:

Hva — ?, Det er en CLI-alternativet som lar oss se noe mer nyttig:

main (master * u=) $ git diff --submodule=log
Submodule vendor/plugins/demo fe64799..0e90143:
> Pseudo-commit #2
> Pseudo-commit #1

Det er ingen andre lokale endringer akkurat nå foruten submodule er referert begå… legg Merke dette samsvarer med nesten nøyaktig den nedre delen av vår utvidede git-status-skjerm.

etter å Ha skrive den slags CLI alternativet hver gang (som, forresten, ikke vises i Git er gjeldende ferdigstillelse tilbyr) er ganske uhåndterlig. Heldigvis, det er en matchende konfigurasjonsinnstilling:

Vi trenger nå bare å utføre container forplikte som avslutter vår submodule er oppdateringen., Hvis du hadde å berøre container koden for å gjøre det arbeidet med denne oppdateringen, vil begå det sammen, naturligvis. På den annen side, unngå å blande submodule-relaterte endringene og andre ting som bare ville angå container kode: ved pent å skille de to, senere flyttinger til andre code-gjenbruk tilnærminger er gjort enklere (også, som vanlig, atomic begår FTW).

Som vi er i ferd med å ta dette submodule oppdatering i vår kollega ‘ s repo, vil vi presse høyre etter å ha begått (som ikke er en generell god praksis).

main (master * u=) $ git commit -am "Setting submodule on PC2"
main (master u+1) $ git push

Trekke en submodule-med repo

Klikk!, «Kollega» lue på!

Så vi trekker oppdateringer fra beholderen repo-ens fjernkontroll…

(Du har kanskje ikke den «Vellykket rebased og oppdatert…» og ser en «Merge gjort av ‘rekursiv’ strategi» i stedet. Hvis så, mitt hjerte går ut til deg, og du bør straks finne ut hvorfor trekker bør rebase).

Merk den andre halvparten av dette bildet: det handler om submodule, som starter med «Henting submodule…».

Dette problemet ble standard med Git 1.7.5, med innstillinger for henting.,recurseSubmodules nå automatisk blir satt til on-demand: hvis en beholder prosjektet blir oppdateringer til refererte submodule forplikter, disse submodules få hentet automatisk. (Husk at samlingen er den første delen av å trekke.)

Likevel, og dette er kritisk: Git auto-henter, men ikke auto-oppdatering. Den lokale hurtigbufferen er up-to-date med den submodule-ens fjernkontroll, men den submodule er arbeidsmappe fast til sin tidligere innhold. Minst, du kan stenge at laptop, hoppe på et fly, og fortsatt smi videre når du er frakoblet., Selv om dette automatisk henting er begrenset til det som allerede er kjent submodules: noen nye, ennå ikke kopiert inn i lokale konfigurasjon, er ikke automatisk hentes.

Git auto-henter, men ikke auto-oppdatering.

Den nåværende spør, med sin stjerne ( * ) gir et hint om lokale modifikasjoner, fordi våre WD er ikke i sync med indeksen, sistnevnte er klar over det nylig referert til submodule forplikter. Sjekk ut status:

legg Merke til hvordan vinkelparenteser punkt venstre (<)?, Git ser at dagens WD ikke har disse to forplikter seg, i motsetning til beholderen prosjektets forventninger.

Dette er den massive fare: hvis du ikke eksplisitt oppdatering de submodule er arbeidsmappe, ditt neste container begå vil regress den submodule. Dette er et første-ordens felle.

Er derfor obligatorisk at du fullfører oppdateringen:

Så lenge vi prøver å danne generisk gode vaner, foretrukket kommandoen her ville være et git submodule update — init — rekursive, for å auto-init alle nye submodule, og undermapper oppdatere disse ved behov.,

Det er en annen kant tilfelle: hvis submodule er ekstern URL endret seg siden sist brukt (kanskje en av de samarbeidspartnere som endret i den .gitmodules), må du manuelt oppdatere din lokale config for å matche dette. I en slik situasjon, før git submodule oppdatering, vil du trenger for å kjøre en git submodule sync.

jeg bør nevne, for fullstendighet’ skyld, at selv om git submodule oppdatering som standard til å sjekke ut den refererte SHA1, kan du endre det til, for eksempel, rebase noen lokale submodule arbeid (vi skal snakke om det veldig snart) på toppen av det., Vil du gjøre det ved å sette oppdatere konfigurasjonen setting for ditt submodule å rebase inne i beholderen din lokale konfigurasjon.

Og jeg beklager, men nei, det er ingen lokale konfigurasjon stille, eller enda CLI alternativ for den saks skyld, som kan auto-oppdatering på trekk. Til å automatisere slike ting, ville du trenger å bruke enten aliaser, egendefinert skript, eller nøye utformet lokale kroker., Her er et eksempel spull alias (én linje, split her for visning):

git config --global alias.spull '!git pull && git submodule sync --recursive && git submodule update --init --recursive'

Hvis du ønsker å beholde muligheten til å passere tilpasset argumenter for å git pull, kan du enten angi en funksjon on-the-fly, og kaller det, eller gå med en tilpasset skript. Den første tilnærmingen vil se ut som dette (igjen, med én linje):

Ikke veldig lesbar, eh? Jeg foretrekker egendefinert skript tilnærming., La oss si at du vil sette et git-spull script fil et eller annet sted inne på BANEN (jeg har en ~/person/bin-katalogen i min VEI bare for slike ting):

#! /bin/bash
git pull "$@" &&
git submodule sync --recursive &&
git submodule update --init --recursive

deretter gir Vi det kjøring rettigheter:

chmod +x git-spull

Og nå kan vi bruke den akkurat som vi ville ha brukt aliaset.

Oppdatering av en submodule i-plasser i beholder

Dette er den vanskeligste bruk-saken, og du bør holde deg unna det så mye som mulig, og foretrakk vedlikehold gjennom sentral -, dedikert repo.,

Imidlertid kan det skje at submodule koden kan ikke testes, eller selv utarbeidet, utenfor container code. Mange temaer og plugins har slike begrensninger.

Den første tingen å forstå er, fordi du kommer til å gjøre forplikter, må du starte fra riktig grunnlag, som vil være en gren tips. Du må derfor bekrefte at grenen er siste forplikter seg ikke til å «bryte» beholderen prosjektet. Hvis de gjør det, vel, lage din egen container-spesifikke grenen i submodule høres fristende, men at veien fører til sterk kopling mellom submodule og beholder som er ikke tilrådelig., Det kan hende du ønsker å stoppe «submoduling» at koden i dette prosjektet, og bare legge det som en vanlig innholdet i stedet.

La oss innrømme det du kan, med god samvittighet, legge til submodule nåværende master gren. La oss starte ved å synkronisere våre lokale staten på fjernkontrollen:

en Annen måte å gå om dette ville være, fra beholderen repo, for eksplisitt å synkronisere submodule ‘ s lokallag over sin spores ekstern gren (én linje på toppen, siste — etterfulgt av mellomrom):

Vi kan nå redigere koden, kan du gjøre det arbeidet, kan du teste den, osv., Når vi er alt i orden, vi kan da utføre to forplikter og de to nødvendig presser (det er super enkelt, og i praksis altfor ofte, for å glemme noe av det).

La oss bare legge til falske arbeid og gjøre de to i slekt forplikter, på submodule og beholder nivåer:

På dette punktet, er den største faren er å glemme å presse submodule. Du kommer tilbake til beholderen prosjektet, begå det, og bare skyv beholderen. Det er en enkel feil å gjøre, spesielt inne i en IDE-eller GUI. Når dine kolleger prøver å få oppdateringer, alle helvete bryter løs., Se på den første trinn:

Det er absolutt ingen indikasjon på at Git kunne ikke hente den refererte begå fra submodule-ens fjernkontroll. Den første hint av dette er i status:

legg Merke til advarselen: tilsynelatende, den nylig refererte forplikte seg for submodule er ingensteds å bli funnet. Faktisk, hvis vi forsøker å oppdatere submodule arbeider katalogen, får vi:

main (master * u=) $ git submodule update
fatal: reference is not a tree: 12e3a529698c519b2fab790630f71bd531c45727
Unable to checkout '12e3a529698c519b2fab790630f71bd531c45727' in submodule path 'vendor/plugins/demo'

Du kan tydelig se hvor viktig det er å huske å skyve submodule også, ideelt sett, før du skyver container., La oss gjøre det i kollega og prøv oppdateringen på nytt:

jeg bør være oppmerksom på det er en CLI-alternativet som vil kontrollere om tiden refererte submodule begår trenger å bli presset for, og hvis så vil presse dem: det er git push — recurse-submodules=on-demand (litt av en munnfull, riktignok). Det er behov for å ha noe container-nivå for å presse til å fungere, men bare submodules vil ikke kutte den.

Hva er mer, (det er ingen konfigurasjon innstillingen for dette, slik at du har til å standardisere rutiner rundt et alias, f.eks. spush:) starter med Git 2.7.0), er det nå en push.,recurseSubmodules konfigurasjoner innstillingen kan du angi (on-demand eller sjekk).

git config --global alias.spush 'push --recurse-submodules=on-demand'

Fjerne en submodule

Det er to situasjoner der du ønsker å «fjerne» en submodule:

  • Du bare ønsker å fjerne arbeidsmappe (kanskje før arkivering beholderen er WD), men ønsker å beholde muligheten for å gjenopprette det senere (så det må være i .gitmodules og .git/modules);
  • Du vil definitivt fjerne det fra gjeldende gren.

La oss se på hvert enkelt tilfelle i sving.,

Midlertidig fjerne en submodule

Den første situasjonen er lett håndteres av git submodule deinit. Se for deg selv:

Dette har ingen innvirkning på beholderen status overhodet. Den submodule ikke er lokalt kjent lenger (det er borte fra .git/config), så dens fravær fra arbeidsmappen går ubemerket hen. Vi har fortsatt leverandøren/plugins/demo-katalogen, men den er tom, vi kunne stripe det med ingen konsekvens.

submodule må ikke ha noen lokale modifikasjoner når du gjør dette, ellers ville du trenger å tvinge samtale.,

Eventuelle senere delkommandoen av git submodule vil salig bort fra denne submodule til du init det igjen, som submodule vil ikke engang være i lokale config. Slike kommandoer inkluderer oppdater, foreach og synkronisering.

På den annen side, submodule fortsatt er definert i .gitmodules: en init etterfulgt av en oppdatering (eller en eneste oppdatering — init) vil gjenopprette den som ny:

Permanent fjerne en submodule

Dette betyr at du ønsker å bli kvitt den submodule for godt: en vanlig git rm vil gjøre, akkurat som for alle andre del av arbeidsmappen., Dette vil bare fungere hvis submodule bruker en gitfile (en .git, som er en fil, ikke en katalog), som er tilfellet starter med Git 1.7.8. Ellers vil du ha for å håndtere dette for hånd (jeg skal fortelle deg hvordan på slutten).

I tillegg til stripping submodule fra arbeidsmappen, kommandoen vil oppdatere .gitmodules fil, slik at den ikke referanse submodule lenger. Her kan du gå til:

Naturlig, avansert status info tur over seg her, fordi gitfile for submodule er borte (faktisk, hele demo-katalogen forsvunnet).,

Hva er odd skjønt, er at den lokale config beholder submodule informasjon, i motsetning til hva som skjer når du deinit. Så, for en omfattende fjerning, anbefaler jeg at du gjør begge deler, i rekkefølge, for så å ende opp på riktig måte ryddet opp (det ville ikke fungere etter at vår forrige kommando, fordi det er fjernet .gitmodules allerede):

git submodule deinit path/to/module # ensure local config cleanup
git rm path/to/module # clean WD and .gitmodules

Uansett tilnærming, submodule ‘ s repo fortsatt til stede i .git/moduler/leverandør/plugins/demo, men du er fri til å drepe at når du ønsker.

Hvis du trenger å fjerne en submodule som var satt opp før Git 1.7.,8, og derfor bygger sin .git-katalogen rett i beholderen er arbeidsmappe (i stedet for å stole på en gitfile), vil du har til å bryte ut bulldoser: de to foregående kommandoer må innledes med en manual fjerning, f.eks. rm -fr leverandør/plugins/demo, fordi sa kommandoer vil alltid nekte å slette en faktisk depotet.