Přejít k obsahu

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í

PHP
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í

  1. Inicializace: vytvoření nového prázdného úložiště v cílové cestě
  2. Konfigurace vzdáleného úložiště: uložení URL jako remote.origin.url a nastavení refspec pro načítání
  3. Vyhledání odkazů: GET /info/refs?service=git-upload-pack zjistí, co server obsahuje
  4. Vyjednání: odeslání řádků want pro všechny vzdálené odkazy (hashy se deduplikují)
  5. Přijetí packu: stažení souboru pack se všemi požadovanými objekty
  6. Zápis packu: uložení souboru pack do .git/objects/pack/
  7. Indexování packu: vytvoření souboru .idx pro rychlé vyhledávání
  8. Nastavení odkazů: vytvoření vzdálených sledovacích větví (refs/remotes/origin/*) a štítků
  9. Nastavení HEAD: nasměrování HEAD na výchozí větev (podle schopnosti symref=HEAD:... serveru, nebo refs/heads/main, pokud server žádnou nepošle) a nastavení branch.<name>.remote a branch.<name>.merge
  10. 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).

PHP
$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í

PHP
// 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í

  1. Načtení vzdáleného URL: z .git/config (remote.<name>.url)
  2. Vyhledání odkazů: získání aktuálního stavu všech vzdálených odkazů
  3. 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
  4. Přijetí packu: stažení pouze nových objektů
  5. Zápis packu: uložení do .git/objects/pack/ a vytvoření jeho indexu
  6. 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)
PHP
// 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í

PHP
$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í

  1. Načíst vzdálené URL: z .git/config
  2. Určit, co odeslat: zjistit hash commitu místní větve
  3. Zjistit vzdálené reference: získat aktuální stav vzdálené větve (pro výpočet starého hashe)
  4. 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
  5. Odeslat aktualizaci reference: <old-hash> <new-hash> refs/heads/<branch>
  6. Odeslat data packu: pack obsahující objekty, které vzdálený server potřebuje
  7. 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é.

PHP
// 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:

PHP
// 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)

PHP
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:

Kód
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)

PHP
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:

Kód
<old-hash> <new-hash> refs/heads/main\0<capabilities>\n
0000
PACK<pack data>

Zpracování chyb

PHP
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:

Ini
[remote "origin"]
    url = https://github.com/user/repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*

Přístup přes konfiguraci API:

PHP
$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.