Klonen, abrufen, übertragen
Pitmaster unterstützt die drei grundlegenden git-Netzwerkoperationen: Clone (erstmaliger Download), Fetch (inkrementeller Download) und Push (Upload). SSH-URLs (ssh://..., user@host:path) verwenden den Transport SSH; andere URLs nutzen Smart-HTTP. Wenn der Smart-Austausch fehlschlägt, greifen Clone und Fetch auf Dumb-HTTP zurück (siehe Protokolle).
Alle drei starten Netzwerktransporte und werfen daher RuntimeException, wenn ein Handle mit ['processes' => false] oder Pitmaster::processFreeOptions() geöffnet wurde.
Klonen
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);
Das neue Repository verwendet immer SHA-1. Schlägt das Klonen fehl und das Zielverzeichnis existierte vorher nicht, wird das teilweise erstellte Verzeichnis entfernt.
Ablauf des Klonens
- Initialisieren: Erstelle ein neues, leeres Repository am Zielpfad
- Remote konfigurieren: Speichere URL als
remote.origin.urlund richte die Fetch-Refspec ein - Refs ermitteln:
GET /info/refs?service=git-upload-pack, um herauszufinden, was auf dem Server vorhanden ist - Aushandeln: Sende
want-Zeilen für alle Remote-Refs (nach Hash dedupliziert) - Pack empfangen: Lade die Packdatei mit allen angeforderten Objekten herunter
- Pack schreiben: Speichere die Packdatei unter
.git/objects/pack/ - Pack indizieren: Erzeuge die Datei
.idxfür schnelle Suchvorgänge - Refs einrichten: Erstelle Remote-Tracking-Branches (
refs/remotes/origin/*) und Tags - HEAD setzen: Verweise mit HEAD auf den Standard-Branch (aus der
symref=HEAD:...-Fähigkeit des Servers oder aufrefs/heads/main, wenn der Server keine sendet) und setzebranch.<name>.remoteundbranch.<name>.merge - Checkout: Checke diesen Branch aus. Dabei werden der Arbeitsbaum und der Index geschrieben
Fehler beim abschließenden Checkout werden abgefangen und ignoriert. Daher kann das Klonen mit einem leeren oder unvollständigen Arbeitsbaum zurückkehren (#19).
$repo = Pitmaster::clone($url, $path);
$head = $repo->head();
echo $head->message;
$branches = $repo->branches();
$files = $repo->status();
Arbeitsbaum erstellen
Nach dem Empfang des Packs ruft Pitmaster für den Standard-Branch $repo->checkout() auf. Dadurch wird jede Datei im Baum des HEAD-Commits in das Arbeitsverzeichnis geschrieben und der Index aus den Baumeinträgen erstellt, sodass status() einen sauberen Arbeitsbaum meldet.
Abrufen
// 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
Ablauf des Abrufens
- Remote-URL lesen: aus
.git/config(remote.<name>.url) - Refs ermitteln: Ermittle den aktuellen Stand aller Remote-Refs
- Wants und Haves aushandeln:
- Want: Remote-Refs, auf die die
remote.<name>.fetch-Refspecs zutreffen (Standard:+refs/heads/*:refs/remotes/<name>/*) und deren Objekte lokal fehlen (bei angegebener Tiefe alle) - Have: jeder lokale Ref-Tip
- Want: Remote-Refs, auf die die
- Pack empfangen: Lade nur die neuen Objekte herunter
- Pack schreiben: Speichere es unter
.git/objects/pack/und erstelle seinen Index - Tracking-Refs aktualisieren: Aktualisiere
refs/remotes/<remote>/<branch>für jeden passenden Remote-Branch und schreibe alle angekündigten Tag-Refs. Tag-Objekte werden nicht separat angefordert. Ein neuer annotierter Tag kann daher als Ref auf ein nicht heruntergeladenes Objekt geschrieben werden (#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() sucht Namen als refs/heads/<name>, refs/tags/<name> und als den Namen selbst. Daher wird die Kurzform origin/main nicht gefunden.
Pitmaster fordert Thin Packs an, die es noch nicht indizieren kann (#16). Antwortet ein Server auf einen inkrementellen Abruf mit einem Thin Pack, wirft fetch() PackParseException.
Bereits aktuell
Wenn alle Objekte, auf die die Remote-Refs verweisen, bereits lokal vorhanden sind, wird kein Pack angefordert und fetch() kehrt nach dem Ermitteln der Refs zurück. In diesem Fall werden auch die Tracking-Refs nicht aktualisiert, selbst wenn sich die Remote-Branches auf Commits verschoben haben, die bereits lokal vorhanden sind (#23).
Übertragen
$repo->push(); // Push current branch to 'origin'
$repo->push('origin', 'main'); // Push a specific branch
$repo->push('upstream', 'feature'); // Push to a named remote
Ablauf der Übertragung
- Remote-URL lesen: aus
.git/config - Zu übertragende Änderungen bestimmen: Löse den lokalen Branch in seinen Commit-Hash auf
- Remote-Refs ermitteln: Ermittle den aktuellen Stand des Remote-Branches, um den alten Hash zu berechnen
- Fast-Forward prüfen: Ist der Remote-Branch kein Vorfahr des lokalen Branches, wirf
RuntimeException("Push rejected: non-fast-forward update to <branch>"), bevor etwas gesendet wird - Ref aktualisieren:
<old-hash> <new-hash> refs/heads/<branch> - Packdaten senden: Pack mit den Objekten, die der Remote benötigt
- Ergebnis prüfen: Der Server meldet Erfolg oder Fehlschlag für jeden Ref
Der Hook pre-push wird ausgeführt, bevor die Aktualisierung gesendet wird, sofern Hooks nicht deaktiviert sind.
// Push the current branch
$repo->push();
// Push a specific branch to a specific remote
$repo->push('origin', 'feature/login');
Voraussetzungen für die Übertragung
- Der aktuelle Branch muss lokal vorhanden sein
- Ein Remote muss mit einem URL konfiguriert sein
- Der Remote muss die Übertragung akzeptieren (Authentifizierung, Berechtigungen, Richtlinien für Nicht-Fast-Forward-Updates)
Erzwungene Übertragung
Pitmaster sendet den alten Hash aus der aktuellen Ref-Ankündigung des Remote, und push() weist Nicht-Fast-Forward-Updates lokal zurück. Um einen Remote-Branch zu überschreiben, verwende 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 (Interna des Abrufs)
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);
Die Want/Have-Aushandlung erstellt eine pkt-line-Anfrage:
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
Der Server antwortet mit Bestätigungen und anschließend den Packdaten (möglicherweise in Side-Band verpackt).
ReceivePackClient (Interna der Übertragung)
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);
Das Format der Übertragungsanfrage:
<old-hash> <new-hash> refs/heads/main\0<capabilities>\n
0000
PACK<pack data>
Fehlerbehandlung
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() kann auch ein einfaches RuntimeException (unbekannter Remote, Handle ohne Prozess) oder PackParseException (ein Pack, das nicht indiziert werden kann) werfen. ProtocolException und PackParseException erweitern beide \RuntimeException, sodass das Abfangen von \RuntimeException alle abdeckt.
Remote-Konfiguration
Remotes werden in .git/config gespeichert:
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
Zugriff über die Konfiguration 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');
Packdaten für die Übertragung
Derzeit sammelt Pitmaster alle Objekte, die von den übertragenen Commits aus erreichbar, aber von den aktuellen Refs des Remote aus nicht erreichbar sind, und schreibt sie als Basisobjekte (ohne Delta-Komprimierung) in ein Pack. Das ist funktional korrekt, erzeugt aber größere Packs als git push. Delta-Komprimierung beim Schreiben von Packs ist als Optimierung geplant.