-
1. Введение
- 1.1 О системе контроля версий
- 1.2 Краткая история Git
- 1.3 Что такое Git?
- 1.4 Командная строка
- 1.5 Установка Git
- 1.6 Первоначальная настройка Git
- 1.7 Как получить помощь?
- 1.8 Заключение
-
2. Основы Git
-
3. Ветвление в Git
- 3.1 О ветвлении в двух словах
- 3.2 Основы ветвления и слияния
- 3.3 Управление ветками
- 3.4 Работа с ветками
- 3.5 Удалённые ветки
- 3.6 Перебазирование
- 3.7 Заключение
-
4. Git на сервере
- 4.1 Протоколы
- 4.2 Установка Git на сервер
- 4.3 Генерация открытого SSH ключа
- 4.4 Настраиваем сервер
- 4.5 Git-демон
- 4.6 Умный HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Git-хостинг
- 4.10 Заключение
-
5. Распределённый Git
-
6. GitHub
-
7. Инструменты Git
- 7.1 Выбор ревизии
- 7.2 Интерактивное индексирование
- 7.3 Припрятывание и очистка
- 7.4 Подпись
- 7.5 Поиск
- 7.6 Перезапись истории
- 7.7 Раскрытие тайн reset
- 7.8 Продвинутое слияние
- 7.9 Rerere
- 7.10 Обнаружение ошибок с помощью Git
- 7.11 Подмодули
- 7.12 Создание пакетов
- 7.13 Замена
- 7.14 Хранилище учётных данных
- 7.15 Заключение
-
8. Настройка Git
- 8.1 Конфигурация Git
- 8.2 Атрибуты Git
- 8.3 Хуки в Git
- 8.4 Пример принудительной политики Git
- 8.5 Заключение
-
9. Git и другие системы контроля версий
- 9.1 Git как клиент
- 9.2 Переход на Git
- 9.3 Заключение
-
10. Git изнутри
- 10.1 Сантехника и Фарфор
- 10.2 Объекты Git
- 10.3 Ссылки в Git
- 10.4 Pack-файлы
- 10.5 Спецификации ссылок
- 10.6 Протоколы передачи данных
- 10.7 Обслуживание репозитория и восстановление данных
- 10.8 Переменные окружения
- 10.9 Заключение
-
A1. Приложение A: Git в других окружениях
- A1.1 Графические интерфейсы
- A1.2 Git в Visual Studio
- A1.3 Git в Visual Studio Code
- A1.4 Git в Eclipse
- A1.5 Git в IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.6 Git в Sublime Text
- A1.7 Git в Bash
- A1.8 Git в Zsh
- A1.9 Git в PowerShell
- A1.10 Заключение
-
A2. Приложение B: Встраивание Git в ваши приложения
- A2.1 Git из командной строки
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Приложение C: Команды Git
- A3.1 Настройка и конфигурация
- A3.2 Клонирование и создание репозиториев
- A3.3 Основные команды
- A3.4 Ветвление и слияния
- A3.5 Совместная работа и обновление проектов
- A3.6 Осмотр и сравнение
- A3.7 Отладка
- A3.8 Внесение исправлений
- A3.9 Работа с помощью электронной почты
- A3.10 Внешние системы
- A3.11 Администрирование
- A3.12 Низкоуровневые команды
10.6 Git изнутри - Протоколы передачи данных
Протоколы передачи данных
Git умеет передавать данные между репозиториями двумя способами: используя «глупый» и «умный» протоколы. В этой главе мы рассмотрим, как они работают.
Глупый протокол
Если вы разрешили доступ на чтение к вашему репозиторию через HTTP, то скорее всего будет использован «глупый» протокол.
Протокол назвали «глупым», потому что для его работы не требуется выполнение специфичных для Git операций на стороне сервера: весь процесс получения данных представляет собой серию HTTP GET
запросов, при этом клиент ожидает наличия на сервере структуры каталогов аналогичной Git репозиторию.
Примечание
|
Глупый протокол довольно редко используется в наши дни. При использовании глупого протокола сложно обеспечить безопасность передачи и приватность данных, поэтому большинство Git серверов (как облачных, так и тех, что требуют установки) откажутся работать через него. Рекомендуется использовать умный протокол, который мы рассмотрим далее. |
Давайте рассмотрим процесс получения данных из репозитория simplegit-progit
:
$ git clone http://server/simplegit-progit.git
Первым делом будет загружен файл info/refs
.
Данный файл записывается командой update-server-info
, поэтому для корректной работы HTTP-транспорта необходимо выполнять её в post-receive
триггере.
=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949 refs/heads/master
Теперь у нас имеется список удалённых веток и их хеши. Далее, надо посмотреть, куда ссылается HEAD, чтобы знать на что переключиться после завершения работы команды.
=> GET HEAD
ref: refs/heads/master
Итак, нужно переключится на ветку master
после окончания работы.
На данном этапе можно начинать обход репозитория.
Начальной точкой является коммит ca82a6
, о чём мы узнали из файла info/refs
, поэтому мы начинаем с его загрузки:
=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)
Объект получен, он был в рыхлом формате на сервере, и мы получили его по HTTP, используя GET-запрос. Теперь можно его разархивировать, обрезать заголовок и посмотреть на содержимое:
$ 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. Возможны два варианта: объект в другом репозитории или в упакованном файле текущего репозитория. Сначала Git проверяет список альтернативных репозиториев:
=> GET objects/info/http-alternates
(empty file)
Если бы этот запрос вернул непустой список альтернатив, Git проверил бы указанные репозитории на наличие файла в «рыхлом» формате — довольно полезная возможность для проектов-форков, позволяющая устранить дублирование объектов на диске.
Так как в данном случае альтернатив нет, объект должен быть упакован в 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)
Так как в индексе содержится список SHA-1 хешей объектов и соответствующих им смещений объектов внутри pack-файла, то можно проверить наличие объекта в этом pack-файле. Наш объект там присутствует, так что продолжим и скачаем весь pack-файл:
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)
Итак, мы получили наше дерево, можно продолжить обход списка коммитов.
Все они содержатся внутри только что скачанного pack-файла, так что снова обращаться к серверу не надо.
Git извлекает рабочую копию ветки master
, так как на неё указывает ссылка HEAD
, которая была скачана в самом начале.
Умный протокол
Глупый протокол прост, но неэффективен и не позволяет производить запись в удалённые репозитории. Гораздо чаще для обмена данными используют «умный» протокол, но это требует наличия на сервере специального процесса, знающего о структуре Git репозитория, умеющего выяснять, какие данные необходимо отправить клиенту и генерирующего отдельный pack-файл с недостающими изменениями для него. Работу умного протокола обеспечивают несколько процессов: два для отправки данных на сервер и два для загрузки с него.
Загрузка данных на сервер
Для загрузки данных на удалённый сервер используются процессы 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-байтового шестнадцатеричного значения, определяющего его размер (включая эти 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 посылает по строке, содержащей собственную длину, старый хеш, новый хеш и имя ссылки.
В первой строке также посылаются возможности клиента.
Хеш, состоящий из нулей, говорит о том, что раньше такой ссылки не было — вы ведь добавляете новую ветку experiment
.
При удалении ветки всё было бы наоборот: нули были бы справа.
Затем клиент посылает pack-файл c объектами, которых нет на сервере. Наконец, сервер передаёт статус операции — успех или ошибка:
000eunpack ok
HTTP(S)
Этот процесс похож на HTTP, но установка соединения слегка отличается. Всё начинается с такого запроса:
=> GET http://server/simplegit-progit.git/info/refs?service=git-receive-pack
001f# service=git-receive-pack
000000ab6c5f0e45abd7832bf23074a333f739977c9e8188 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
Этот запрос включает в себя результаты 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\0multi_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
, но только возможности другие.
Вдобавок upload-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
запрос на тот же URL, что и в случае глупого протокола:
=> GET $GIT_URL/info/refs?service=git-upload-pack
001e# service=git-upload-pack
00e7ca82a6dff817ec66f44342007202690a93763949 HEAD\0multi_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-файл.
Заключение
В этом разделе мы вкратце рассмотрели протоколы передачи данных.
Протоколы обмена данных в Git включают в себя множество возможностей, таких как multi_ack
или side-band
, но их рассмотрение выходит за пределы этой книги.
Мы описали формат сообщений между клиентом и сервером не вдаваясь в детали, если хотите покопаться в этой теме глубже — обратитесь к исходному коду Git.