
# Klonování, načítání, odesílání

Pitmaster podporuje tři základní síťové operace gitu: clone (počáteční stažení), fetch (průběžné stahování) a push (odeslání). Adresy URL SSH (`ssh://...`, `user@host:path`) používají přenos SSH; ostatní adresy URL používají chytrý HTTP a clone i fetch při selhání chytré výměny přecházejí na jednoduchý HTTP (viz [Protokoly](https://pitmaster.inline0.dev/cs/docs/network/protocols)).

Všechny tři operace spouštějí síťové přenosy, proto vyvolají `RuntimeException` u objektu otevřeného pomocí `['processes' => false]` nebo `Pitmaster::processFreeOptions()`.

## Klonování

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

Nové úložiště vždy používá SHA-1. Pokud klonování selže a cílová cesta předtím neexistovala, částečně vytvořený adresář se odstraní.

### Průběh klonování

1. **Inicializace**: vytvoření nového prázdného úložiště v cílové cestě
2. **Konfigurace vzdáleného úložiště**: uložení URL jako `remote.origin.url` a nastavení refspec pro načítání
3. **Vyhledání odkazů**: `GET /info/refs?service=git-upload-pack` zjistí, co server obsahuje
4. **Vyjednání**: odeslání řádků `want` pro všechny vzdálené odkazy (hashy se deduplikují)
5. **Přijetí packu**: stažení souboru pack se všemi požadovanými objekty
6. **Zápis packu**: uložení souboru pack do `.git/objects/pack/`
7. **Indexování packu**: vytvoření souboru `.idx` pro rychlé vyhledávání
8. **Nastavení odkazů**: vytvoření vzdálených sledovacích větví (`refs/remotes/origin/*`) a štítků
9. **Nastavení HEAD**: nasměrování HEAD na výchozí větev (podle schopnosti `symref=HEAD:...` serveru, nebo `refs/heads/main`, pokud server žádnou nepošle) a nastavení `branch.<name>.remote` a `branch.<name>.merge`
10. **Checkout**: přepnutí na tuto větev, čímž se zapíše pracovní strom a index

Chyby při závěrečném checkoutu se zachytí a ignorují, takže se klonování může dokončit s prázdným nebo neúplným pracovním stromem ([#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();
```

### Vytvoření pracovního stromu

Po přijetí packu zavolá Pitmaster metodu `$repo->checkout()` pro výchozí větev. Ta zapíše všechny soubory ze stromu commitu HEAD do pracovního adresáře a vytvoří index z položek stromu, takže `status()` oznámí čistý pracovní strom.

## Načítání

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

### Průběh načítání

1. **Načtení vzdáleného URL**: z `.git/config` (`remote.<name>.url`)
2. **Vyhledání odkazů**: získání aktuálního stavu všech vzdálených odkazů
3. **Vyjednání požadovaných/dostupných objektů**:
   - Požadované: vzdálené odkazy odpovídající refspecům `remote.<name>.fetch` (výchozí `+refs/heads/*:refs/remotes/<name>/*`), jejichž objekty místně chybí (při zadání hloubky všechny)
   - Dostupné: každý vrchol místního odkazu
4. **Přijetí packu**: stažení pouze nových objektů
5. **Zápis packu**: uložení do `.git/objects/pack/` a vytvoření jeho indexu
6. **Aktualizace sledovacích odkazů**: aktualizace `refs/remotes/<remote>/<branch>` pro každou odpovídající vzdálenou větev a zápis všech zveřejněných odkazů na štítky. Objekty štítků se nevyžadují samostatně, takže nový anotovaný štítek lze zapsat jako odkaz na objekt, který nebyl stažen ([#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()` vyhledává názvy jako `refs/heads/<name>`, `refs/tags/<name>` a samotný název, takže krátký tvar `origin/main` se nenajde.

Pitmaster požaduje tenké packy, které zatím neumí indexovat ([#16](https://github.com/inline0/pitmaster/issues/16)). Pokud server odpoví na průběžné načtení tenkým packem, `fetch()` vyvolá `PackParseException`.

### Již aktuální

Pokud všechny objekty, na které ukazují vzdálené odkazy, již existují místně, pack se nevyžádá a `fetch()` se po vyhledání odkazů vrátí. V takovém případě se neaktualizují ani sledovací odkazy, a to ani v případě, že se vzdálené větve přesunuly na commity, které již máte ([#23](https://github.com/inline0/pitmaster/issues/23)).

## Odesílání

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

### Průběh odesílání

1. **Načíst vzdálené URL**: z `.git/config`
2. **Určit, co odeslat**: zjistit hash commitu místní větve
3. **Zjistit vzdálené reference**: získat aktuální stav vzdálené větve (pro výpočet starého hashe)
4. **Ověřit posun vpřed**: pokud vzdálená větev není předkem místní větve, vyhodit `RuntimeException("Push rejected: non-fast-forward update to <branch>")` ještě před odesláním čehokoli
5. **Odeslat aktualizaci reference**: `<old-hash> <new-hash> refs/heads/<branch>`
6. **Odeslat data packu**: pack obsahující objekty, které vzdálený server potřebuje
7. **Ověřit výsledek**: server vrátí úspěch nebo neúspěch pro každou referenci

Hook `pre-push` se spustí před odesláním aktualizace, pokud jsou hooky povolené.

```php
// Push the current branch
$repo->push();

// Push a specific branch to a specific remote
$repo->push('origin', 'feature/login');
```

### Požadavky pro push

- Aktuální větev musí existovat lokálně
- Musí být nakonfigurována vzdálená repozitář s URL
- Vzdálený server musí push přijmout (ověření, oprávnění, zásady pro aktualizace bez posunu vpřed)

### Vynucený push

Pitmaster odešle starý hash z aktuální inzerce vzdálené reference a `push()` lokálně odmítá aktualizace bez posunu vpřed. Chcete-li přepsat vzdálenou větev, použijte 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 (interní funkce fetch)

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

Vyjednávání want/have sestaví požadavek ve formátu 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
```

Server odpoví potvrzeními a poté daty packu (případně zabalenými do side-band).

## ReceivePackClient (interní funkce push)

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

Formát požadavku push:

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

## Zpracování chyb

```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()` může také vyhodit běžnou `RuntimeException` (neznámý vzdálený server, handle bez procesu) nebo `PackParseException` (pack, který nelze indexovat). `ProtocolException` i `PackParseException` rozšiřují `\RuntimeException`, takže zachycení `\RuntimeException` pokryje všechny tyto případy.

## Konfigurace vzdálených repozitářů

Vzdálené repozitáře jsou uloženy v `.git/config`:

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

Přístup přes konfiguraci 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');
```

## Data packu pro push

V současnosti Pitmaster shromažďuje objekty dosažitelné z odesílaných commitů, které nejsou dosažitelné z aktuálních vzdálených referencí, a zapisuje je do packu jako základní objekty (bez delta komprese). Funkčně je to správné, ale vznikají větší packy než u `git push`. Delta komprese při zápisu packu je plánovaným vylepšením.
