Articles

Mastering af Git-undermoduler

undermoduler, trin for trin

Vi undersøger nu hvert trin i at bruge undermoduler i et samarbejdsprojekt, og sørger for, at vi fremhæver standardadfærd, fælder og tilgængelige forbedringer.

for at gøre det lettere at følge med, har jeg sammensat et par eksempel repos med deres “fjernbetjeninger” (faktisk bare mapper)., Du kan udpakke arkivet, hvor du ønsker, og derefter åbne en shell (eller Git Bash, hvis du er på Windows) i git-subs mappe, det skaber:

Download eksempel genkøbsforretninger

Du vil finde tre mapper, der:

  • main fungerer som container repo, lokale til den første samarbejdspartner,
  • plugin fungerer som den centrale vedligeholdelse repo til modulet, og
  • fjernbetjeninger indeholder filsystem fjernbetjeninger til de to foregående genkøbsforretninger.

i eksempelkommandoerne nedenfor viser prompten altid, hvilke repo vi er i.,

tilføjelse af en undermodul

lad os starte med at tilføje vores plugin som en undermodul inde i vores container (som er i hoved). Selve plugin har en simpel struktur:

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

så lad os gå ind i hoved og bruge kommandoen git submodule add. Det tager fjernbetjeningens URL og en undermappe til at” instantiere ” undermodulet.fordi vi bruger stier i stedet for Urebadresser her til vores Fjernbetjeninger, rammer vi en underlig, omend velkendt, snag: relative stier til fjernbetjeninger fortolkes i forhold til vores vigtigste fjernbetjening, nej til vores Repos rodmappe., Dette er super underligt, ikke beskrevet nogen steder, men jeg har set det ske hver gang. Så i stedet for at sige ../ Fjernbetjeninger / plugin, vi bare sige ../plug.

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

Denne tilføjet nogle indstillinger i vores lokale konfiguration:

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


url = ../remotes/plugin

Og dette også afholdt to filer:

Huh?! Hvad er det her .gitmodules fil? Lad os se på det:

main (master + u=) $ cat .gitmodules

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

dette ligner rasende vores lokale config … så hvorfor duplikeringen? Netop fordi vores lokale config er … lokal., Vores samarbejdspartnere vil ikke se det (hvilket er helt normalt), så de har brug for en mekanisme til at få definitionerne af alle undermoduler, de har brug for at oprette i deres egne repos. Dette er hvad .gitmodules er til; det læses senere af GIT submodule init-kommandoen, som vi ser om et øjeblik.

mens vi er på status, bemærk, hvor minimalistisk det er, når det kommer til vores undermodul: det går bare med en alt for generisk ny fil i stedet for at fortælle os mere om, hvad der foregår inde i den., Vores submodule var faktisk injiceres i underbiblioteket:


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

Status, ligesom logfiler og diffs, er begrænset til den aktive repo (lige nu, container), for ikke at submodules, som er indlejret genkøbsforretninger. Dette er ofte problematisk (det er super nemt at gå glip af en regression, når den er begrænset til denne visning), så jeg anbefaler, at du opretter en undermodul-opmærksom status en gang for alle:

git config --global status.submoduleSummary true

og nu:

Aaaah, dette er langt bedre., Status udvider sin base oplysninger at tilføje, at submodule til stede på sælger/plugins/demo fik 3 nyheder begår i (som vi lige har oprettet det, det betyder, at fjernbetjeningen gren havde kun tre begår), den sidste ene er en tilføjelse (bemærk den peger til højre vinkel beslag >), med en første begå besked linje, der hedder “Fix repo navn…”.

for virkelig at bringe hjem, at vi beskæftiger os med to separate repos her, lad os komme ind i undermodulets bibliotek:

den aktive repo er ændret, fordi en ny .,git overtager: i den aktuelle mappe (demo, undermodulets mappe), a .git eksisterer faktisk en enkelt fil også, ikke en mappe. Lad os kigge ind:

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

Igen, da Git 1.7.8, Git ikke forlade repo mapper inde i beholderen er arbejdsmappe, men centraliserer disse i containeren .git bibliotek (inde .Git / moduler), og bruger en gitdir reference i undermoduler.,

rationalet bag dette er simpelt: det giver container repo at have submodule-mindre grene, uden at skulle skrotte submodule er repo fra arbejdsmappen og gendanne den senere.

