Клонування, отримання, надсилання
Pitmaster підтримує три основні мережеві операції git: клонування (початкове завантаження), отримання (інкрементне завантаження) та надсилання (вивантаження). URL-адреси SSH (ssh://..., user@host:path) використовують транспорт SSH; інші URL-адреси використовують розумний HTTP, а клонування та отримання переходять до простого HTTP, якщо розумний обмін даними не вдається (див. Протоколи).
Усі три операції запускають мережеві транспорти, тому для дескриптора, відкритого з ['processes' => false] або Pitmaster::processFreeOptions(), вони викликають RuntimeException.
Клонування
use Pitmaster\Pitmaster;
// public static function clone(string $url, string $path, ?int $depth = null, array $options = []): Repository
$repo = Pitmaster::clone('https://github.com/user/repo.git', '/path/to/destination');
$repo = Pitmaster::clone('https://github.com/user/repo.git', '/path/to/shallow', 1);
Новий репозиторій завжди використовує SHA-1. Якщо клонування не вдається, а призначення до цього не існувало, частково створений каталог видаляється.
Процес клонування
- Ініціалізація: створити новий порожній репозиторій за шляхом призначення
- Налаштування віддаленого репозиторію: зберегти URL як
remote.origin.urlі налаштувати специфікацію отримання посилань - Виявлення посилань: виконати
GET /info/refs?service=git-upload-pack, щоб дізнатися, що є на сервері - Узгодження: надіслати рядки
wantдля всіх віддалених посилань (без дублікатів за хешем) - Отримання пакета: завантажити файл пакета з усіма запитаними об’єктами
- Запис пакета: зберегти файл пакета в
.git/objects/pack/ - Індексація пакета: створити файл
.idxдля швидкого пошуку - Налаштування посилань: створити гілки для відстеження віддалених гілок (
refs/remotes/origin/*) і теги - Налаштування HEAD: спрямувати HEAD на типову гілку (з можливості сервера
symref=HEAD:...або наrefs/heads/main, якщо сервер її не надішле), а також налаштуватиbranch.<name>.remoteіbranch.<name>.merge - Вибір гілки: вибрати цю гілку, щоб створити робоче дерево та індекс
Помилки під час завершального вибору гілки перехоплюються та ігноруються, тому клонування може завершитися з порожнім або частково створеним робочим деревом (#19).
$repo = Pitmaster::clone($url, $path);
$head = $repo->head();
echo $head->message;
$branches = $repo->branches();
$files = $repo->status();
Створення робочого дерева
Після отримання пакета Pitmaster викликає $repo->checkout() для типової гілки. Це записує кожен файл із дерева коміту HEAD у робочий каталог і створює індекс із записів дерева, тож status() повідомляє про чисте робоче дерево.
Отримання
// public function fetch(string $remote = 'origin', ?int $depth = null): void
$repo->fetch(); // Fetch from 'origin'
$repo->fetch('upstream'); // Fetch from a named remote
$repo->fetch('origin', 1); // Shallow fetch
Процес отримання
- Читання віддаленого URL: з
.git/config(remote.<name>.url) - Виявлення посилань: отримати поточний стан усіх віддалених посилань
- Узгодження потрібних і наявних об’єктів:
- Потрібні: віддалені посилання, що відповідають специфікаціям
remote.<name>.fetch(типово+refs/heads/*:refs/remotes/<name>/*), об’єкти яких відсутні локально (усі вони, якщо вказано глибину) - Наявні: усі локальні вершини посилань
- Потрібні: віддалені посилання, що відповідають специфікаціям
- Отримання пакета: завантажити лише нові об’єкти
- Запис пакета: зберегти в
.git/objects/pack/і створити його індекс - Оновлення посилань відстеження: оновити
refs/remotes/<remote>/<branch>для кожної відповідної віддаленої гілки та записати кожне оголошене посилання на тег. Об’єкти тегів окремо не запитуються, тому новий анотований тег може бути записаний як посилання на незавантажений об’єкт (#24)
// Fetch is incremental: only downloads what's new
$repo->fetch();
// After fetching, merge or rebase to incorporate changes (use the full ref name)
$result = $repo->merge('refs/remotes/origin/main');
resolve() шукає імена як refs/heads/<name>, refs/tags/<name>, а потім саме ім’я, тому коротку форму origin/main не знайдено.
Pitmaster запитує тонкі пакети, які поки не може індексувати (#16). Якщо сервер відповідає на інкрементне отримання тонким пакетом, fetch() викликає PackParseException.
Уже актуально
Якщо всі об’єкти, на які вказують віддалені посилання, вже є локально, пакет не запитується, а fetch() повертається після виявлення посилань. У цьому разі посилання відстеження також не оновлюються, навіть якщо віддалені гілки перемістилися на коміти, які вже є у вас (#23).
Надсилання
$repo->push(); // Push current branch to 'origin'
$repo->push('origin', 'main'); // Push a specific branch
$repo->push('upstream', 'feature'); // Push to a named remote
Процес надсилання
- Читання віддаленого URL: з
.git/config - Визначення змін для надсилання: знайти хеш коміту локальної гілки
- Виявлення віддалених посилань: отримати поточний стан віддаленої гілки (щоб обчислити старий хеш)
- Перевірка перемотування вперед: якщо віддалена гілка не є предком локальної гілки, викликати
RuntimeException("Push rejected: non-fast-forward update to <branch>")до надсилання будь-яких даних - Надсилання оновлення посилання:
<old-hash> <new-hash> refs/heads/<branch> - Надсилання даних пакета: пакет з об’єктами, потрібними віддаленому репозиторію
- Перевірка результату: сервер відповідає успішним або невдалим результатом для кожного посилання
Хук pre-push виконується перед надсиланням оновлення, якщо хуки не вимкнено.
// Push the current branch
$repo->push();
// Push a specific branch to a specific remote
$repo->push('origin', 'feature/login');
Вимоги для надсилання
- Поточна гілка має існувати локально
- Для віддаленого репозиторію має бути налаштовано URL
- Віддалений репозиторій має приймати надсилання (автентифікація, дозволи, політики щодо перемотування вперед)
Примусове надсилання
Pitmaster надсилає старий хеш із поточного оголошення посилань віддаленого репозиторію, а push() відхиляє локальні оновлення, що не є перемотуванням уперед. Щоб перезаписати гілку віддаленого репозиторію, використовуйте force-with-lease:
// Lease defaults to refs/remotes/<remote>/<branch>; pass an ObjectId to set it explicitly
$repo->pushForceWithLease('origin', 'main');
$repo->pushForceWithLease('origin', 'main', $repo->resolve('refs/remotes/origin/main'));
// Update several branches in one atomic request
$repo->pushAtomic('origin', ['main', 'release']);
// Make the remote mirror local refs (also deletes remote refs that no longer exist locally)
$repo->pushMirror('origin');
UploadPackClient (внутрішня логіка fetch)
use Pitmaster\Protocol\UploadPackClient;
use Pitmaster\Protocol\SmartHttpClient;
$http = new SmartHttpClient();
$uploadPack = new UploadPackClient($http);
// Fetch with explicit wants and haves
$packData = $uploadPack->fetch($url, $wants, $haves);
Узгодження want/have формує запит pkt-line:
want <hash1> <capabilities>\n
want <hash2>\n
deepen <depth>\n (only when a depth is given)
0000
have <hash3>\n
have <hash4>\n
done\n
Сервер відповідає підтвердженнями, а потім даними пакета (можливо, загорнутими в side-band).
ReceivePackClient (внутрішня логіка push)
use Pitmaster\Protocol\ReceivePackClient;
use Pitmaster\Protocol\SmartHttpClient;
$http = new SmartHttpClient();
$receivePack = new ReceivePackClient($http);
$receivePack->push($url, [
[
'old' => $oldId, // Current remote hash (or zero hash for new branch)
'new' => $localId, // Local commit hash to push
'ref' => 'refs/heads/main',
],
], $packData);
Формат запиту push:
<old-hash> <new-hash> refs/heads/main\0<capabilities>\n
0000
PACK<pack data>
Обробка помилок
use Pitmaster\Exceptions\ProtocolException;
try {
$repo->fetch();
} catch (ProtocolException $e) {
// Network unreachable, auth failed, server error, etc.
echo $e->getMessage();
}
try {
$repo->push();
} catch (\RuntimeException $e) {
// Remote not configured, not on a branch, branch not found, non-fast-forward
echo $e->getMessage();
}
fetch() також може викинути звичайний виняток RuntimeException (невідомий віддалений репозиторій, дескриптор без процесу) або PackParseException (пакет, який неможливо індексувати). І ProtocolException, і PackParseException успадковують \RuntimeException, тож перехоплення \RuntimeException охоплює їх усі.
Конфігурація віддалених репозиторіїв
Віддалені репозиторії зберігаються в .git/config:
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
Доступ через конфігурацію API:
$config = $repo->config();
$url = $config->get('remote.origin.url');
$config->set('remote.upstream.url', 'https://github.com/other/repo.git');
$config->set('remote.upstream.fetch', '+refs/heads/*:refs/remotes/upstream/*');
// set() changes the in-memory config only; write it to persist the change
$config->writeToFile($repo->commonGitDir() . '/config');
Дані пакета для push
Наразі Pitmaster збирає об’єкти, досяжні з комітів, які надсилаються, але недосяжні з поточних посилань віддаленого репозиторію, і записує їх у пакет як базові об’єкти (без дельта-стиснення). Функціонально це правильно, але пакети виходять більшими, ніж у git push. Дельта-стиснення під час запису пакетів заплановано як оптимізацію.