
# Clone, Fetch, Push

Pitmaster mendukung tiga operasi jaringan git mendasar: clone (unduhan awal), fetch (unduhan bertahap), dan push (unggahan). URL SSH (`ssh://...`, `user@host:path`) menggunakan transport SSH; URL lainnya menggunakan HTTP pintar, dan clone serta fetch beralih ke HTTP biasa jika pertukaran pintar gagal (lihat [Protokol](https://pitmaster.inline0.dev/id/docs/network/protocols)).

Ketiganya memulai transport jaringan, sehingga akan menimbulkan `RuntimeException` pada handle yang dibuka dengan `['processes' => false]` atau `Pitmaster::processFreeOptions()`.

## Clone

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

Repositori baru selalu menggunakan SHA-1. Jika clone gagal dan tujuan belum ada sebelumnya, direktori yang baru dibuat sebagian akan dihapus.

### Alur clone

1. **Inisialisasi**: buat repositori kosong baru di jalur tujuan
2. **Konfigurasikan remote**: simpan URL sebagai `remote.origin.url` dan siapkan refspec fetch
3. **Temukan refs**: `GET /info/refs?service=git-upload-pack` untuk mengetahui isi server
4. **Negosiasi**: kirim baris `want` untuk semua refs remote (dideduplikasi berdasarkan hash)
5. **Terima pack**: unduh file pack yang berisi semua objek yang diminta
6. **Tulis pack**: simpan file pack ke `.git/objects/pack/`
7. **Indeks pack**: buat file `.idx` untuk pencarian cepat
8. **Siapkan refs**: buat branch pelacakan remote (`refs/remotes/origin/*`) dan tag
9. **Atur HEAD**: arahkan HEAD ke branch default (dari kapabilitas `symref=HEAD:...` server, atau `refs/heads/main` jika server tidak mengirimnya), lalu atur `branch.<name>.remote` dan `branch.<name>.merge`
10. **Checkout**: checkout branch tersebut, yang menulis pohon kerja dan indeks

Kesalahan saat checkout terakhir ditangkap dan diabaikan, sehingga clone dapat selesai dengan pohon kerja kosong atau sebagian ([#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();
```

### Membuat pohon kerja

Setelah menerima pack, Pitmaster memanggil `$repo->checkout()` untuk branch default. Proses ini menulis setiap file dari pohon commit HEAD ke direktori kerja dan membangun indeks dari entri pohon, sehingga `status()` melaporkan pohon kerja yang bersih.

## Fetch

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

### Alur fetch

1. **Baca URL remote**: dari `.git/config` (`remote.<name>.url`)
2. **Temukan refs**: dapatkan status terkini semua refs remote
3. **Negosiasikan want/have**:
   - Want: refs remote yang cocok dengan refspec `remote.<name>.fetch` (default `+refs/heads/*:refs/remotes/<name>/*`) dan objeknya belum ada secara lokal (semuanya jika kedalaman diberikan)
   - Have: setiap ujung ref lokal
4. **Terima pack**: unduh hanya objek baru
5. **Tulis pack**: simpan ke `.git/objects/pack/` dan buat indeksnya
6. **Perbarui refs pelacakan**: perbarui `refs/remotes/<remote>/<branch>` untuk setiap branch remote yang cocok, lalu tulis setiap ref tag yang diumumkan. Objek tag tidak diminta secara terpisah, sehingga tag beranotasi baru dapat ditulis sebagai ref ke objek yang belum diunduh ([#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()` mencari nama sebagai `refs/heads/<name>`, `refs/tags/<name>`, lalu nama itu sendiri, sehingga bentuk singkat `origin/main` tidak ditemukan.

Pitmaster meminta pack tipis, yang belum dapat diindeksnya ([#16](https://github.com/inline0/pitmaster/issues/16)). Server yang menjawab fetch bertahap dengan pack tipis akan menyebabkan `fetch()` menimbulkan `PackParseException`.

### Sudah mutakhir

Jika setiap objek yang dituju refs remote sudah ada secara lokal, pack tidak diminta dan `fetch()` selesai setelah penemuan ref. Dalam kasus tersebut, refs pelacakan juga tidak diperbarui, meskipun branch remote berpindah ke commit yang sudah Anda miliki ([#23](https://github.com/inline0/pitmaster/issues/23)).

## Push

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

### Alur push

1. **Baca URL remote**: dari `.git/config`
2. **Tentukan yang akan dikirim**: resolve branch lokal ke hash commit-nya
3. **Temukan refs remote**: dapatkan status terkini branch remote (untuk menghitung hash lama)
4. **Periksa fast-forward**: jika branch remote bukan leluhur branch lokal, timbulkan `RuntimeException("Push rejected: non-fast-forward update to <branch>")` sebelum mengirim apa pun
5. **Kirim pembaruan ref**: `<old-hash> <new-hash> refs/heads/<branch>`
6. **Kirim data pack**: pack yang berisi objek yang dibutuhkan remote
7. **Periksa hasil**: server merespons dengan status berhasil/gagal untuk setiap ref

Hook `pre-push` dijalankan sebelum pembaruan dikirim, kecuali hook dinonaktifkan.

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

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

### Persyaratan push

- Branch saat ini harus ada secara lokal
- Remote harus dikonfigurasi dengan URL
- Remote harus menerima push (autentikasi, izin, kebijakan non-fast-forward)

### Force push

Pitmaster mengirim hash lama dari iklan ref aktif remote, dan `push()` menolak pembaruan non-fast-forward secara lokal. Untuk menimpa cabang remote, gunakan 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 (internal 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);
```

Negosiasi want/have menyusun permintaan 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 merespons dengan acknowledgment, lalu data pack (mungkin dibungkus side-band).

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

Format permintaan push:

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

## Penanganan error

```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()` juga dapat melempar `RuntimeException` biasa (remote tidak dikenal, handle tanpa proses) atau `PackParseException` (pack yang tidak dapat diindeks). `ProtocolException` dan `PackParseException` sama-sama mewarisi `\RuntimeException`, jadi menangkap `\RuntimeException` akan mencakup semuanya.

## Konfigurasi remote

Remote disimpan di `.git/config`:

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

Akses melalui 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');
```

## Data pack untuk push

Saat ini, Pitmaster mengumpulkan objek yang dapat dijangkau dari commit yang di-push, tetapi tidak dapat dijangkau dari ref remote saat ini, lalu menuliskannya sebagai objek dasar (tanpa kompresi delta) ke dalam pack. Secara fungsi ini benar, tetapi menghasilkan pack yang lebih besar daripada `git push`. Kompresi delta saat menulis pack direncanakan sebagai optimasi.
