Clonare, preluare, trimitere
Pitmaster acceptă cele trei operații fundamentale de rețea git: clonare (descărcare inițială), preluare (descărcare incrementală) și trimitere (încărcare). URL-urile SSH (ssh://..., user@host:path) folosesc transportul SSH; celelalte URL-uri folosesc smart HTTP, iar clonarea și preluarea revin la dumb HTTP dacă schimbul smart eșuează (consultați Protocoale).
Toate cele trei pornesc transporturi de rețea, așadar aruncă RuntimeException dacă instanța a fost deschisă cu ['processes' => false] sau Pitmaster::processFreeOptions().
Clonare
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);
Noul depozit folosește întotdeauna SHA-1. Dacă clonarea eșuează și destinația nu exista înainte, directorul creat parțial este șters.
Fluxul clonării
- Inițializare: creează un depozit nou, gol, la calea destinației
- Configurarea depozitului la distanță: salvează URL ca
remote.origin.urlși configurează refspec-ul de preluare - Descoperirea referințelor:
GET /info/refs?service=git-upload-packpentru a afla ce conține serverul - Negociere: trimite liniile
wantpentru toate referințele la distanță (deduplicate după hash) - Primirea fișierului pack: descarcă fișierul pack care conține toate obiectele solicitate
- Scrierea fișierului pack: salvează fișierul pack în
.git/objects/pack/ - Indexarea fișierului pack: generează fișierul
.idxpentru căutări rapide - Configurarea referințelor: creează ramuri de urmărire la distanță (
refs/remotes/origin/*) și tag-uri - Setarea HEAD: indică ramura implicită către HEAD (din capabilitatea
symref=HEAD:...a serverului saurefs/heads/maindacă serverul nu trimite niciuna) și seteazăbranch.<name>.remoteșibranch.<name>.merge - Checkout: face checkout la ramura respectivă, ceea ce scrie arborele de lucru și indexul
Erorile din timpul ultimului checkout sunt prinse și ignorate, așadar clonarea se poate încheia cu un arbore de lucru gol sau parțial (#19).
$repo = Pitmaster::clone($url, $path);
$head = $repo->head();
echo $head->message;
$branches = $repo->branches();
$files = $repo->status();
Materializarea arborelui de lucru
După primirea fișierului pack, Pitmaster apelează $repo->checkout() pentru ramura implicită. Aceasta scrie fiecare fișier din arborele commit-ului HEAD în directorul de lucru și creează indexul din intrările arborelui, astfel încât status() raportează un arbore de lucru curat.
Preluare
// 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
Fluxul preluării
- Citirea depozitului la distanță URL: din
.git/config(remote.<name>.url) - Descoperirea referințelor: obține starea curentă a tuturor referințelor la distanță
- Negocierea obiectelor solicitate/deținute:
- Solicitate: referințele la distanță potrivite cu refspec-urile
remote.<name>.fetch(implicit+refs/heads/*:refs/remotes/<name>/*), ale căror obiecte lipsesc local (toate dacă este specificată o adâncime) - Deținute: fiecare vârf al referințelor locale
- Solicitate: referințele la distanță potrivite cu refspec-urile
- Primirea fișierului pack: descarcă numai obiectele noi
- Scrierea fișierului pack: îl salvează în
.git/objects/pack/și îi creează indexul - Actualizarea referințelor de urmărire: actualizează
refs/remotes/<remote>/<branch>pentru fiecare ramură la distanță potrivită și scrie fiecare referință de tag anunțată. Obiectele tag-urilor nu sunt solicitate separat, așadar un tag adnotat nou poate fi scris ca referință către un obiect care nu a fost descărcat (#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() caută numele ca refs/heads/<name>, refs/tags/<name> și numele în sine, astfel că forma scurtă origin/main nu este găsită.
Pitmaster solicită fișiere pack subțiri, pe care încă nu le poate indexa (#16). Dacă un server răspunde unei preluări incrementale cu un fișier pack subțire, fetch() aruncă PackParseException.
Actualizat deja
Dacă fiecare obiect indicat de referințele la distanță există deja local, nu se solicită niciun fișier pack, iar fetch() returnează după descoperirea referințelor. În acest caz, nici referințele de urmărire nu sunt actualizate, chiar dacă ramurile la distanță au avansat către commit-uri pe care le aveți deja (#23).
Trimitere
$repo->push(); // Push current branch to 'origin'
$repo->push('origin', 'main'); // Push a specific branch
$repo->push('upstream', 'feature'); // Push to a named remote
Fluxul trimiterii
- Citirea depozitului la distanță URL: din
.git/config - Determinarea elementelor de trimis: rezolvă ramura locală la hash-ul commit-ului său
- Descoperirea referințelor la distanță: obține starea curentă a ramurii la distanță (pentru calcularea hash-ului vechi)
- Verificarea avansării rapide: dacă ramura la distanță nu este un strămoș al ramurii locale, aruncă
RuntimeException("Push rejected: non-fast-forward update to <branch>")înainte de a trimite ceva - Trimiterea actualizării referinței:
<old-hash> <new-hash> refs/heads/<branch> - Trimiterea datelor pack: fișier pack cu obiectele de care are nevoie depozitul la distanță
- Verificarea rezultatului: serverul răspunde cu succes/eșec pentru fiecare referință
Hook-ul pre-push rulează înainte de trimiterea actualizării, cu excepția cazului în care hook-urile sunt dezactivate.
// Push the current branch
$repo->push();
// Push a specific branch to a specific remote
$repo->push('origin', 'feature/login');
Cerințe pentru trimitere
- Ramura curentă trebuie să existe local
- Un depozit la distanță trebuie să fie configurat cu un URL
- Depozitul la distanță trebuie să accepte trimiterea (autentificare, permisiuni, politici pentru actualizări care nu sunt avansări rapide)
Trimitere forțată
Pitmaster trimite hash-ul vechi din anunțul live al referinței la distanță, iar push() refuză local actualizările care nu sunt avansări rapide. Pentru a suprascrie o ramură la distanță, folosiți 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 (elemente interne ale preluării)
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);
Negocierea obiectelor solicitate/deținute construiește o cerere 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
Serverul răspunde cu confirmări, apoi trimite datele pack (posibil încapsulate cu side-band).
ReceivePackClient (elemente interne ale trimiterii)
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);
Formatul cererii de trimitere:
<old-hash> <new-hash> refs/heads/main\0<capabilities>\n
0000
PACK<pack data>
Tratarea erorilor
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() poate arunca și o RuntimeException simplă (depozit la distanță necunoscut, instanță fără procese) sau PackParseException (un fișier pack care nu poate fi indexat). ProtocolException și PackParseException extind ambele \RuntimeException, așadar prinderea lui \RuntimeException le acoperă pe toate.
Configurarea depozitelor la distanță
Depozitele la distanță sunt stocate în .git/config:
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
Accesați prin API de configurare:
$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');
Datele pack pentru trimitere
În prezent, Pitmaster colectează obiectele la care se poate ajunge din commit-urile trimise și care nu sunt accesibile din referințele actuale ale depozitului la distanță, apoi le scrie într-un fișier pack ca obiecte de bază (fără comprimare delta). Metoda este corectă funcțional, dar produce fișiere pack mai mari decât git push. Comprimarea delta la scrierea fișierelor pack este o optimizare planificată.