Når du tilføjer undermodulet, kan du naturligvis vælge at bruge en bestemt gren eller endda en bestemt commit ved hjælp af-B CLI-indstillingen (som normalt er standard master). Bemærk, at vi ikke er lige nu på et løsrevet hoved, i modsætning til hvad der vil ske senere: dette skyldes, at Git checkede master, ikke en bestemt SHA1. Vi ville have været nødt til at specificere en SHA1 til-b for at få et løsrevet hoved fra get-go.,

Så tilbage til beholderen repo, og lad os færdiggøre submodule ‘ s over og skubbe det til fjernbetjeningen:

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

Snuppe en repo, der bruger submodules

for at illustrere problemer med at samarbejde om en repo, der bruger submodules, vi vil dele personligheder og fungere som vores kollega, der kloner container ‘ ets fjernbetjening til at begynde at arbejde med os. Vi kloner det i en kollegakatalog, så vi straks kan fortælle, hvilken personlighedshætte vi har på til enhver tid.,

den første ting at bemærke er, at vores undermodul mangler i arbejdsmappen; kun dens basismappe er her:

vendor
└── plugins
└── demo

hvordan skete det? Dette skyldes simpelthen det faktum, at vores nye repo (kollega) indtil videre ikke er opmærksom på vores undermodul endnu: informationen til den er intetsteds i dens lokale konfiguration (kontroller dens .git / config hvis du ikke tror mig). Vi bliver nødt til at udfylde det, baseret på hvad .gitmodules har at sige, hvilket er præcis, hvad git submodule init gør:

vores.git / config er nu opmærksom på vores undermodul., Vi har dog stadig ikke hentet det fra dets fjernbetjening for ikke at sige noget om at have det til stede i vores arbejdsmappe. Og alligevel, vores status viser sig som ren!

se, vi er nødt til at gribe de relevante forpligtelser manuelt. Det er ikke noget, vores første klon gjorde, vi er nødt til at gøre det ved hvert træk. Vi kommer tilbage til det om et minut, da dette er en adfærdsklon faktisk kan automatisere, når det er korrekt kaldt.,

i praksis grupperer vi normalt de to kommandoer (init og update) i en:

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

det er stadig en skam, at Git har dig til at gøre alt det selv. Forestil dig, på større FLOSS projekter, når undermoduler har deres egne undermoduler, og så videre og så videre … dette ville hurtigt blive et mareridt.

Det så sker, at Git giver en CLI mulighed for klon til automatisk git submodule update — init rekursivt lige efter kloning: den temmelig rammende navngivne — rekursive løsning.,

så lad os prøve det hele igen:

nu er det bedre! Bemærk, at vi nu på en fritliggende hoved i submodule (som vi vil være fra nu af):

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

Se dobbelt sæt af parenteser i min prompt, i stedet for et enkelt sæt?, Hvis din prompt ikke er konfigureret som mit, for at vise aftagne hoved, som beskriver (med Git ‘ s indbyggede lynhurtig script, du er nødt til at definere GIT_PS1_DESCRIBE_STYLE=gren miljø-variabel), vil du hellere se noget som dette:

demo ((fe64799...)) $

under alle omstændigheder, status bekræfter, hvor vi er på:

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

Få en opdatering fra submodule remote

OK, når vi nu har vores eget repo (hoved) og vores “kollega” (kollega) alle oprettet til collaborate (samarbejde), lad os træd ind i sko af en tredje person: den der opretholder plugin., Her, lad os komme til det:

lad os Nu tilføje to pseudo-forpligter, og offentliggør disse på fjernbetjeningen:

Endelig, lad os sætte vores første “udvikler” hætten på igen:

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

Antag at vi ønsker nu at få disse to forpligter sig inde i vores submodule. For at opnå dette skal vi opdatere sin lokale repo, startende med at flytte ind i sin arbejdsmappe, så det bliver vores aktive repo.

på en sidebemærkning vil jeg ikke anbefale at bruge træk til denne type opdatering., For korrekt at få opdateringerne i arbejdsmappen kræver denne kommando, at du er på den rigtige aktive gren, som du normalt ikke er (du er mest på et fritliggende hoved). Du skal starte med en checkout af den filial. Men endnu vigtigere, fjerngrenen kunne meget vel have bevæget sig længere frem siden den forpligtelse, du vil sætte på, og et træk ville injicere forpligtelser, som du måske ikke ønsker i din lokale kodebase.,

