
# 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](https://pitmaster.inline0.dev/de/docs/network/protocols)).

Alle drei starten Netzwerktransporte und werfen daher `RuntimeException`, wenn ein Handle mit `['processes' => false]` oder `Pitmaster::processFreeOptions()` geöffnet wurde.

## Klonen

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

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

1. **Initialisieren**: Erstelle ein neues, leeres Repository am Zielpfad
2. **Remote konfigurieren**: Speichere URL als `remote.origin.url` und richte die Fetch-Refspec ein
3. **Refs ermitteln**: `GET /info/refs?service=git-upload-pack`, um herauszufinden, was auf dem Server vorhanden ist
4. **Aushandeln**: Sende `want`-Zeilen für alle Remote-Refs (nach Hash dedupliziert)
5. **Pack empfangen**: Lade die Packdatei mit allen angeforderten Objekten herunter
6. **Pack schreiben**: Speichere die Packdatei unter `.git/objects/pack/`
7. **Pack indizieren**: Erzeuge die Datei `.idx` für schnelle Suchvorgänge
8. **Refs einrichten**: Erstelle Remote-Tracking-Branches (`refs/remotes/origin/*`) und Tags
9. **HEAD setzen**: Verweise mit HEAD auf den Standard-Branch (aus der `symref=HEAD:...`-Fähigkeit des Servers oder auf `refs/heads/main`, wenn der Server keine sendet) und setze `branch.<name>.remote` und `branch.<name>.merge`
10. **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](https://github.com/inline0/pitmaster/issues/19)).

```php
$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

```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
```

### Ablauf des Abrufens

1. **Remote-URL lesen**: aus `.git/config` (`remote.<name>.url`)
2. **Refs ermitteln**: Ermittle den aktuellen Stand aller Remote-Refs
3. **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
4. **Pack empfangen**: Lade nur die neuen Objekte herunter
5. **Pack schreiben**: Speichere es unter `.git/objects/pack/` und erstelle seinen Index
6. **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](https://github.com/inline0/pitmaster/issues/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()` 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](https://github.com/inline0/pitmaster/issues/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](https://github.com/inline0/pitmaster/issues/23)).

## Übertragen

```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
```

### Ablauf der Übertragung

1. **Remote-URL lesen**: aus `.git/config`
2. **Zu übertragende Änderungen bestimmen**: Löse den lokalen Branch in seinen Commit-Hash auf
3. **Remote-Refs ermitteln**: Ermittle den aktuellen Stand des Remote-Branches, um den alten Hash zu berechnen
4. **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
5. **Ref aktualisieren**: `<old-hash> <new-hash> refs/heads/<branch>`
6. **Packdaten senden**: Pack mit den Objekten, die der Remote benötigt
7. **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.

```php
// 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:

```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 (Interna des Abrufs)

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

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)

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

Das Format der Übertragungsanfrage:

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

## Fehlerbehandlung

```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()` 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:

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

Zugriff über die Konfiguration 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');
```

## 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.
