Clonen, fetchen, pushen
Pitmaster ondersteunt de drie fundamentele git-netwerkbewerkingen: clone (eerste download), fetch (aanvullende download) en push (upload). URL's SSH (ssh://..., user@host:path) gebruiken het transport SSH; andere URL's gebruiken smart HTTP, en clone en fetch vallen terug op dumb HTTP wanneer de smart-uitwisseling mislukt (zie Protocollen).
Alle drie starten netwerktransporten en geven daarom RuntimeException als je ze gebruikt met een handle die is geopend met ['processes' => false] of Pitmaster::processFreeOptions().
Clonen
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);
De nieuwe repository gebruikt altijd SHA-1. Als het clonen mislukt en de bestemming nog niet bestond, wordt de gedeeltelijk aangemaakte map verwijderd.
Verloop van een clone
- Initialiseren: maak een nieuwe lege repository aan op het bestemmingspad
- Remote configureren: sla de URL op als
remote.origin.urlen stel de fetch-refspec in - Refs vinden: gebruik
GET /info/refs?service=git-upload-packom te achterhalen wat de server heeft - Onderhandelen: verstuur
want-regels voor alle remote refs (dubbele hashes worden verwijderd) - Pack ontvangen: download het pack-bestand met alle opgevraagde objecten
- Pack opslaan: sla het pack-bestand op in
.git/objects/pack/ - Pack indexeren: genereer het bestand
.idxvoor snelle zoekopdrachten - Refs instellen: maak remote tracking branches (
refs/remotes/origin/*) en tags aan - HEAD instellen: laat HEAD verwijzen naar de standaardbranch (volgens de capability
symref=HEAD:...van de server, ofrefs/heads/mainals de server er geen verstuurt) en stelbranch.<name>.remoteenbranch.<name>.mergein - Checkout uitvoeren: check die branch uit; hiermee worden de werkboom en de index geschreven
Fouten tijdens de laatste checkout worden opgevangen en genegeerd. Daardoor kan een clone eindigen met een lege of gedeeltelijke werkboom (#19).
$repo = Pitmaster::clone($url, $path);
$head = $repo->head();
echo $head->message;
$branches = $repo->branches();
$files = $repo->status();
De werkboom materialiseren
Na ontvangst van het pack roept Pitmaster $repo->checkout() aan voor de standaardbranch. Daarmee wordt elk bestand uit de tree van de HEAD-commit naar de werkdirectory geschreven en de index opgebouwd uit de tree-items, zodat status() een schone werkboom meldt.
Fetchen
// 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
Verloop van een fetch
- Remote URL lezen: uit
.git/config(remote.<name>.url) - Refs vinden: de huidige status van alle remote refs ophalen
- Wants/haves afstemmen:
- Want: remote refs die overeenkomen met de
remote.<name>.fetch-refspecs (standaard+refs/heads/*:refs/remotes/<name>/*) en waarvan de objecten lokaal ontbreken (alle refs wanneer een diepte is opgegeven) - Have: elke lokale ref-tip
- Want: remote refs die overeenkomen met de
- Pack ontvangen: alleen de nieuwe objecten downloaden
- Pack opslaan: opslaan in
.git/objects/pack/en de index ervan opbouwen - Tracking refs bijwerken:
refs/remotes/<remote>/<branch>bijwerken voor elke overeenkomende remote branch en elke aangekondigde tag-ref schrijven. Tagobjecten worden niet afzonderlijk opgevraagd, dus een nieuwe geannoteerde tag kan worden geschreven als een ref naar een object dat niet is gedownload (#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() zoekt namen op als refs/heads/<name>, refs/tags/<name> en de naam zelf. Daarom wordt de korte vorm origin/main niet gevonden.
Pitmaster vraagt om thin packs, die het nog niet kan indexeren (#16). Als een server een incrementele fetch beantwoordt met een thin pack, geeft fetch() PackParseException.
Al bijgewerkt
Als elk object waar de remote refs naar verwijzen al lokaal bestaat, wordt er geen pack opgevraagd en keert fetch() terug na het vinden van refs. In dat geval worden ook de tracking refs niet bijgewerkt, zelfs niet als de remote branches zijn verplaatst naar commits die je al hebt (#23).
Pushen
$repo->push(); // Push current branch to 'origin'
$repo->push('origin', 'main'); // Push a specific branch
$repo->push('upstream', 'feature'); // Push to a named remote
Verloop van een push
- Remote URL lezen: uit
.git/config - Bepalen wat er gepusht wordt: de lokale branch oplossen naar de commit-hash
- Remote refs vinden: de huidige status van de remote branch ophalen (om de oude hash te bepalen)
- Fast-forward controleren: als de remote branch geen voorouder is van de lokale branch,
RuntimeException("Push rejected: non-fast-forward update to <branch>")geven voordat er iets wordt verstuurd - Ref-update versturen:
<old-hash> <new-hash> refs/heads/<branch> - Packgegevens versturen: pack met de objecten die de remote nodig heeft
- Resultaat controleren: de server antwoordt met succes of mislukking per ref
De hook pre-push wordt uitgevoerd voordat de update wordt verstuurd, tenzij hooks zijn uitgeschakeld.
// Push the current branch
$repo->push();
// Push a specific branch to a specific remote
$repo->push('origin', 'feature/login');
Vereisten voor pushen
- De huidige branch moet lokaal bestaan
- Er moet een remote zijn geconfigureerd met een URL
- De remote moet de push accepteren (authenticatie, machtigingen, beleid voor niet-fast-forward-updates)
Force push
Pitmaster stuurt de oude hash uit de live ref-advertentie van de remote, en push() weigert lokaal updates die geen fast-forward zijn. Gebruik force-with-lease om een remote branch te overschrijven:
// 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 (fetch-internals)
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);
De want/have-onderhandeling bouwt een pkt-line-verzoek op:
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
De server reageert met bevestigingen en daarna de pack-data (mogelijk verpakt in side-band).
ReceivePackClient (push-internals)
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);
De indeling van het push-verzoek:
<old-hash> <new-hash> refs/heads/main\0<capabilities>\n
0000
PACK<pack data>
Foutafhandeling
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() kan ook een gewone RuntimeException (onbekende remote, handle zonder proces) of PackParseException (een pack die niet kan worden geïndexeerd) gooien. ProtocolException en PackParseException breiden beide \RuntimeException uit, dus met het opvangen van \RuntimeException vangt u ze allemaal op.
Configuratie van remotes
Remotes worden opgeslagen in .git/config:
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
Toegang via de config 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');
Pack-data voor push
Momenteel verzamelt Pitmaster de objecten die bereikbaar zijn vanaf de commits die worden gepusht, maar niet vanaf de huidige refs van de remote, en schrijft die als basisobjecten (zonder deltacompressie) naar een pack. Dit werkt correct, maar levert grotere packs op dan git push. Deltacompressie bij het schrijven van packs is een geplande optimalisatie.