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

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

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

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

1. **Initialiseren**: maak een nieuwe lege repository aan op het bestemmingspad
2. **Remote configureren**: sla de URL op als `remote.origin.url` en stel de fetch-refspec in
3. **Refs vinden**: gebruik `GET /info/refs?service=git-upload-pack` om te achterhalen wat de server heeft
4. **Onderhandelen**: verstuur `want`-regels voor alle remote refs (dubbele hashes worden verwijderd)
5. **Pack ontvangen**: download het pack-bestand met alle opgevraagde objecten
6. **Pack opslaan**: sla het pack-bestand op in `.git/objects/pack/`
7. **Pack indexeren**: genereer het bestand `.idx` voor snelle zoekopdrachten
8. **Refs instellen**: maak remote tracking branches (`refs/remotes/origin/*`) en tags aan
9. **HEAD instellen**: laat HEAD verwijzen naar de standaardbranch (volgens de capability `symref=HEAD:...` van de server, of `refs/heads/main` als de server er geen verstuurt) en stel `branch.<name>.remote` en `branch.<name>.merge` in
10. **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](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();
```

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

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

### Verloop van een fetch

1. **Remote URL lezen**: uit `.git/config` (`remote.<name>.url`)
2. **Refs vinden**: de huidige status van alle remote refs ophalen
3. **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
4. **Pack ontvangen**: alleen de nieuwe objecten downloaden
5. **Pack opslaan**: opslaan in `.git/objects/pack/` en de index ervan opbouwen
6. **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](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()` 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](https://github.com/inline0/pitmaster/issues/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](https://github.com/inline0/pitmaster/issues/23)).

## Pushen

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

### Verloop van een push

1. **Remote URL lezen**: uit `.git/config`
2. **Bepalen wat er gepusht wordt**: de lokale branch oplossen naar de commit-hash
3. **Remote refs vinden**: de huidige status van de remote branch ophalen (om de oude hash te bepalen)
4. **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
5. **Ref-update versturen**: `<old-hash> <new-hash> refs/heads/<branch>`
6. **Packgegevens versturen**: pack met de objecten die de remote nodig heeft
7. **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.

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

```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 (fetch-internals)

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

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)

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

De indeling van het push-verzoek:

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

## Foutafhandeling

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

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

Toegang via de config 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');
```

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