Articles

Zvládnutí Git submoduly

Submoduly, krok za krokem

My se budeme nyní zkoumat každý krok pomocí dílčích modulů ve společný projekt, ujistěte se, jsme zvýraznit výchozí chování, pasti a je k dispozici vylepšení.

abych usnadnil vaše sledování, sestavil jsem několik příkladů repo s jejich „dálkovými ovladači“ (ve skutečnosti jen adresáře)., Můžete rozbalit archiv kdekoli budete chtít, a pak otevřete shell (nebo Git Bash, pokud jste na Windows), v git-subs adresáře vytváří:

Stáhněte si vzor repos

najdete tři adresáře tam:

  • hlavní se chová jako kontejner repo, místní na první spolupracovník,
  • plugin působí jako centrální údržby repo pro modul, a
  • dálkové ovladače obsahuje souborový systém, dálkové ovládání pro dvou předchozích repo operace.

v níže uvedených příkladových příkazech se vždy zobrazí výzva, do které repo jsme.,

přidání submodule

začněme přidáním našeho pluginu jako submodule uvnitř našeho kontejneru(který je hlavní). Samotný plugin má jednoduchou strukturu:

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

takže pojďme do main a použijte příkaz git submodule add. Trvá URL dálkového ovladače a podadresář, ve kterém se „instanciate“ submodule.

Protože používáme cesty, místo Url zde pro naše dálkové ovladače, jsme narazili na divně, i když dobře známý, zádrhel: relativní cesty pro dálkové ovladače jsou interpretovány vzhledem k naší hlavní dálkové, ne na naše repo je kořenový adresář., To je super divné, nikde není popsáno, ale viděl jsem, že se to stalo pokaždé. Takže místo toho, abych to řekl ../ dálkové ovladače / plugin, jen říkáme ../modul.

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

přidána některá nastavení v naší místní konfigurace:

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


url = ../remotes/plugin

A tohle také představil dva soubory:

Co?! Co to je .soubor gitmodules? Podívejme se na to:

main (master + u=) $ cat .gitmodules

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

to zuřivě připomíná naši místní konfiguraci… tak proč duplikace? No, právě proto, že naše místní konfigurace je … místní., Naši spolupracovníci nebudou vidět (což je naprosto normální), takže potřebují mechanismus, aby definice všech dílčích modulů, které je třeba nastavit v jejich vlastní repo. To je to, co .gitmodules je pro; to bude číst později příkazem git submodule init, jak uvidíme za chvíli.

Když jsme ve stavu, všimněte si, jak minimalistický je, pokud jde o náš submodul: jde jen o příliš obecný nový soubor, místo aby nám řekl více o tom, co se děje uvnitř., Naše submodul byl skutečně aplikován v podadresáři:


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

Stav, jako protokoly a diffy, je omezena na aktivní repo (teď, kontejner), nikoliv na dílčích modulů, které jsou vnořené repo operace. To je často problematické (to je super snadné přehlédnout regrese při omezení tohoto názoru), takže doporučuji si nastavit submodul-vědomý stav, jednou a pro všechny:

git config --global status.submoduleSummary true

A teď:

Aaaah, tento je mnohem lepší., Status vyjadřuje základní informace dodat, že submodul přítomen na vendor/plugins/demo mám 3 zprávy zavazuje v (jak jsme právě vytvořili, to znamená, vzdálené pobočky měl jen tři zavazuje), poslední přírůstek (poznámka: právo-polohovací úhel držáku >) s první zprávu odevzdání řádek, který čte „Opravit repo jméno…“.

abychom skutečně přinesli domů, že se zde zabýváme dvěma samostatnými repo, pojďme se dostat do adresáře submodule:

aktivní repo se změnilo, protože nový .,git přebírá: v aktuálním adresáři (demo, adresář submodule), a .git skutečně existuje, jeden soubor příliš, není adresář. Podívejme se dovnitř:

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

znovu, protože git 1.7.8, git nezanechává adresáře repo uvnitř pracovního adresáře kontejneru, ale centralizuje je v kontejneru .git directory (uvnitř .git / modules), a používá odkaz gitdir v submodulech.,

důvodem pro to je jednoduchý: umožňuje kontejner repo mít submodul-méně poboček, aniž by museli zrušit submodulu je repo z pracovního adresáře a obnovit jej později.

