Git
Chapters ▾ 2nd Edition

10.6 Git на ниско ниво - Транспортни протоколи

Транспортни протоколи

Git може да обменя данни между две хранилища по два основни начина: с “dumb” протокола и със “smart” протокола. Тук ще видим накратко как работят те.

Dumb протокол

Ако настройвате хранилище само за четене през HTTP, тогава dumb протоколът е вероятния избор. Този вид протокол се нарича “dumb”, защото не изисква Git-specific код от страна на сървъра по време на транспортния процес. Процесът е серия от HTTP GET заявки, при които клиентът може да предположи какво е разположението на Git хранилището в сървъра.

Note

Този вид протокол вече се използва сравнително рядко. При него е трудно да се гарантира защитата на данните, така че повечето Git хостове (cloud-based и on-premises) ще откажат използването му. Препоръчително е да се използва smart протокола, който ще разгледаме след малко.

Нека преминем през http-fetch процеса за библиотеката simplegit:

$ git clone http://server/simplegit-progit.git

Първото нещо, което прави командата, е да изтегли файла info/refs. Този файл се записва от командата update-server-info, ето защо трябва да разрешите това като post-receive hook за да може HTTP транспорта да работи коректно:

=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949     refs/heads/master

Сега имате списък на отдалечените референции и SHA-1 стойности. След това поглеждаме към какво сочи референцията HEAD, така че да знаем какво да извлечем в работната директория, когато сме готови:

=> GET HEAD
ref: refs/heads/master

Това значи, че ще разпакетираме клона master в края. В този момент сме готови да пуснем процеса. Понеже изходната точка е къмит обекта ca82a6, който виждаме във файла info/refs, започваме с неговото изтегляне:

=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)

Получаваме обратно обект — този обект е в loose формат на сървъра и сме го изтеглили през статична HTTP GET заявка. Можете да използвате zlib за да го декомпресирате, да премахнете хедъра и да видите съдържанието на къмита:

$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
author Scott Chacon <schacon@gmail.com> 1205815931 -0700
committer Scott Chacon <schacon@gmail.com> 1240030591 -0700

changed the version number

Следва да се изтеглят още два обекта — cfda3b, което е дървото на съдържанието, към което сочи току що изтегления къмит, и родителския къмит 085bb3:

=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
(179 bytes of data)

Това ни дава следващия къмит обект. Изтегляме tree обекта:

=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
(404 - Not Found)

Получаваме отговор 404 — изглежда, че tree обектът не е в loose формат на сървъра. За това може да има различни причини — обектът може да е в алтернативно хранилище или пък може да е в packfile в текущото. Git първо проверява за евентуални заместители:

=> GET objects/info/http-alternates
(empty file)

Ако получим списък от алтернативни URL-и, Git проверява за loose файлове и packfile файлове в тях — това е чудесен начин за проекти, които са forks на други да споделят обекти на диска. Само че, в този случай нямаме такъв списък, така че обектът трябва да е пакетиран в packfile. За да видим какви packfiles са налични на сървъра трябва да вземем файла objects/info/packs, който съдържа списък с тях (генерира се също от update-server-info):

=> GET objects/info/packs
P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack

Има само един packfile на сървъра, така че обектът очевидно е там, но все пак проверяваме и в индекса за да се уверим в това. Такава проверка е полезна и ако имате повече packfiles на сървъра за да намерите кой точно от тях съдържа търсения обект:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k of binary data)

Сега имаме packfile индекса и можем да видим дали обектът ни е посочен в него, индексът съдържа списък с SHA-1 чексумите и позициите (отместванията) на обектите от packfile файловете. Обектът ни е тук и следва да изтеглим целия packfile:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)

Получихме tree обекта и можем да продължим стъпките през къмитите. Те също са в packfile пакета, който току що изтеглихме, така че не се налага да правим повече заявки към сървъра. Git извлича работно копие от клона master, към който сочеше HEAD референцията изтеглена в началото.

Протоколът Smart

Протоколът dumb е прост, но не много ефективен и освен това не може да обслужва изпращането на данни от клиента към сървъра. Протоколът smart е много по-подходящ за трансфер на данни, но изисква процес в отдалечената страна на връзката, който да е запознат с Git — той трябва да може да прочита локалните данни, да определи какво има и от какво се нуждае клиента и да му генерира custom packfile като резултат. Съществуват две множества процеси за трансфер на данни: чифт за изпращане и още един чифт за приемане.

Изпращане на данни

За да изпраща данни към отдалечен процес, Git използва процесите send-pack и receive-pack. Процесът send-pack, очаквано, работи от страна на клиента и се свързва с receive-pack процеса в другия край на връзката.

SSH

За пример, да кажем, че изпълнявате git push origin master в проекта си и origin е дефинирана като URL, за който се използва SSH протокола. Git стартира send-pack процеса, който инициира конекция към сървъра през SSH. Този процес опитва да изпълни команда на сървъра през SSH повикване, което изглежда по подобен начин:

$ ssh -x git@server "git-receive-pack 'simplegit-progit.git'"
00a5ca82a6dff817ec66f4437202690a93763949 refs/heads/master□report-status \
	delete-refs side-band-64k quiet ofs-delta \
	agent=git/2:2.1.1+github-607-gfba4028 delete-refs
0000

