-
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 Низкоуровневые команды
A2.2 Приложение B: Встраивание Git в ваши приложения - Libgit2
Libgit2
Другой доступный вам вариант — это использование библиотеки Libgit2. Libgit2 — это свободная от внешних зависимостей реализация Git, ориентирующаяся на предоставление отличного API другим программам. Вы можете найти её на https://libgit2.org.
Для начала, давайте посмотрим на что похож C API. Вот краткий обзор:
// Открытие репозитория
git_repository *repo;
int error = git_repository_open(&repo, "/path/to/repository");
// Получение HEAD коммита
git_object *head_commit;
error = git_revparse_single(&head_commit, repo, "HEAD^{commit}");
git_commit *commit = (git_commit*)head_commit;
// Вывод некоторых атрибутов коммита на печать
printf("%s", git_commit_message(commit));
const git_signature *author = git_commit_author(commit);
printf("%s <%s>\n", author->name, author->email);
const git_oid *tree_id = git_commit_tree_id(commit);
// Очистка
git_commit_free(commit);
git_repository_free(repo);
Первая пара строк открывают Git репозиторий.
Тип git_repository
представляет собой ссылку на репозиторий с кешем в памяти.
Это самый простой метод, его можно использовать если вы знаете точный путь к рабочему каталогу репозитория или к каталогу .git
.
Кроме этого, существуют методы git_repository_open_ext
, который принимает набор параметров для поиска репозитория, git_clone
и сопутствующие — для создания локальной копии удалённого репозитория и git_repository_init
— для создания нового репозитория с нуля.
Следующий блок кода использует синтаксис rev-parse
(см. Ссылки на ветки), чтобы получить коммит, на который указывает HEAD.
Возвращаемый тип является указателем на структуру git_object
, которая представляет любой объект, хранящийся во внутренней базе данных Git.
git_object
является «родительским» для некоторых других типов; внутренняя структура всех этих типов одинаковая и соответствует git_object
, так что вы можете относительно безопасно преобразовывать типы друг в друга.
В нашем случае git_object_type(head_commit)
вернёт GIT_OBJ_COMMIT
, так что мы вправе привести тип к git_commit
.
Следующий блока кода показывает как получить доступ к свойствам коммита.
Последняя строчка в этом фрагменте кода использует тип git_oid
— это внутреннее представление SHA-1 в Libgit2.
Глядя на этот пример, можно сделать несколько выводов:
-
Если вы объявили указатель и передали его в одну из функций Libgit2, то она, скорее всего, вернёт целочисленный код ошибки. Значение
0
означает успешное выполнение операции, всё что меньше — означает ошибку. -
Если Libgit2 возвращает вам указатель, вы ответственны за очистку ресурсов.
-
Если Libgit2 возвращает
const
-указатель, вам не нужно заботится о его очистке, но он может оказаться невалидным, если объект на который он ссылается будет уничтожен. -
Писать на C — несколько сложновато.
Последний пункт намекает на маловероятность использования C при работе с Libgit2. К счастью, существует ряд обёрток над Libgit2 для различных языков, которые позволяют довольно удобно работать с Git репозиториями, используя ваш язык программирования и среду исполнения. Давайте взглянем на тот же пример, только написанный с использованием Ruby и обёртки над Libgit2 под названием Rugged, которую можно найти на https://github.com/libgit2/rugged.
repo = Rugged::Repository.new('path/to/repository')
commit = repo.head.target
puts commit.message
puts "#{commit.author[:name]} <#{commit.author[:email]}>"
tree = commit.tree
Как видите, код гораздо менее загромождён.
Во-первых, Rugged использует исключения: он может кинуть ошибку типа ConfigError
или ObjectError
чтобы просигнализировать о сбое.
Во-вторых, нет необходимости явно освобождать ресурсы, потому что в Ruby есть встроенный сборщик мусора.
Давайте посмотрим на более сложный пример — создание коммита с нуля:
blob_id = repo.write("Blob contents", :blob) # (1)
index = repo.index
index.read_tree(repo.head.target.tree)
index.add(:path => 'newfile.txt', :oid => blob_id) # (2)
sig = {
:email => "bob@example.com",
:name => "Bob User",
:time => Time.now,
}
commit_id = Rugged::Commit.create(repo,
:tree => index.write_tree(repo), # (3)
:author => sig,
:committer => sig, # (4)
:message => "Add newfile.txt", # (5)
:parents => repo.empty? ? [] : [ repo.head.target ].compact, # (6)
:update_ref => 'HEAD', # (7)
)
commit = repo.lookup(commit_id) # (8)
-
Создание нового blob, включающего содержимое нового файла.
-
Заполнение индекса содержимым дерева HEAD и добавление нового файла
newfile.txt
. -
Создание нового дерева в базе данных объектов и использование его для нового коммита.
-
Мы используем одну и ту же подпись для автора и коммитера.
-
Сообщение коммита.
-
При создании коммита нужно указать его предков. Для этих целей мы используем HEAD как единственного родителя.
-
Rugged (как и Libgit2) дополнительно могут обновить HEAD при создании коммита.
-
Используя полученное значение SHA-1 хеша нового коммита, можно получить объект типа
Commit
.
Код на Ruby приятен и чист, а благодаря тому что Libgit2 делает основную работу ещё и выполняется довольно быстро. На случай если вы пишете не на Ruby, мы рассмотрим другие обёртки над Libgit2 в Обёртки для других языков.
Расширенная функциональность
Libgit2 обладает рядом возможностей, выходящих за рамки стандартного Git. Одна из таких возможностей — расширяемость: Libgit2 позволяет использовать нестандартные интерфейсы для ряда операций, таким образом вы можете хранить объекты по-иному, нежели это делает стандартный Git. Например, Libgit2 позволяет использовать нестандартные хранилища для конфигурации, ссылок и внутренней базы данных объектов.
Давайте взглянем, как это работает. Код ниже заимствован из примеров, написанных командой разработчиков Libgit2, вы можете ознакомиться с ними на https://github.com/libgit2/libgit2-backends. Вот как можно использовать нестандартное хранилище для базы данных объектов:
git_odb *odb;
int error = git_odb_new(&odb); // (1)
git_odb_backend *my_backend;
error = git_odb_backend_mine(&my_backend, /*…*/); // (2)
error = git_odb_add_backend(odb, my_backend, 1); // (3)
git_repository *repo;
error = git_repository_open(&repo, "some-path");
error = git_repository_set_odb(repo, odb); // (4)
(Заметьте, ошибки перехватываются, но не обрабатываются. Мы надеемся, ваш код лучше нашего.)
-
Инициализация интерфейса для пустой базы данных объектов, который будет использоваться как контейнер для внутренних интерфейсов, которые будут выполнять работу.
-
Инициализация произвольного внутреннего интерфейса базы данных объектов.
-
Добавление внутреннего интерфейса к внешнему.
-
Открытие репозитория и настройка на использование собственной базы для поиска объектов.
Что же скрыто внутри git_odb_backend_mine
?
Это ваша собственная реализация базы данных объектов, где вы можете делать что угодно, лишь бы поля структуры git_odb_backend
были заполнены верно.
Например, внутри может быть следующий код:
typedef struct {
git_odb_backend parent;
// Дополнительные поля
void *custom_context;
} my_backend_struct;
int git_odb_backend_mine(git_odb_backend **backend_out, /*…*/)
{
my_backend_struct *backend;
backend = calloc(1, sizeof (my_backend_struct));
backend->custom_context = …;
backend->parent.read = &my_backend__read;
backend->parent.read_prefix = &my_backend__read_prefix;
backend->parent.read_header = &my_backend__read_header;
// …
*backend_out = (git_odb_backend *) backend;
return GIT_SUCCESS;
}
Важный момент: в my_backend_struct
первое поле должно быть структурой git_odb_backend
, что обеспечит расположение полей в памяти в формате, ожидаемом Libgit2.
Оставшиеся поля можно располагать произвольно, а сама структура может быть любого нужного вам размера.
Функция инициализации выделяет память под структуру, устанавливает произвольный контекст и заполняет поля структуры parent
, которые необходимо поддерживать.
Взгляните на файл include/git2/sys/odb_backend.h
в исходном коде Libgit2 чтобы узнать полный список сигнатур доступных методов; в вашем конкретном случае вы сами решаете, какие из них необходимо имплементировать.
Обёртки для других языков
У Libgit2 есть привязки для многих языков.
Здесь мы приведём лишь парочку небольших примеров; полный список поддерживаемых языков гораздо шире и включает в себя, среди прочего, C++, Go, Node.js, Erlang и JVM, на разных стадиях зрелости.
Официальный список обёрток можно найти на https://github.com/libgit2.
Примеры кода ниже показывают как получить сообщение HEAD-коммита (что-то типа git log -1
).
LibGit2Sharp
Если вы пишете под платформы .NET / Mono, LibGit2Sharp (https://github.com/libgit2/libgit2sharp) — то, что вы искали. Эта библиотека написана на C# и все прямые вызовы методов Libgit2 тщательно обёрнуты в управляемый CLR код. Вот как будет выглядеть наш пример:
new Repository(@"C:\path\to\repo").Head.Tip.Message;
Также существует NuGet пакет для десктопных Windows-приложений, который поможет начать разработку ещё быстрее.
objective-git
Если вы пишете приложение для продукции Apple, то скорее всего оно написано на Objective-C. Обёртка над Libgit2 в этом случае называется Objective-Git: (https://github.com/libgit2/objective-git). Пример кода:
GTRepository *repo =
[[GTRepository alloc] initWithURL:[NSURL fileURLWithPath: @"/path/to/repo"] error:NULL];
NSString *msg = [[[repo headReferenceWithError:NULL] resolvedTarget] message];
Objective-git полностью интероперабелен с новым языком Swift, так что не бойтесь переходить на него с Objective-C.
pygit2
Обёртка над Libgit2 для Python называется Pygit2, её можно найти на https://www.pygit2.org. И наш пример будет выглядеть так:
pygit2.Repository("/path/to/repo") # открыть репозиторий
.head # получить текущую ветку
.peel(pygit2.Commit) # получить коммит
.message # прочитать сообщение
Дополнительные материалы
Конечно же, полное описание возможностей Libgit2 выходит далеко за пределы этой книги. Если вы хотите подробнее ознакомиться с Libgit2, можете начать с документации к API https://libgit2.github.com/libgit2 и набора руководств https://libgit2.github.com/docs. Для привязок к другим языкам, загляните в README и исходники тестов, довольно часто в них встречаются ссылки на полезные материалы по теме.