derfor anbefaler jeg at opdele processen manuelt: først git Hent for at hente alle nye data fra fjernbetjeningen i lokal cache, log derefter for at kontrollere, hvad du har, og kassen på den ønskede SHA1. Ud over finere kornet kontrol har denne tilgang den ekstra fordel at arbejde uanset din aktuelle tilstand (aktiv gren eller løsrevet hoved).

OK, så vi er gode, ingen fremmede commit., Vær det som muligt, lad os eksplicit indstille på den, vi er interesseret i (selvfølgelig har du en anden SHA1):

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

(- q er kun der for at skåne os Git blabbering om, hvordan vi ender på et løsrevet hoved. Normalt ville dette være en sund påmindelse, men på denne ene ved vi, hvad vi laver.)

nu hvor vores undermodul er opdateret, kan vi se resultatet i container Repos status:

i den “klassiske” del af status ser vi en ny commits-ændringstype, hvilket betyder, at den refererede commit ændres., En anden mulighed (som kunne forværres til denne) er nyt indhold, hvilket ville betyde, at vi lavede lokale ændringer i undermodulets arbejdsmappe.

den nederste del, aktiveret af vores status.submoduleSummary = true indstilling tidligere angiver udtrykkeligt de introducerede commits (da de bruger en retpegende vinkelbeslag >) siden vores sidste container commit, der havde rørt undermodulet.

i familien “frygtelig standardadfærd” efterlader git diff meget at ønske:

hvad — ?, Der er en CLI-indstilling, der lader os se noget mere nyttigt:

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

Der er ingen andre lokale ændringer lige nu udover undermodulets refererede commit… Bemærk, at dette matcher næsten nøjagtigt den nederste del af vores forbedrede git-Statusvisning.

at skulle skrive den slags CLI-indstilling hver gang (som forresten ikke vises i Gits nuværende færdiggørelsestilbud) er ret uhåndterligt. Heldigvis er der en matchende konfigurationsindstilling:

Vi behøver nu kun at udføre containerforpligtelsen, der afslutter vores undermodules opdatering., Hvis du skulle røre containerens kode for at få den til at fungere med denne opdatering, skal du forpligte den naturligt. På den anden side skal du undgå at blande submodulrelaterede ændringer og andre ting, der bare vedrører containerkoden: ved pænt at adskille de to, gøres senere migrationer til andre kodegenbrugsmetoder lettere (også som sædvanligt forpligter atomic ft.).da vi er ved at få fat i denne undermodulopdatering i vores kollegas repo, skubber vi lige efter at have begået (hvilket ikke er en generel god praksis).

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

træk en undermodul-ved hjælp af repo

klik!, “Kollega” cap på!

så vi trækker opdateringer fra container Repos fjernbetjening…

(du har måske ikke “succesfuldt rebased og opdateret…” og se en “fusion lavet af ‘rekursiv’ strategi” i stedet. Hvis ja, går mit hjerte ud til dig, og du skal straks lære, hvorfor trækker skal rebase).

Bemærk anden halvdel af dette display: det handler om undermodulet, der starter med “Hent undermodulet…”.

denne adfærd blev standard med Git 1.7.5, med konfigurationsindstillingen hente.,recurseSubmodules nu misligholder on-demand: hvis en container projekt får opdateringer til refererede undermodule begår, bliver disse undermoduler hentes automatisk. (Husk at hente er den første del af at trække.)

stadig, og dette er kritisk: Git henter automatisk, men opdaterer ikke automatisk. Din lokale cache er opdateret med undermodulets fjernbetjening, men undermodulets arbejdsmappe sidder fast på dets tidligere indhold. I det mindste kan du lukke den bærbare computer, hoppe på et fly og stadig gå videre en gang offline., Selvom denne Automatisk hentning er begrænset til allerede kendte undermoduler: eventuelle nye, der endnu ikke er kopieret til lokal konfiguration, hentes ikke automatisk.

Git henter automatisk, men opdaterer ikke automatisk.

den aktuelle prompt med sin stjerne ( * ) antyder lokale ændringer, fordi vores did ikke er synkroniseret med indekset, hvor sidstnævnte er opmærksom på de nyligt refererede undermodule-forpligtelser. Tjek status:

