Клониране, извличане и изпращане
Pitmaster поддържа трите основни мрежови операции на git: клониране (първоначално изтегляне), извличане (инкрементално изтегляне) и изпращане (качване). URL адресите SSH (ssh://..., user@host:path) използват транспорта SSH; другите URL адреси използват smart HTTP, а клонирането и извличането преминават към dumb HTTP, ако smart обменът се провали (вижте Протоколи).
И трите стартират мрежови транспорти, затова хвърлят RuntimeException при обект, отворен с ['processes' => false] или Pitmaster::processFreeOptions().
Клониране
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и задайте fetch refspec - Откриване на препратки:
GET /info/refs?service=git-upload-pack, за да разберете какво има на сървъра - Договаряне: изпратете редове
wantза всички препратки на отдалеченото хранилище (премахват се дубликатите по хеш) - Получаване на pack: изтеглете pack файла, съдържащ всички поискани обекти
- Записване на pack: запишете pack файла в
.git/objects/pack/ - Индексиране на 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();
Материализиране на работното дърво
След получаването на pack файла 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) - Откриване на препратки: вземете текущото състояние на всички препратки на отдалеченото хранилище
- Договаряне на искани/налични обекти:
- Искани: препратки на отдалеченото хранилище, съвпадащи с refspec-овете
remote.<name>.fetch(по подразбиране+refs/heads/*:refs/remotes/<name>/*), чиито обекти липсват локално (всички, ако е зададена дълбочина) - Налични: всеки локален връх на препратка
- Искани: препратки на отдалеченото хранилище, съвпадащи с refspec-овете
- Получаване на pack: изтеглете само новите обекти
- Записване на pack: запишете в
.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 заявява тънки pack файлове, които все още не може да индексира (#16). Ако сървърът отговори на инкрементално извличане с тънък pack файл, fetch() хвърля PackParseException.
Вече е актуално
Ако всички обекти, към които сочат препратките на отдалеченото хранилище, вече съществуват локално, не се заявява pack файл и 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 (вътрешни механизми за извличане)
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
Сървърът връща потвърждения, а след това пакетните данни (възможно е да са обвити в странична лента).
ReceivePackClient (вътрешни механизми за изпращане)
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);
Форматът на заявката за изпращане:
<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');
Пакетни данни за изпращане
В момента Pitmaster събира обектите, достижими от изпращаните комити, но недостижими от текущите препратки на отдалеченото хранилище, и ги записва в пакет като базови обекти (без делта компресия). Това е функционално коректно, но създава по-големи пакети от git push. Делта компресията при запис на пакети е планирана оптимизация.