Командата git-receive-pack незабавно отговаря с по един ред за всяка референция, която има — в този случай само клона master и неговата SHA-1 чексума. Първият ред също така подава списък с поддържаните от сървъра възможности (в случая report-status, delete-refs, и някои други, вкл. идентификация на клиента).

Всеки ред започва с 4-символна шестнайсетична стойност, която указва колко е дълъг остатъка от реда. Първият ред започва с 00a5, което десетично е 165 и означава, че на този ред остават 165 байта. Следващият ред започва с 0000, което индикира, че сървърът няма какви други референции да показва.

След като вече знае статуса на сървъра, процесът send-pack определя локалните къмити, които не присъстват на него. За всяка референция, която това публикуване ще актуализира, процесът send-pack подава на receive-pack съответната информация. Например, ако обновявате master клона и добавяте клон experiment, send-pack отговорът може да изглежда така:

0076ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 \
	refs/heads/master report-status
006c0000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d \
	refs/heads/experiment
0000

Git изпраща по ред за всяка референция, която обновява съдържащ дължината на реда, старата SHA-1 стойност, новата SHA-1 стойност и името на съответната референция. Може да видите, че първият ред също така подава като информация поддържаните от клиента функционалности (report status). SHA-1 стойността състояща се само от нули означава, че там преди не е имало нищо — понеже добавяте липсващата до момента референция experiment. Ако изтривате референция, ще видите обратното, всички нули ще са в дясната SHA-1 чексума.

След това клиентът изпраща packfile на всички обекти, които сървърът няма. Последно, сървърът отговаря с индикация за успех или грешка:

000eunpack ok
HTTP(S)

Този процес в голямата си част е същия като HTTP, въпреки че handshaking механизмът е по-различен. Връзката се инициира с тази заявка:

=> GET http://server/simplegit-progit.git/info/refs?service=git-receive-pack
001f# service=git-receive-pack
00ab6c5f0e45abd7832bf23074a333f739977c9e8188 refs/heads/master□report-status \
	delete-refs side-band-64k quiet ofs-delta \
	agent=git/2:2.1.1~vmg-bitmaps-bugaloo-608-g116744e
0000

Това е краят на първата client-server обмяна. Клиентът прави още една заявка, този път по метода POST с данните, които send-pack доставя.

=> POST http://server/simplegit-progit.git/git-receive-pack

Заявката POST включва изхода от send-pack и самия packfile. Сървът след това отговаря с резултата за успех или отказ.

Изтегляне на данни

Когато теглите данни, участват процесите fetch-pack и upload-pack. Клиентът инициира fetch-pack процес, който се свързва с upload-pack такъв в другия край на връзката за да уговори какви данни ще се изтеглят.

SSH

Ако теглите през SSH, fetch-pack изпълнява нещо подобно:

$ ssh -x git@server "git-upload-pack 'simplegit-progit.git'"

След като fetch-pack се свърже, upload-pack изпраща обратно нещо такова:

00dfca82a6dff817ec66f44342007202690a93763949 HEAD□multi_ack thin-pack \
	side-band side-band-64k ofs-delta shallow no-progress include-tag \
	multi_ack_detailed symref=HEAD:refs/heads/master \
	agent=git/2:2.1.1+github-607-gfba4028
003fe2409a098dc3e53539a9028a94b6224db9d6a6b6 refs/heads/master
0000

Това е много подобно на отговора, който връща receive-pack, но функционалностите се различават. Процесът връща в добавка къде сочи HEAD (symref=HEAD:refs/heads/master), така че клиентът да знае какво да разпакетира, ако това е клониране.

На този етап процесът fetch-pack проверява какви обекти има и отговаря с обектите, които му трябват изпращайки “want” и след това SHA-1 стойностите, които иска. Той изпраща с “have” обектите, които има и техните SHA-1 стойности. В края на този списък той подава “done” за да инструктира upload-pack да започне изпращането на packfile пакета данни, който се търси:

003cwant ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0009done
0000
HTTP(S)

Handshake механизмът за fetch операцията използва две HTTP заявки. Първата е GET към същия endpoint използван при dumb протокола:

=> GET $GIT_URL/info/refs?service=git-upload-pack
001e# service=git-upload-pack
00e7ca82a6dff817ec66f44342007202690a93763949 HEAD□multi_ack thin-pack \
	side-band side-band-64k ofs-delta shallow no-progress include-tag \
	multi_ack_detailed no-done symref=HEAD:refs/heads/master \
	agent=git/2:2.1.1+github-607-gfba4028
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
0000

Това е много подобно на повикване към git-upload-pack през SSH връзка, но втората размяна на данни се изпълнява като отделна заявка:

=> POST $GIT_URL/git-upload-pack HTTP/1.0
0032want 0a53e9ddeaddad63ad106860237bbf53411d11a7
0032have 441b40d833fdfa93eb2908e52742248faf0ee993
0000

Отново, това е същия формат като по-горе. Отговорът на тази заявка индикира успех или отказ и включва данните в packfile.

Обобщение

Тази секция съдържа много съкратен преглед на транспортните протоколи. Протоколът включва и много други възможности като multi_ack или side-band поддръжка, но те са извън темата за книгата. Опитахме да опишем основната идея в механизмите на комуникацията между клиент и сървър, ако се нуждаете от повече подробности, може да разгледате сорс кода на Git.