při přidávání podnabídky se samozřejmě můžete rozhodnout použít konkrétní větev nebo dokonce konkrétní commit pomocí volby-b CLI (jako obvykle je výchozí hodnota master). Všimněte si, že nejsme, právě teď, na oddělené hlavě, na rozdíl od toho, co se stane později: je to proto, že git odhlásil mistra, ne konkrétní SHA1. Museli bychom specifikovat SHA1 to-b, abychom získali oddělenou hlavu od začátku.,

Takže, zpět do nádoby repo, a pojďme dokončit submodulu je sčítání a tlačit, aby do dálkového:

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

Popadl repo, který používá submoduly

pro ilustraci problémy s spolupráce na repo, které používá submoduly, rozdělíme osobnosti a působit jako náš kolega, který klony kontejneru vzdáleného začít pracovat s námi. Klonujeme to v adresáři kolegů, abychom mohli okamžitě zjistit,na které osobnosti máme v daném okamžiku.,

první věc, kterou všimnete, je, že naše submodul chybí z pracovního adresáře; pouze jeho základní adresář je tady:

vendor
└── plugins
└── demo

Jak se to stalo? To je prostě způsobeno skutečností, že náš nový repo (kolega) zatím neví o našem submodulu: informace o něm nejsou nikde v místní konfiguraci (zkontrolujte jeho .git / config, pokud mi nevěříte). Budeme to muset vyplnit podle toho, co .gitmodules musí říci, což je přesně to, co git submodule init dělá:

naše .git / config si je nyní vědom našeho submodulu., Nicméně, stále ještě není přitažené za vlasy ji od svého vzdáleného, nic říct, že je přítomen v našem pracovním adresáři. A přesto se náš stav ukazuje jako čistý!

viz, musíme příslušné revize uchopit ručně. Není to něco, co náš původní klon udělal, musíme to udělat na každém tahu. Vrátíme se k tomu za minutu, protože toto je klon chování, který může skutečně automatizovat, když je správně volán.,

V praxi při jednání s submodul-s použitím repo operace, obvykle jsme skupina dvou příkazů (init a aktualizovat) v jednom:

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

To je ještě škoda, že Git má tohle všechno uděláte sami. Jen si představte, že na větších FLOSS projektech, když submoduly mají své vlastní submoduly, a tak dále a tak dále … to by se rychle stalo noční můrou.

stává se tak, že Git poskytuje možnost CLI pro klonování automaticky git submodule update — init rekurzivně hned po klonování: spíše příhodně pojmenovaná-rekurzivní volba.,

takže zkusme celou věc znovu:

teď je to lepší! Všimněte si, že jsme teď na oddělenou hlavu uvnitř submodulu (jak budeme od teď):

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

Podívejte se na dvojité složené závorky na můj dotaz, namísto jednotného souboru?, Pokud váš řádek není nakonfigurován jako je ta moje, se zobrazí samostatná budova hlavu, jak popisuje (s Git je postaven-v řádku skriptu, musel bys definovat GIT_PS1_DESCRIBE_STYLE=větev, proměnné prostředí), budete raději viděl něco jako toto:

demo ((fe64799...)) $

V každém případě, status potvrzuje, že jsme tam kde jsme:

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

aktualizace z submodulu je vzdálený

OK, teď, že máme vlastní repo (hlavní) a naše „kolegy“ (kolega) všechno připraveno spolupracovat, pojďme se krok do boty třetí osobě: ten, kdo udržuje plugin., Tady, pojďme na to:

Nyní pojďme přidat dva pseudo-dopouští a tyto zveřejňovat na dálkové ovládání:

a Konečně, pojďme dát naše „první developer“ cap znovu:

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

Předpokládejme, že nyní chcete, aby se tyto dvě zavazuje uvnitř submodulu. Abychom toho dosáhli, musíme aktualizovat místní repo, počínaje přesunem do pracovního adresáře, aby se stalo naším aktivním repo.

na boční poznámce bych nedoporučoval používat pull pro tento druh aktualizace., Správně získat aktualizace v pracovním adresáři, tento příkaz vyžaduje, že jste na správné aktivní pobočky, které obvykle nejsou (ty jsi na oddělenou hlavu většinu času). Museli byste začít s pokladnou té pobočky. Ale co je důležitější, vzdálené pobočce může velmi dobře mít se stěhoval dál od spáchání, kterou chcete nastavit, a vytáhnout by se aplikovat zavazuje nechcete ve vaší místní codebase.,

