Clone, fetch, push
Pitmaster supporta le tre operazioni di rete fondamentali di git: clone (download iniziale), fetch (download incrementale) e push (caricamento). Gli URL SSH (ssh://..., user@host:path) usano il trasporto SSH; gli altri URL usano HTTP intelligente, mentre clone e fetch ripiegano su HTTP semplice quando lo scambio intelligente fallisce (vedere Protocolli).
Tutte e tre avviano trasporti di rete, quindi generano RuntimeException con un handle aperto con ['processes' => false] o 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);
Il nuovo repository usa sempre SHA-1. Se la clonazione fallisce e la destinazione non esisteva già, la directory creata parzialmente viene rimossa.
Procedura di clonazione
- Inizializzazione: crea un nuovo repository vuoto nel percorso di destinazione
- Configurazione del remoto: salva URL come
remote.origin.urle configura la refspec di fetch - Individuazione dei riferimenti: usa
GET /info/refs?service=git-upload-packper scoprire cosa contiene il server - Negoziazione: invia righe
wantper tutti i riferimenti remoti (deduplicate per hash) - Ricezione del pack: scarica il file pack contenente tutti gli oggetti richiesti
- Scrittura del pack: salva il file pack in
.git/objects/pack/ - Indicizzazione del pack: genera il file
.idxper ricerche rapide - Configurazione dei riferimenti: crea i rami di tracciamento remoto (
refs/remotes/origin/*) e i tag - Impostazione di HEAD: punta HEAD al ramo predefinito (dalla funzionalità
symref=HEAD:...del server, oppurerefs/heads/mainse il server non ne invia alcuna) e impostabranch.<name>.remoteebranch.<name>.merge - Checkout: esegue il checkout di quel ramo, scrivendo l'albero di lavoro e l'indice
Gli errori durante il checkout finale vengono intercettati e ignorati, quindi la clonazione può terminare con un albero di lavoro vuoto o parziale (#19).
$repo = Pitmaster::clone($url, $path);
$head = $repo->head();
echo $head->message;
$branches = $repo->branches();
$files = $repo->status();
Materializzazione dell'albero di lavoro
Dopo aver ricevuto il pack, Pitmaster chiama $repo->checkout() per il ramo predefinito. Questo scrive nella directory di lavoro ogni file contenuto nell'albero del commit HEAD e crea l'indice dalle voci dell'albero, così status() segnala che l'albero di lavoro è pulito.
Recuperare gli aggiornamenti
// 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
Procedura di fetch
- Leggere il remoto URL: da
.git/config(remote.<name>.url) - Individuare i riferimenti: ottenere lo stato corrente di tutti i riferimenti remoti
- Negoziare ciò che serve e ciò che si possiede:
- Da ottenere: riferimenti remoti corrispondenti alle refspec
remote.<name>.fetch(predefinite+refs/heads/*:refs/remotes/<name>/*) i cui oggetti mancano localmente (tutti, se viene specificata una profondità) - Già posseduti: punte di tutti i riferimenti locali
- Da ottenere: riferimenti remoti corrispondenti alle refspec
- Ricezione del pack: scaricare solo i nuovi oggetti
- Scrittura del pack: salvare in
.git/objects/pack/e crearne l'indice - Aggiornamento dei riferimenti di tracciamento: aggiornare
refs/remotes/<remote>/<branch>per ogni ramo remoto corrispondente e scrivere ogni riferimento ai tag annunciato. Gli oggetti tag non vengono richiesti separatamente, quindi un nuovo tag annotato può essere scritto come riferimento a un oggetto non scaricato (#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() cerca i nomi come refs/heads/<name>, refs/tags/<name> e come nome stesso, quindi non trova la forma abbreviata origin/main.
Pitmaster richiede pack sottili, che ancora non è in grado di indicizzare (#16). Se un server risponde a un fetch incrementale con un pack sottile, fetch() genera PackParseException.
Già aggiornato
Se tutti gli oggetti a cui puntano i riferimenti remoti esistono già in locale, non viene richiesto alcun pack e fetch() termina dopo aver individuato i riferimenti. In tal caso, i riferimenti di tracciamento non vengono aggiornati neanche se i rami remoti sono passati a commit già presenti in locale (#23).
Inviare le modifiche
$repo->push(); // Push current branch to 'origin'
$repo->push('origin', 'main'); // Push a specific branch
$repo->push('upstream', 'feature'); // Push to a named remote
Procedura di push
- Leggere il remoto URL: da
.git/config - Determinare cosa inviare: risolvere il ramo locale nel relativo hash di commit
- Individuare i riferimenti remoti: ottenere lo stato corrente del ramo remoto (per calcolare il vecchio hash)
- Verificare l'avanzamento rapido: se il ramo remoto non è un antenato del ramo locale, generare
RuntimeException("Push rejected: non-fast-forward update to <branch>")prima di inviare qualsiasi dato - Inviare l'aggiornamento del riferimento:
<old-hash> <new-hash> refs/heads/<branch> - Inviare i dati del pack: il pack contenente gli oggetti necessari al remoto
- Verificare il risultato: il server risponde indicando successo o fallimento per ciascun riferimento
L'hook pre-push viene eseguito prima dell'invio dell'aggiornamento, a meno che gli hook non siano disabilitati.
// Push the current branch
$repo->push();
// Push a specific branch to a specific remote
$repo->push('origin', 'feature/login');
Requisiti per il push
- Il ramo corrente deve esistere in locale
- Deve essere configurato un remoto con un URL
- Il remoto deve accettare il push (autenticazione, autorizzazioni, criteri per gli aggiornamenti non fast-forward)
Push forzato
Pitmaster invia l'hash precedente dall'annuncio live dei riferimenti del remote, mentre push() rifiuta localmente gli aggiornamenti non fast-forward. Per sovrascrivere un branch remoto, usate 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 (interni del 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);
La negoziazione want/have crea una richiesta 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
Il server risponde con gli acknowledgment e poi i dati del pack (eventualmente incapsulati side-band).
ReceivePackClient (interni del 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);
Formato della richiesta push:
<old-hash> <new-hash> refs/heads/main\0<capabilities>\n
0000
PACK<pack data>
Gestione degli errori
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() può anche generare un semplice RuntimeException (remote sconosciuto, handle senza processi) o PackParseException (un pack che non può essere indicizzato). Sia ProtocolException sia PackParseException estendono \RuntimeException, quindi intercettando \RuntimeException li coprite tutti.
Configurazione dei remote
I remote sono memorizzati in .git/config:
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
Accesso tramite la configurazione 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');
Dati del pack per il push
Attualmente, Pitmaster raccoglie gli oggetti raggiungibili dai commit inviati che non sono raggiungibili dai riferimenti correnti del remote e li scrive in un pack come oggetti di base (senza compressione delta). È funzionalmente corretto, ma produce pack più grandi rispetto a git push. La compressione delta durante la scrittura dei pack è un'ottimizzazione prevista.