Preskoči na vsebino

Kloniranje, pridobivanje in pošiljanje

Pitmaster podpira tri temeljna omrežna opravila git: kloniranje (začetni prenos), pridobivanje (postopni prenos) in pošiljanje (nalaganje). URL-ji SSH (ssh://..., user@host:path) uporabljajo prenos SSH; drugi URL-ji uporabljajo pametni protokol HTTP, kloniranje in pridobivanje pa ob neuspešni pametni izmenjavi preklopita na preprosti HTTP (glejte Protokoli).

Vsi trije zaženejo omrežne prenose, zato ob uporabi povezave, odprte z ['processes' => false] ali Pitmaster::processFreeOptions(), sprožijo RuntimeException.

Kloniranje

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);

Novi repozitorij vedno uporablja SHA-1. Če kloniranje ne uspe in ciljna pot prej ni obstajala, se delno ustvarjeni imenik odstrani.

Potek kloniranja

  1. Inicializacija: na ciljni poti ustvarite nov prazen repozitorij
  2. Konfiguracija oddaljenega repozitorija: shranite URL kot remote.origin.url in nastavite specifikacijo referenc za pridobivanje
  3. Odkritje referenc: izvedite GET /info/refs?service=git-upload-pack, da ugotovite, kaj je na strežniku
  4. Dogovarjanje: pošljite vrstice want za vse oddaljene reference (podvojitve odstranite glede na zgoščeno vrednost)
  5. Prejem paketa: prenesite datoteko paketa z vsemi zahtevanimi objekti
  6. Zapis paketa: shranite datoteko paketa v .git/objects/pack/
  7. Indeksiranje paketa: ustvarite datoteko .idx za hitro iskanje
  8. Nastavitev referenc: ustvarite oddaljene sledilne veje (refs/remotes/origin/*) in oznake
  9. Nastavitev HEAD: usmerite HEAD na privzeto vejo (glede na zmožnost symref=HEAD:... strežnika ali na refs/heads/main, če strežnik ne pošlje nobene), nato nastavite branch.<name>.remote in branch.<name>.merge
  10. Prevzem: prevzemite to vejo; s tem se zapiše delovno drevo in indeks

Napake pri končnem prevzemu se prestrežejo in prezrejo, zato se lahko kloniranje konča s praznim ali delnim delovnim drevesom (#19).

PHP
$repo = Pitmaster::clone($url, $path);
$head = $repo->head();
echo $head->message;

$branches = $repo->branches();
$files = $repo->status();

Priprava delovnega drevesa

Po prejemu paketa Pitmaster pokliče $repo->checkout() za privzeto vejo. To zapiše vse datoteke iz drevesa objave HEAD v delovni imenik in iz vnosov v drevesu sestavi indeks, zato status() prikaže čisto delovno drevo.

Pridobivanje

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

Potek pridobivanja

  1. Branje oddaljenega URL: iz .git/config (remote.<name>.url)
  2. Odkritje referenc: pridobite trenutno stanje vseh oddaljenih referenc
  3. Dogovarjanje o zahtevanih in razpoložljivih objektih:
    • Zahtevani: oddaljene reference, ki ustrezajo specifikacijam referenc remote.<name>.fetch (privzeto +refs/heads/*:refs/remotes/<name>/*) in katerih objekti lokalno manjkajo (vse reference, če je podana globina)
    • Razpoložljivi: vrh vsake lokalne reference
  4. Prejem paketa: prenesite samo nove objekte
  5. Zapis paketa: shranite v .git/objects/pack/ in ustvarite njegov indeks
  6. Posodobitev sledilnih referenc: posodobite refs/remotes/<remote>/<branch> za vsako ujemajočo se oddaljeno vejo in zapišite vse oglaševane reference oznak. Objekti oznak niso zahtevani posebej, zato je lahko nova označena oznaka zapisana kot referenca na objekt, ki ni bil prenesen (#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() išče imena pod refs/heads/<name>, refs/tags/<name> in samim imenom, zato kratke oblike origin/main ne najde.

Pitmaster zahteva tanke pakete, ki jih še ne more indeksirati (#16). Če strežnik na postopno pridobivanje odgovori s tankim paketom, fetch() sproži PackParseException.

Vse je posodobljeno

Če vsi objekti, na katere kažejo oddaljene reference, že obstajajo lokalno, se paket ne zahteva in fetch() se vrne po odkritju referenc. V tem primeru se ne posodobijo niti sledilne reference, tudi če so se oddaljene veje premaknile na objave, ki jih že imate (#23).

Pošiljanje

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

Potek pošiljanja

  1. Branje oddaljenega URL: iz .git/config
  2. Določitev vsebine pošiljanja: razrešite lokalno vejo v zgoščeno vrednost njene objave
  3. Odkritje oddaljenih referenc: pridobite trenutno stanje oddaljene veje (za izračun stare zgoščene vrednosti)
  4. Preverjanje pomika naprej: če oddaljena veja ni prednik lokalne, pred pošiljanjem sprožite RuntimeException("Push rejected: non-fast-forward update to <branch>")
  5. Pošiljanje posodobitve reference: <old-hash> <new-hash> refs/heads/<branch>
  6. Pošiljanje podatkov paketa: paket vsebuje objekte, ki jih potrebuje oddaljeni repozitorij
  7. Preverjanje rezultata: strežnik za vsako referenco sporoči uspeh ali neuspeh

Kavelj pre-push se izvede pred pošiljanjem posodobitve, razen če so kavlji onemogočeni.

PHP
// Push the current branch
$repo->push();

// Push a specific branch to a specific remote
$repo->push('origin', 'feature/login');

Zahteve za pošiljanje

  • Trenutna veja mora obstajati lokalno
  • Oddaljeni repozitorij mora biti nastavljen z URL
  • Oddaljeni repozitorij mora sprejeti pošiljanje (preverjanje pristnosti, dovoljenja, pravilniki za nelinearni pomik naprej)

Prisilno pošiljanje

Pitmaster pošlje staro zgoščeno vrednost iz trenutnega oglaševanja referenc oddaljenega repozitorija, push() pa lokalno zavrne posodobitve z nelinearnim pomikom naprej. Če želite prepisati oddaljeno vejo, uporabite 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 (podrobnosti pridobivanja)

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);

Dogovarjanje o zahtevanih in razpoložljivih objektih sestavi zahtevo pkt-line:

Koda
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

Strežnik odgovori s potrditvami in nato s podatki paketa (morda ovitimi v stranski pas).

ReceivePackClient (podrobnosti pošiljanja)

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);

Oblika zahteve za pošiljanje:

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

Obravnava napak

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() lahko sproži tudi navadno RuntimeException (neznan oddaljeni repozitorij, povezava brez procesov) ali PackParseException (paketa ni mogoče indeksirati). ProtocolException in PackParseException oba razširjata \RuntimeException, zato zajem \RuntimeException pokrije vse te primere.

Konfiguracija oddaljenih repozitorijev

Oddaljeni repozitoriji so shranjeni v .git/config:

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

Dostop prek konfiguracije 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');

Podatki paketa za pošiljanje

Trenutno Pitmaster zbere objekte, dosegljive iz objav, ki jih pošiljate, a niso dosegljivi iz trenutnih oddaljenih referenc, in jih zapiše kot osnovne objekte (brez stiskanja delt) v paket. Funkcionalno je to pravilno, vendar so paketi večji kot pri git push. Stiskanje delt pri zapisovanju paketov je načrtovana izboljšava.