bemærk, hvordan vinkelbeslagene peger til venstre (<)?, Git ser, at den nuværende WD ikke har disse to forpligtelser, i modsætning til containerprojektets forventninger.

Dette er den massive fare: hvis du ikke eksplicit opdaterer undermodulets arbejdsmappe, vil din næste containerforpligtelse regressere undermodulet. Dette er en første ordens fælde.

er derfor obligatorisk, at du afslutter opdateringen:

så længe vi forsøger at danne generiske gode vaner, ville den foretrukne kommando her være en git — undermodulopdatering — init-rekursiv, for at auto-init enhver ny undermodul og rekursivt opdatere disse om nødvendigt.,

Der er en anden kant sag: hvis undermodulets fjernbetjening URL ændret siden sidst brugt (måske en af samarbejdspartnerne ændret det i .gitmodules), skal du manuelt opdatere din lokale config for at matche dette. I en sådan situation, før Git-undermodulopdateringen, skal du køre en git-undermodulsynkronisering.

Jeg skal for fuldstændighedens skyld nævne, at selvom git submodule update som standard tjekker den refererede SHA1, kan du ændre det til for eksempel at rebase ethvert lokalt undermodulearbejde (vi snakker om det meget snart) oven på det., Du ville gøre det ved at indstille opdateringskonfigurationsindstillingen for din undermodul til at rebase inde i din containers lokale konfiguration.

og jeg er ked af det, men nej, der er ingen lokal konfigurationsindstilling eller endda CLI-mulighed for den sags skyld, der kan automatisk opdateres ved træk. For at automatisere sådanne ting skal du bruge enten aliaser, brugerdefinerede scripts eller omhyggeligt udformede lokale kroge., Her er et eksempel spull alias (enkelt linje, split her til visning):

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

Hvis du ønsker at bevare muligheden for at overføre brugerdefinerede argumenter til git træk, kan du enten definere en funktion on-the-fly og kalder det, eller gå med et brugerdefineret script. Den første tilgang ville se sådan ud (igen, enkelt linje):

ikke meget læsbar, eh? Jeg foretrækker den brugerdefinerede script tilgang., Lad os sige, at du ville sætte et git-spull script-fil, et eller andet sted inde i din STI (jeg har ~/perso/bin mappe på min VEJ blot for sådanne ting):

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

Vi derefter give det udførelse rettigheder:

chmod +x git-spull

Og nu kan vi bruge det lige som vi ville have brugt alias.

opdatering af en undermodul på plads i beholderen

Dette er den sværeste brugssag, og du skal holde dig væk fra den så meget som muligt og foretrække vedligeholdelse gennem den centrale, dedikerede repo.,

det kan dog ske, at undermodulkode ikke kan testes eller endda kompileres uden for containerkoden. Mange temaer og plugins har sådanne begrænsninger.

den første ting at forstå er, fordi du skal gøre forpligtelser, skal du starte fra et ordentligt grundlag, hvilket vil være et grenspids. Du skal derfor kontrollere, at filialens seneste forpligtelser ikke “bryder” dit containerprojekt. Hvis de gør det, godt, at oprette din egen containerspecifik gren i undermodulet lyder fristende, men den sti fører til stærk kobling mellem undermodul og beholder, hvilket ikke er tilrådeligt., Du ønsker måske at stoppe med at “undermodulere” den kode i dette særlige projekt, og bare indlejre den som ethvert almindeligt indhold i stedet.

lad os indrømme, at du med god samvittighed kan føje til undermodulets nuværende mastergren. Lad os starte med at synkronisere vores lokale stat på fjernbetjeningen er:

en Anden måde at gøre dette på ville være, fra beholderen repo til eksplicit at synkronisere submodule ‘ s lokale afdeling i løbet af sin spores remote branch (enkelt linje på toppen, sidste — efterfulgt af blanktegn):

Vi kan nu redigere koden, gøre det arbejde, test, osv., Når vi er alle indstillet, kan vi derefter udføre de to forpligtelser og de to nødvendige pushes (det er super nemt og i praksis alt for hyppigt at glemme noget af det).

lad os blot tilføje falsk arbejde og gøre de to relaterede commits, på undermodul og container niveauer:

På dette tidspunkt er den største fare at glemme at skubbe undermodulet. Du kommer tilbage til containerprojektet, forpligter det og skubber kun containeren. Det er en nem fejl at gøre, især inde i en IDE eller GUI. Når dine kolleger forsøger at få opdateringer, bryder helvede løs., Se på det første trin:

