Klonování, načítání, odesílání
Pitmaster podporuje tři základní síťové operace gitu: clone (počáteční stažení), fetch (průběžné stahování) a push (odeslání). Adresy URL SSH (ssh://..., user@host:path) používají přenos SSH; ostatní adresy URL používají chytrý HTTP a clone i fetch při selhání chytré výměny přecházejí na jednoduchý HTTP (viz Protokoly).
Všechny tři operace spouštějí síťové přenosy, proto vyvolají RuntimeException u objektu otevřeného pomocí ['processes' => false] nebo Pitmaster::processFreeOptions().
Klonování
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);
Nové úložiště vždy používá SHA-1. Pokud klonování selže a cílová cesta předtím neexistovala, částečně vytvořený adresář se odstraní.
Průběh klonování
- Inicializace: vytvoření nového prázdného úložiště v cílové cestě
- Konfigurace vzdáleného úložiště: uložení URL jako
remote.origin.urla nastavení refspec pro načítání - Vyhledání odkazů:
GET /info/refs?service=git-upload-packzjistí, co server obsahuje - Vyjednání: odeslání řádků
wantpro všechny vzdálené odkazy (hashy se deduplikují) - Přijetí packu: stažení souboru pack se všemi požadovanými objekty
- Zápis packu: uložení souboru pack do
.git/objects/pack/ - Indexování packu: vytvoření souboru
.idxpro rychlé vyhledávání - Nastavení odkazů: vytvoření vzdálených sledovacích větví (
refs/remotes/origin/*) a štítků - Nastavení HEAD: nasměrování HEAD na výchozí větev (podle schopnosti
symref=HEAD:...serveru, neborefs/heads/main, pokud server žádnou nepošle) a nastaveníbranch.<name>.remoteabranch.<name>.merge - Checkout: přepnutí na tuto větev, čímž se zapíše pracovní strom a index
Chyby při závěrečném checkoutu se zachytí a ignorují, takže se klonování může dokončit s prázdným nebo neúplným pracovním stromem (#19).
$repo = Pitmaster::clone($url, $path);
$head = $repo->head();
echo $head->message;
$branches = $repo->branches();
$files = $repo->status();
Vytvoření pracovního stromu
Po přijetí packu zavolá Pitmaster metodu $repo->checkout() pro výchozí větev. Ta zapíše všechny soubory ze stromu commitu HEAD do pracovního adresáře a vytvoří index z položek stromu, takže status() oznámí čistý pracovní strom.
Načítání
// 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
Průběh načítání
- Načtení vzdáleného URL: z
.git/config(remote.<name>.url) - Vyhledání odkazů: získání aktuálního stavu všech vzdálených odkazů
- Vyjednání požadovaných/dostupných objektů:
- Požadované: vzdálené odkazy odpovídající refspecům
remote.<name>.fetch(výchozí+refs/heads/*:refs/remotes/<name>/*), jejichž objekty místně chybí (při zadání hloubky všechny) - Dostupné: každý vrchol místního odkazu
- Požadované: vzdálené odkazy odpovídající refspecům
- Přijetí packu: stažení pouze nových objektů
- Zápis packu: uložení do
.git/objects/pack/a vytvoření jeho indexu - Aktualizace sledovacích odkazů: aktualizace
refs/remotes/<remote>/<branch>pro každou odpovídající vzdálenou větev a zápis všech zveřejněných odkazů na štítky. Objekty štítků se nevyžadují samostatně, takže nový anotovaný štítek lze zapsat jako odkaz na objekt, který nebyl stažen (#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() vyhledává názvy jako refs/heads/<name>, refs/tags/<name> a samotný název, takže krátký tvar origin/main se nenajde.
Pitmaster požaduje tenké packy, které zatím neumí indexovat (#16). Pokud server odpoví na průběžné načtení tenkým packem, fetch() vyvolá PackParseException.
Již aktuální
Pokud všechny objekty, na které ukazují vzdálené odkazy, již existují místně, pack se nevyžádá a fetch() se po vyhledání odkazů vrátí. V takovém případě se neaktualizují ani sledovací odkazy, a to ani v případě, že se vzdálené větve přesunuly na commity, které již máte (#23).
Odesílání
$repo->push(); // Push current branch to 'origin'
$repo->push('origin', 'main'); // Push a specific branch
$repo->push('upstream', 'feature'); // Push to a named remote
Průběh odesílání
- Načíst vzdálené URL: z
.git/config - Určit, co odeslat: zjistit hash commitu místní větve
- Zjistit vzdálené reference: získat aktuální stav vzdálené větve (pro výpočet starého hashe)
- Ověřit posun vpřed: pokud vzdálená větev není předkem místní větve, vyhodit
RuntimeException("Push rejected: non-fast-forward update to <branch>")ještě před odesláním čehokoli - Odeslat aktualizaci reference:
<old-hash> <new-hash> refs/heads/<branch> - Odeslat data packu: pack obsahující objekty, které vzdálený server potřebuje
- Ověřit výsledek: server vrátí úspěch nebo neúspěch pro každou referenci
Hook pre-push se spustí před odesláním aktualizace, pokud jsou hooky povolené.
// Push the current branch
$repo->push();
// Push a specific branch to a specific remote
$repo->push('origin', 'feature/login');
Požadavky pro push
- Aktuální větev musí existovat lokálně
- Musí být nakonfigurována vzdálená repozitář s URL
- Vzdálený server musí push přijmout (ověření, oprávnění, zásady pro aktualizace bez posunu vpřed)
Vynucený push
Pitmaster odešle starý hash z aktuální inzerce vzdálené reference a push() lokálně odmítá aktualizace bez posunu vpřed. Chcete-li přepsat vzdálenou větev, použijte 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 (interní funkce 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);
Vyjednávání want/have sestaví požadavek ve formátu 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
Server odpoví potvrzeními a poté daty packu (případně zabalenými do side-band).
ReceivePackClient (interní funkce 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);
Formát požadavku push:
<old-hash> <new-hash> refs/heads/main\0<capabilities>\n
0000
PACK<pack data>
Zpracování chyb
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() může také vyhodit běžnou RuntimeException (neznámý vzdálený server, handle bez procesu) nebo PackParseException (pack, který nelze indexovat). ProtocolException i PackParseException rozšiřují \RuntimeException, takže zachycení \RuntimeException pokryje všechny tyto případy.
Konfigurace vzdálených repozitářů
Vzdálené repozitáře jsou uloženy v .git/config:
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
Přístup přes konfiguraci 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');
Data packu pro push
V současnosti Pitmaster shromažďuje objekty dosažitelné z odesílaných commitů, které nejsou dosažitelné z aktuálních vzdálených referencí, a zapisuje je do packu jako základní objekty (bez delta komprese). Funkčně je to správné, ale vznikají větší packy než u git push. Delta komprese při zápisu packu je plánovaným vylepšením.