Git
Chapters ▾ 2nd Edition

10.6 Гит изнутра - Протоколи за пренос

Протоколи за пренос

Програм Гит може да преноси податке између два репозиторијума на два главна начина: „приглуп” протокол и „паметни” протокол. Овај одељак ће укратко представити начин на који функционишу ови главни протоколи.

Приглуп протокол

Ако постављате репозиторијум који ће да се сервира само-за-читање преко HTTP, највероватније ћете употребити приглуп протокол. Овај протокол се назива „приглуп” јер током процеса преноса на серверској страни није потребан никакав Гит специфични кôд; процес преузимања је низ HTTP GET захтева, а клијент може да претпостави распоред Гит репозиторијума на серверу.

Белешка

У данашње време се приглуп протокол прилично ретко користи. Не може лако да се обезбеди или учини приватним, тако да ће већина Гит хостова (и оних базираних на облаку и оних унутар фирми) одбити да га користи. У општем случају се саветује да се користи паметни протокол који ћемо описати мало касније.

Хајде да испратимо http-fetch процес за simplegit библиотеку:

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

Прва ствар коју ова команда ради је да повуче info/refs фајл. Овај фајл исписује команда update-server-info и то је разлог због којег то морате да укључите као post-receive куку да би 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)

Враћа вам се објекат – тај објекат је слободан на серверу, а преузели сте га статичким 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

Change version number

Затим, постоји још два објекта које треба да преузмете – cfda3b, што је стабло садржаја на који управо преузети комит указује; и 085bb3, што је родитељ комит:

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

Овим добијате свој наредни комит објекат. Дохватите објекат стабла:

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

Уупс – изгледа да објекат стабла није слободан на серверу, па вам се враћа 404 одговор. За ово постоји неколико разлога – објекат би могао да се налази у алтернативном репозиторијуму, или би могао да буде унутар pack фајла у овом репозиторијуму. Програм Гит најпре проверава постоји ли наведена било која алтернатива:

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

Ако ово буде листа алтернативних URL адреса, програм Гит тамо проверава слободне фајлове и pack фајлове – ово је одличан механизам да пројекти који су рачве један другог деле објекте на диску. Међутим, пошто у овом случају нису наведене никакве алтернативе, ваш објекат мора да се налази у pack фајлу. Да бисте видели постојеће pack фајлове на овом серверу, морате да преузмете фајл objects/info/packs који садржи списак ових фајлова (такође га генерише команда update-server-info):

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

Постоји само један pack фајл на серверу, тако да се ваш објекат очигледно тамо налази, али ипак ћете проверити индекс фајл чисто да будете сигурни. Ово је такође корисно ако на серверу постоји више pack фајлова, тако да видите који pack фајл садржи објекат који вам је потребан:

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

Сада када имате индекс pack фајла, можете видети да ли се у њему налази ваш објекат – пошто индекс наводи SHA-1 суме објеката који се налазе у pack фајлу и помераје до тих објеката. Ваш објекат се тамо налази, па хајде да преузмемо комплетан pack фајл:

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

Дошли сте до вашег објекта стабла, па настављате да се крећете кроз комитове. И они се налазе унутар pack фајла који сте управо преузели, тако да не морате да шаљете још захтева серверу. Програм Гит одјављује радну копију master гране на коју указује HEAD референца коју се преузели на почетку.

Паметни протокол

Приглуп протокол је једноставан али није довољно ефикасан и не подржава упис података са клијента на сервер. Паметни протокол је метод за пренос података који се обично користи, али на удаљеној страни је неопходан процес који је интелигентан у вези програма Гит – он може да чита локалне податке, одреди шта клијент има и шта му је потребно, па да за то генерише прилагођени pack фајл. Постоје два скупа процеса за пренос података: пар за слање података на сервер и пар за преузимање података са сервера.

Слање података на сервер

Када треба да пошаље податке на сервер, програм Гит користи send-pack и receive-pack процесе. Процес send-pack се извршава на клијенту и повезује се са receive-pack процесом на удаљеној страни.

SSH

На пример, рецимо да у свом пројекту извршите git push origin master, а да је origin дефинисано као URL адреса која користи SSH протокол. Програм Гит покреће 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 карактера која наводи дужину остатка линије. Ваша прва линија почиње са 005b, што је хексадецимална представа броја 91, па значи да на тој линији преостаје још 91 бајт. Наредна линија почиње са 003e, што је 62, тако да читате преосталих 62 бајтова. Наредна линија је 0000, што значи да је сервер завршио са листањем референци.

Сада када зна стање сервера, ваш send-pack процес одређује које комитове поседује, а још нису на серверу. За сваку референцу коју ће ово гурање да ажурира, процес send-pack преноси процесу receive-pack ту информацију. На пример, ако ажурирате master грану и додајете experiment грану, send-pack одговор може изгледати отприлике овако:

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

Програм Гит шаље по линију за сваку референцу коју ажурирате у којој се налази дужина линије, стара SHA-1 сума, нова SHA-1 сума и референца која се ажурира. У првој линије се налазе и могућности клијента. Ако се SHA-1 вредност састоји само од нула, то значи да раније није било ничега – јер додајете експеримент референцу. Ако бришете референцу, видели бисте супротно: све нуле са десне стране.

Затим, клијент шаље pack фајл свих објеката које сервер још увек нема. Коначно, сервер одговара тако што означава успех (или неуспех):

000eunpack ok
HTTP(S)

Овај процес је углавном исти као и преко HTTP, мада је руковање донекле другачије. Успостављање везе се започиње следећим захтевом:

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

То је крај прве клијент-сервер размене. Клијент затим шаље наредни захтев, овога пута POST, са подацима које достави git-upload-pack.

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

У товару POST захтева се налази излаз из send-pack и pack фајл. Сервер затим својим HTTP одговором означава успех или неуспех.

Имајте на уму да HTTP протокол ове податке може додатно да обмота у пренос по комадима.

Преузимање података са сервера

Када преузимате податке, укључени су 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 да започне слање pack фајла са подацима који су потребни:

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

Руковање за операцију преузимања подразумева два HTTP захтева. Први је GET исте крајње тачке која се користи и у приглупом протоколу:

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

Још једном, ово је исти формат као и раније. Одговор на овај захтев означава успех или неуспех и садржи pack фајл.

Резиме протокола

Овај одељак садржи доста упрошћени преглед протокола за пренос. Протокол има још доста других могућности, као што су multi_ack или side-band могућности, али је њихово разматрање ван оквира ове књиге. Покушали смо да вам представимо општу размену између клијента и сервера; ако желите више знања о овоме, вероватно би требало да погледате у изворни кôд програма Гит.