Der er absolut ingen indikation af, at Git ikke kunne hente den refererede commit fra undermodulets fjernbetjening. Den første antydning af dette er i status:

Bemærk advarslen: tilsyneladende er den nyligt refererede commit for undermodulet intetsteds at finde. Faktisk, hvis vi forsøger at opdatere undermodulets arbejdsmappe, 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 tydeligt se, hvor vigtigt det er at huske at skubbe undermodulet også, ideelt før du skubber beholderen., Lad os gøre det i kollega og forsøge opdateringen igen:

Jeg skal bemærke, at der er en CLI — indstilling, der vil kontrollere, om der i øjeblikket refereres til submodule commits skal skubbes også, og i så fald vil skubbe dem: det er git push-recurse-submodules=on-demand (ganske mundfuld, ganske vist). Det skal dog have noget containerniveau for at skubbe til arbejde: kun undermoduler vil ikke skære det.

Hvad mere er (der er ingen konfigurationsindstilling for dette, så du bliver nødt til at standardisere procedurer omkring et alias, f.eks.,recurseSubmodules konfigurationer indstilling du kan definere (til On-demand eller check).

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

Fjernelse af en submodule

Der er to situationer hvor du gerne vil “fjerne” en submodule:

  • Du blot ønsker at klare den arbejdsmappe (måske før arkivering beholderen er WD), men ønsker at bevare muligheden for at gendanne den senere (så det er til at forblive i .gitmoduler og .Git / modules);
  • du ønsker definitivt at fjerne det fra den aktuelle gren.

lad os se hvert tilfælde igen.,

midlertidig fjernelse af en undermodul

den første situation håndteres let af git submodule deinit. Se selv:

dette har ingen indflydelse på containerstatus overhovedet. Undermodulet er ikke lokalt kendt længere (det er væk fra .git / config), så dets fravær fra arbejdsmappen går ubemærket. Vi har stadig mappen vendor/plugins/demo, men det er tomt; vi kunne strippe det uden konsekvens.undermodulet må ikke have nogen lokale ændringer, når du gør dette, ellers skal du tvinge opkaldet.,enhver senere underkommand af git-undermodulet vil saligt ignorere denne undermodul, indtil du init det igen, da undermodulet ikke engang vil være i lokal config. Sådanne kommandoer omfatter opdatering, foreach og sync.

På den anden side forbliver undermodulet defineret i .gitmodules: et init-efterfulgt af en opdatering (eller en enkelt opdatering — init) vil gendanne den som ny:

Permanent at fjerne en submodule

Dette betyder, at du ønsker at slippe af med den submodule for godt: en regelmæssig git rm vil gøre, ligesom for enhver anden del af working directory., Dette fungerer kun, hvis din undermodul bruger en gitfile (a .git, som er en fil, ikke en mappe), hvilket er tilfældet, der starter med Git 1.7.8. Ellers bliver du nødt til at håndtere dette for hånd (jeg fortæller dig hvordan i slutningen).

ud over at fjerne undermodulet fra arbejdsmappen, opdaterer kommandoen .gitmodules fil, så det ikke referere undermodulet længere. Her går du:

naturligvis Avanceret status info tur over sig selv her, fordi gitfilen til undermodulet er væk (faktisk forsvandt hele demo-mappen).,

hvad der dog er underligt, er, at den lokale konfiguration bevarer undermodulinformation, i modsætning til hvad der sker, når du deinit. Så for en omfattende fjernelse anbefaler jeg, at du gør begge dele i rækkefølge for at ende korrekt ryddet op (det ville ikke fungere efter vores tidligere kommando, fordi det ryddet .gitmodules allerede):

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

uanset din tilgang forbliver undermodulets repo til stede i .git / modules/vendor/plugins / demo, men du er fri til at dræbe det, når du vil.

Hvis du nogensinde har brug for at fjerne en undermodul, der blev oprettet før Git 1.7.,8, og derfor integrerer sin .git bibliotek direkte i containeren, der arbejder mappe (i stedet for at stole på en gitfile), vil du nødt til at bryde ud bulldozer: de to foregående kommandoer den behøver at være resultatet af en manuel mappe-fjernelse, fx rm -fr sælger/plugins/demo, fordi sagde kommandoer vil altid nægte at slette et virkeligt repository.