proto doporučuji proces rozdělit ručně: nejprve git fetch získat všechna nová data z dálkového ovladače v místní mezipaměti, pak se přihlásit k ověření toho, co máte, a pokladna na požadovaném SHA1. Kromě toho, aby jemnější ovládání, tento přístup má výhodu práce bez ohledu na váš současný stav (aktivní pobočky nebo detašované hlavy).

OK, takže jsme dobří, žádné cizí odevzdání., Být to jak to může, ať je explicitně nastavit na jeden jsme zájem (zřejmě máte jiný SHA1):

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

(- q, je tam jen ušetři nás Git keců o tom, jak jsme skončili na oddělenou hlavu. Obvykle by to bylo zdravé připomenutí, ale na tomto víme, co děláme.)

Nyní, že naše submodulu je aktualizován, můžeme vidět výsledek v kontejneru repo stav:

V „klasické“ části stav vidíme novou zavazuje změnit typ, což znamená, že odkazovaný zavázat se změnil., Další možností (která by mohla být umocněn je tento) je nový obsah, což by znamenalo, že jsme udělali místní změny submodulu je pracovní adresář.

spodní část, povolená naším stavem.submoduleSummary = true nastavení dříve, výslovně uvádí, zavedené commitů (jako používají vpravo ukazující úhel držáku >) od naší poslední kontejner se zavazují, že se dotkl submodulu.

v rodině „hrozné výchozí chování“, git diff ponechává hodně být požadovaný:

Co to — ?, Tam je CLI možnost, která nám umožňuje vidět něco víc užitečné:

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

nejsou žádné jiné místní změny teď kromě submodulu je odkazováno spáchat… Všimněte si, toto odpovídá téměř přesně spodní část z naší rozšířené git status zobrazí.

to, že musíte pokaždé zadat takovou možnost CLI (která se mimochodem nezobrazuje v aktuálních nabídkách dokončení Git), je poněkud těžkopádné. Naštěstí existuje odpovídající nastavení konfigurace:

nyní stačí provést odevzdání kontejneru, které dokončí aktualizaci našeho submodulu., Pokud jste se museli dotknout kódu kontejneru, aby to fungovalo s touto aktualizací, zavázat ji, přirozeně. Na druhou stranu, vyhnout se míchání submodul-související změny a další věci, které by se právě týkají kontejner kód: úhledně odděluje dvě, později stěhování do jiné kód-opakované přístupy jsou jednodušší (také, jako obvykle, atomové zavazuje FTW).

Jak jsme o tom, aby se chytit tento submodul aktualizovat v naší kolegyně repo, budeme tlačit přímo po spáchání (což není obecně dobré praxe).

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

tahání submodule-pomocí repo

klikněte!, „Kolega“ čepice na!

Tak jsme tahat aktualizace z nádoby repo remote…

(nemusí mít „Úspěšně rebased a aktualizovány…“ a viz „Sloučit provedené ‚rekurzivní‘ strategie“ místo. Pokud ano, moje srdce vám vyjde a měli byste se okamžitě dozvědět,proč by se měl tahat).

Všimněte si druhé poloviny tohoto zobrazení: jedná se o submodul, počínaje „načtením submodule…“.

toto chování se stalo výchozím s GIT 1.7.5, s konfiguračním nastavením načíst.,recurseSubmodules nyní výchozí on-demand: pokud projekt kontejneru dostane aktualizace odkazovaných submodule zavazuje, tyto submoduly se automaticky načte. (Pamatujte, že načítání je první částí tahání.)

stále, a to je kritické: git auto-načte, ale ne automaticky aktualizovat. Místní mezipaměť je aktuální s dálkovým ovladačem submodule, ale pracovní adresář submodule je přilepený k jeho bývalému obsahu. Alespoň, můžete vypnout ten notebook, hop na letadlo, a stále dopředu jednou offline., Přestože je toto automatické načítání omezeno na již známé submoduly: žádné nové, dosud nekopírované do místní konfigurace, nejsou automaticky načteny.

git auto-načte, ale ne automatické aktualizace.

aktuální řádek s hvězdičkou (*), nemá náznak na místní změny, protože naše společnost WD není v synchronizaci s, index, druhý byl vědom nově odkazuje submodul zavazuje. Podívejte se na stav:

Všimněte si, jak Úhlové závorky směřují doleva (<)?, Git vidí, že současná WD nemá tyto dvě commity, což je v rozporu s očekáváním projektu kontejneru.

toto je obrovské nebezpečí: pokud výslovně neaktualizujete pracovní adresář submodule, vaše další odevzdání kontejneru regresí submodul. Tohle je past prvního řádu.

Je proto nutné, aby dokončit aktualizace:

tak dlouho, Jak jsme se snaží vytvořit obecné dobré návyky, přednostní velení by být git submodul aktualizace — init — rekurzivní, aby se auto-init žádné nové submodul, a rekurzivně aktualizovat, pokud je třeba.,

existuje další případ edge: pokud se vzdálená adresa URL submodule změnila od posledního použití (možná ji jeden ze spolupracovníků změnil v.gitmodules), musíte ručně aktualizovat místní konfiguraci tak, aby to odpovídalo. V takové situaci, před aktualizací submodule git, budete muset spustit submodulovou synchronizaci git.

měl bych zmínit, že pro úplnost, že i když git submodul aktualizace výchozí kontrola odkazovaných SHA1, můžete změnit, že, například, rebase místní submodul práce (budeme mluvit o tom, že velmi brzy) na vrchol., To byste udělali nastavením Nastavení Konfigurace aktualizace pro váš submodul pro rebase uvnitř místní konfigurace kontejneru.

a je mi líto, ale ne, neexistuje žádné místní nastavení konfigurace, nebo dokonce možnost CLI, která může automaticky aktualizovat na pull. Chcete-li takové věci automatizovat, musíte použít aliasy, vlastní skripty nebo pečlivě vytvořené místní háčky., Zde je příklad spull alias (jeden řádek, rozdělí se zde pro zobrazení):

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

Pokud chcete zachovat schopnost předávat vlastní argumenty git pull, můžete buď definovat funkci on-the-fly a zavolat, nebo jít s vlastní skript. První přístup by vypadal takto (opět jeden řádek):

není příliš čitelný, eh? Preferuji vlastní přístup skriptu., Řekněme, že bych dal git-spull souboru skriptu někde uvnitř vaší CESTĚ (mám ~/osoba/bin adresáře v CESTĚ jen pro takové věci):

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

pak Jsme dát je realizace práv:

chmod +x git-spull

A teď můžeme použít stejně, jako bychom použili alias.

Aktualizace submodul na místě v kontejneru

Toto je nejtěžší použití-případ, a měl bys zůstat pryč od ní, stejně jako je to možné, raději údržby prostřednictvím ústřední, specializované repo.,

může se však stát, že kód submodule nelze testovat nebo dokonce kompilovat mimo kód kontejneru. Mnoho témat a pluginů má taková omezení.

první věc, kterou je třeba pochopit, je, že protože budete provádět commity, musíte začít od správného základu, což bude špička větve. Proto je třeba ověřit, že poslední revize větve váš Kontejnerový projekt „nezlomí“. Pokud ano, dobře, vytvořit si vlastní kontejner-konkrétní pobočku v submodulu zní lákavě, ale tato cesta vede k silné spojení mezi submodul a nádoby, což není vhodné., Možná budete chtít zastavit „submodulování“ tohoto kódu v tomto konkrétním projektu a místo toho jej vložit jako jakýkoli běžný obsah.

přiznejme, že můžete s dobrým svědomím přidat do současné hlavní větve submodule. Začněme tím, synchronizaci našich místních státu na dálkové ovládání:

Další způsob, jak jít o to, by bylo, z nádoby repo, explicitně synchronizace submodulu je místní větev nad jeho sledované odlehlé pobočky (jeden řádek nahoře, poslední — doplněno mezerami):

nyní můžeme upravit kód, aby to fungovalo, test, atd., Jakmile máme vše nastaveno, můžeme provést dvěma zavazuje a dva potřeby tlačí (to je super snadné, a v praxi příliš časté, aby se zapomenout, že).

jednoduše přidat falešné pracovat a dělat dvě týkající se zavazuje, v submodulu a nádoby úrovních:

V tomto bodě, hlavní nebezpečí je zapomenout, aby se zasadila submodulu. Vrátíte se k projektu kontejneru, odevzdáte ho a pouze zatlačíte kontejner. Je to snadná chyba, zejména uvnitř IDE nebo GUI. Když se vaši kolegové snaží získat aktualizace, všechno peklo se uvolní., Podívejte se na první krok:

neexistuje absolutně žádný náznak, že by Git nemohl načíst odkazované odevzdání z dálkového ovladače submodule. První náznak je ve stavu:

Všimněte si varování: zdá se, že nově odkazovaný commit pro submodul není nikde nalezen. Skutečně, pokud se pokusíme o aktualizaci submodulu je pracovní adresář, dostaneme:

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

můžete jasně vidět, jak důležité je pamatovat si, tlačí submodul moc, v ideálním případě před tlačí kontejner., Pojďme dělat, že ve kolegyně a pokus o aktualizaci znovu:

musím poznamenat, že je CLI možnost, že bude ověřit, zda v současné době odkazuje submodul zavazuje musí být tlačil příliš, a pokud ano, bude tlačit je: je to git push — recurse-submoduly=na vyžádání (docela sousto, uznávám). Musí ale mít něco, co by se dalo do práce prosadit: pouze submoduly to neřežou.

a co víc, (neexistuje žádné nastavení konfigurace, takže byste museli standardizovat postupy kolem aliasu, např. spush:) – počínaje Git 2.7.0, je nyní push.,recurseSubmodules nastavení konfigurace můžete definovat (na vyžádání nebo zkontrolovat).

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

Odstranění submodul

Existují dvě situace, kdy budete chtít, aby „odstranit“ submodulu:

  • Si jen chcete vymazat pracovní adresář (možná před archivační kontejner je WD), ale chci zachovat možnost obnovení to později (tak to má zůstat .gitmoduly a .git / modules);
  • chcete jej definitivně odstranit z aktuální větve.

podívejme se na každý případ.,

Dočasně odstranění submodul

první situace je snadno manipulovat pomocí git submodul deinit. Přesvědčte se sami:

to nemá žádný vliv na stav kontejneru vůbec. Submodul již není místně znám (je pryč .git / config), takže jeho nepřítomnost v pracovním adresáři zůstává bez povšimnutí. Stále máme adresář dodavatele/pluginy/demo, ale je prázdný; mohli bychom ho bez následků odstranit.

submodul nesmí mít žádné místní úpravy, pokud tak učiníte, jinak byste museli — vynutit hovor.,

jakýkoli pozdější submodul git bude tento submodul blaženě ignorovat, dokud jej znovu nevezmete, protože submodul nebude ani v místní konfiguraci. Takové příkazy zahrnují aktualizaci, prokaždé a synchronizace.

na druhé straně zůstává submodul definován .gitmodules: init následuje aktualizaci (nebo jeden update — init), bude to obnovit jako nové:

Trvale odstranění submodul

To znamená, že chcete zbavit submodul pro dobrý: pravidelné git rm, bude dělat, stejně jako pro všechny ostatní části pracovní adresář., To bude fungovat pouze v případě, že vaše submodule používá gitfile (a .git, což je soubor, nikoli adresář), což je případ začínající na Git 1.7.8. Jinak to budete muset zvládnout ručně (řeknu vám, jak na konci).

kromě odstranění submodulu z pracovního adresáře bude příkaz Aktualizovat .gitmodules soubor, takže to není odkaz na submodule už. Tady to je:

přirozeně, advanced status info trip nad sebou, protože gitfile pro submodule je pryč(ve skutečnosti, celý demo adresář zmizel).,

Co je však zvláštní, je to, že místní config uchovává submodulové informace, na rozdíl od toho, co se stane, když deinit. Takže, pro komplexní odstranění, doporučuji dělat obojí, a to v pořadí, tak skončí řádně vyčistit (to nebude fungovat po naší předchozí příkaz, protože se vyjasnilo .gitmodules už):

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

bez Ohledu na váš přístup, submodul je repo zůstává přítomen .git / modules / vendor / plugins / demo, ale můžete to zabít, kdykoli budete chtít.

Pokud potřebujete odstranit submodul, který byl nastaven před Git 1.7.,8, a proto vloží jeho .git adresář přímo v nádobě je pracovní adresář (místo spoléhání se na gitfile), budete se muset odtud dostat buldozer: předchozí dva příkazy musí předcházet ruční odstranění složky, např. rm -fr vendor/plugins/demo, protože řekl, že příkazy bude vždy odmítnout chcete-li odstranit vlastní úložiště.