
# Clone, fetch, push

Pitmaster supporta le tre operazioni di rete fondamentali di git: clone (download iniziale), fetch (download incrementale) e push (caricamento). Gli URL SSH (`ssh://...`, `user@host:path`) usano il trasporto SSH; gli altri URL usano HTTP intelligente, mentre clone e fetch ripiegano su HTTP semplice quando lo scambio intelligente fallisce (vedere [Protocolli](https://pitmaster.inline0.dev/it/docs/network/protocols)).

Tutte e tre avviano trasporti di rete, quindi generano `RuntimeException` con un handle aperto con `['processes' => false]` o `Pitmaster::processFreeOptions()`.

## Clonare

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

Il nuovo repository usa sempre SHA-1. Se la clonazione fallisce e la destinazione non esisteva già, la directory creata parzialmente viene rimossa.

### Procedura di clonazione

1. **Inizializzazione**: crea un nuovo repository vuoto nel percorso di destinazione
2. **Configurazione del remoto**: salva URL come `remote.origin.url` e configura la refspec di fetch
3. **Individuazione dei riferimenti**: usa `GET /info/refs?service=git-upload-pack` per scoprire cosa contiene il server
4. **Negoziazione**: invia righe `want` per tutti i riferimenti remoti (deduplicate per hash)
5. **Ricezione del pack**: scarica il file pack contenente tutti gli oggetti richiesti
6. **Scrittura del pack**: salva il file pack in `.git/objects/pack/`
7. **Indicizzazione del pack**: genera il file `.idx` per ricerche rapide
8. **Configurazione dei riferimenti**: crea i rami di tracciamento remoto (`refs/remotes/origin/*`) e i tag
9. **Impostazione di HEAD**: punta HEAD al ramo predefinito (dalla funzionalità `symref=HEAD:...` del server, oppure `refs/heads/main` se il server non ne invia alcuna) e imposta `branch.<name>.remote` e `branch.<name>.merge`
10. **Checkout**: esegue il checkout di quel ramo, scrivendo l'albero di lavoro e l'indice

Gli errori durante il checkout finale vengono intercettati e ignorati, quindi la clonazione può terminare con un albero di lavoro vuoto o parziale ([#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();
```

### Materializzazione dell'albero di lavoro

Dopo aver ricevuto il pack, Pitmaster chiama `$repo->checkout()` per il ramo predefinito. Questo scrive nella directory di lavoro ogni file contenuto nell'albero del commit HEAD e crea l'indice dalle voci dell'albero, così `status()` segnala che l'albero di lavoro è pulito.

## Recuperare gli aggiornamenti

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

### Procedura di fetch

1. **Leggere il remoto URL**: da `.git/config` (`remote.<name>.url`)
2. **Individuare i riferimenti**: ottenere lo stato corrente di tutti i riferimenti remoti
3. **Negoziare ciò che serve e ciò che si possiede**:
   - Da ottenere: riferimenti remoti corrispondenti alle refspec `remote.<name>.fetch` (predefinite `+refs/heads/*:refs/remotes/<name>/*`) i cui oggetti mancano localmente (tutti, se viene specificata una profondità)
   - Già posseduti: punte di tutti i riferimenti locali
4. **Ricezione del pack**: scaricare solo i nuovi oggetti
5. **Scrittura del pack**: salvare in `.git/objects/pack/` e crearne l'indice
6. **Aggiornamento dei riferimenti di tracciamento**: aggiornare `refs/remotes/<remote>/<branch>` per ogni ramo remoto corrispondente e scrivere ogni riferimento ai tag annunciato. Gli oggetti tag non vengono richiesti separatamente, quindi un nuovo tag annotato può essere scritto come riferimento a un oggetto non scaricato ([#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()` cerca i nomi come `refs/heads/<name>`, `refs/tags/<name>` e come nome stesso, quindi non trova la forma abbreviata `origin/main`.

Pitmaster richiede pack sottili, che ancora non è in grado di indicizzare ([#16](https://github.com/inline0/pitmaster/issues/16)). Se un server risponde a un fetch incrementale con un pack sottile, `fetch()` genera `PackParseException`.

### Già aggiornato

Se tutti gli oggetti a cui puntano i riferimenti remoti esistono già in locale, non viene richiesto alcun pack e `fetch()` termina dopo aver individuato i riferimenti. In tal caso, i riferimenti di tracciamento non vengono aggiornati neanche se i rami remoti sono passati a commit già presenti in locale ([#23](https://github.com/inline0/pitmaster/issues/23)).

## Inviare le modifiche

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

### Procedura di push

1. **Leggere il remoto URL**: da `.git/config`
2. **Determinare cosa inviare**: risolvere il ramo locale nel relativo hash di commit
3. **Individuare i riferimenti remoti**: ottenere lo stato corrente del ramo remoto (per calcolare il vecchio hash)
4. **Verificare l'avanzamento rapido**: se il ramo remoto non è un antenato del ramo locale, generare `RuntimeException("Push rejected: non-fast-forward update to <branch>")` prima di inviare qualsiasi dato
5. **Inviare l'aggiornamento del riferimento**: `<old-hash> <new-hash> refs/heads/<branch>`
6. **Inviare i dati del pack**: il pack contenente gli oggetti necessari al remoto
7. **Verificare il risultato**: il server risponde indicando successo o fallimento per ciascun riferimento

L'hook `pre-push` viene eseguito prima dell'invio dell'aggiornamento, a meno che gli hook non siano disabilitati.

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

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

### Requisiti per il push

- Il ramo corrente deve esistere in locale
- Deve essere configurato un remoto con un URL
- Il remoto deve accettare il push (autenticazione, autorizzazioni, criteri per gli aggiornamenti non fast-forward)

### Push forzato

Pitmaster invia l'hash precedente dall'annuncio live dei riferimenti del remote, mentre `push()` rifiuta localmente gli aggiornamenti non fast-forward. Per sovrascrivere un branch remoto, usate 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 (interni del 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);
```

La negoziazione want/have crea una richiesta 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
```

Il server risponde con gli acknowledgment e poi i dati del pack (eventualmente incapsulati side-band).

## ReceivePackClient (interni del 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);
```

Formato della richiesta push:

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

## Gestione degli errori

```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()` può anche generare un semplice `RuntimeException` (remote sconosciuto, handle senza processi) o `PackParseException` (un pack che non può essere indicizzato). Sia `ProtocolException` sia `PackParseException` estendono `\RuntimeException`, quindi intercettando `\RuntimeException` li coprite tutti.

## Configurazione dei remote

I remote sono memorizzati in `.git/config`:

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

Accesso tramite la configurazione 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');
```

## Dati del pack per il push

Attualmente, Pitmaster raccoglie gli oggetti raggiungibili dai commit inviati che non sono raggiungibili dai riferimenti correnti del remote e li scrive in un pack come oggetti di base (senza compressione delta). È funzionalmente corretto, ma produce pack più grandi rispetto a `git push`. La compressione delta durante la scrittura dei pack è un'ottimizzazione prevista.
