Git
Chapters ▾ 2nd Edition

10.6 Git зсередини - Протоколи передачі

Протоколи передачі

Git може передавати дані між двома репозиторіями двома головними способами: “тупим” протоколом та “розумним”. У цій секції швидко розглянемо, як ці два головні протоколи працюють.

Тупий протокол

Якщо ви налаштовуєте репозиторій, щоб він був доступним лише для читання через HTTP, то, напевно, ви використаєте тупий протокол. Цей протокол називається “тупим”, оскільки він не вимагає жодного специфічного для Git коду з боку сервера впродовж процесу передачі; процес отримання даних — це просто низка HTTP запитів GET, в яких клієнти можуть припустити, як розташовано репозиторій Git на сервері.

Note

Нині тупий протокол використовується доволі зрідка. Його важко зробити безпечним чи приватним, отже більшість серверів розгортання Git (як хмарних, як і особистих) відмовляються ним користуватись. Зазвичай варто використовувати розумний протокол, який буде описано трохи далі.

Прослідкуймо за процесом 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

changed the version number

Далі, вам треба отримати ще два об’єкти – cfda3b, який є деревом вмісту, на який вказує щойно отриманий коміт; а також 085bb3, який є батьківським комітом:

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

Це надає нам об’єкт наступного коміту. Хапайте об’єкт дерева:

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

Овва – здається, цього об’єкта дерева немає у вільному форматі на сервері, тому ви отримали відповідь 404. Є декілька причин для цього – об’єкт може бути в альтернативному сховищі, або він може бути у файлі пакунку цього репозиторія. Git перевіряє спочатку задані альтернативи:

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

Якщо це поверне список альтернативних URL, Git перевірить вільні файли та пакунки там – це зручний засіб для проектів, які є форками інших, щоб спільно користуватись об’єктами на диску. Втім, оскільки в даному випадку жодної альтернативи не зазначено, ваш об’єкт має бути у файлі пакунків. Щоб побачити, чи існує файл пакунків на цьому сервері, вам треба отримати файл objects/info/packs, який містить їх список (також згенерований командою update-server-info):

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

Існує лише один файл пакунок на сервері, отже, очевидно, ваш об’єкт знаходиться там, проте ви перевірите файл індексу, щоб переконатись. Це також корисно, якщо у вас декілька файлів пакунків на сервері, щоб ви могли побачити, який з них містить потрібний об’єкт:

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

Тепер, коли у вас є індекс пакунку, ви можете перевірити, чи є там ваш об’єкт – адже індекс надає список SHA-1 сум об’єктів, які містяться в пакунку, та зсуви до них. Ваш об’єкт там, отже уперед отримувати весь пакунок:

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

У вас є об’єкт дерева, отже ви продовжуєте обходити ваші коміти. Вони всі також у щойно завантаженому пакунку, отже вам не доводиться більше робити запитів до сервера. Git створює робочу копію гілки master, на яку вказувало посилання HEAD, яке ви завантажили спочатку.

Розумний протокол

Тупий протокол простий, проте нефективний, та не може писати дані клієнта до сервера. Розумний протокол є більш поширеним методом передачі даних, проте вимагає, щоб на віддаленому сервері був процес, який знає про Git – він може читати локальні дані, зрозуміти, що потрібно клієнту, та згенерувати окремий пакунок для нього. Є два набори процесів для передачі даних: пара для відвантаження даних та пара для завантаження даних.

Відвантаження даних

Щоб відвантажити дані до віддаленого процесу, 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, а також деякі інші, включно з ідентифікатором клієнта).

Кожен рядок починається з чотирисимвольного шістнадцяткового значення, яке задає, наскільки довгою є решта рядка. Ваш перший рядок починається з 00a5, що шістнадцятковою означає 165, тобто в цьому рядку ще 165 байтів. Наступний рядок 0000, що означає, що сервер закінчив перелічування посилань.

Тепер, коли send-pack знає стан сервера, він може визначити, які коміти в нього є, а у сервера немає. Для кожного посилання, яке цей push буде оновлювати, процес 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, та посиланням, яке оновлюється. Перший рядок також містить можливості клієнта. Значення SHA-1 зі всіма нулями означає, що раніше нічого не було – оскільки ви додаєте посилання experiment. Якщо ви вилучаєте посилання, то буде навпаки: всі нулі з правого боку.

Далі, клієнт надсилає пакунок, що містить усі об’єкти, яких ще немає на сервері. Нарешті, сервер відповідає успіхом чи невдачею.

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, з даними, які надає send-pack.

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

Запит POST включає вивід send-pack, а також пакунок, як тіло запиту. Сервер потім зазначає успіх чи провал за допомогою відповіді 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 почав надсилати пакунок з необхідними даними:

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

Знову, це той самий формат, що й вище. Відповідь на цей запит містить успіх або провал, та включає пакунок.

Підсумок щодо протоколів

Друга секція містить дуже базовий огляд протоколів передачі. Протокол включає багато іншого функціоналу, такого як можливості multi_ack чи sde-band, проте їх розгляд виходить за межі цієї книги. Ми намагались дати вам розуміння загальної взаємодії між клієнтом та сервером; якщо вам потрібно більше інформації, то ви напевно забажаєте подивитись у вихідний код Git.