
# Git LFS

Git Large File Storage (LFS) mengganti file besar di repositori Anda dengan file pointer kecil. Konten file sebenarnya disimpan di server LFS terpisah. Pitmaster dapat mengurai dan membuat pointer LFS serta berkomunikasi dengan server LFS melalui API batch `.gitattributes` dan tidak membaca `add()` atau menjalankan filter clean dan smudge LFS: `checkout()` menyimpan file apa adanya, dan menulis file pointer. Pengambilan konten sebenarnya menjadi tanggung jawab kode Anda, seperti yang ditunjukkan di bawah.

## File Pointer LFS

Saat file dilacak oleh LFS, git menyimpan file pointer sebagai pengganti konten sebenarnya:

```
version https://git-lfs.github.com/spec/v1
oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393
size 12345
```

Pointer berisi:
- `version`: versi spesifikasi LFS URL
- `oid`: hash SHA-256 dari konten file sebenarnya (diawali dengan `sha256:`)
- `size`: ukuran file sebenarnya dalam byte

### Mengurai pointer

```php
use Pitmaster\Lfs\LfsPointer;

$content = file_get_contents('.git-lfs-tracked-file');
$pointer = LfsPointer::parse($content);

if ($pointer !== null) {
    echo "OID: {$pointer->oid}\n";
    echo "Size: {$pointer->size}\n";
    echo "Version: {$pointer->version}\n";
}
```

`parse()` mengembalikan `null` jika baris `version`, `oid sha256:`, atau `size` tidak ada. Nilainya tidak divalidasi (OID non-heksadesimal atau ukuran nonnumerik tetap diterima).

### Mendeteksi pointer

```php
if (LfsPointer::isPointer($blobContent)) {
    // This blob is an LFS pointer, not actual file content
    $pointer = LfsPointer::parse($blobContent);
}
```

`isPointer()` memeriksa keberadaan field version URL, `oid sha256:`, dan `size` tanpa parsing lengkap.

### Membuat pointer

```php
// From known OID and size
$pointer = new LfsPointer(
    oid: '4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393',
    size: 12345,
);

// From file content (computes SHA-256 hash)
$pointer = LfsPointer::forContent($actualFileContent);

echo $pointer->oid;   // SHA-256 hash
echo $pointer->size;  // Content length
```

### Membuat serialisasi pointer

```php
$content = $pointer->serialize();
// "version https://git-lfs.github.com/spec/v1\noid sha256:4d7a21...\nsize 12345\n"
```

## Klien LFS

`LfsClient` berkomunikasi dengan server LFS menggunakan API batch (POST ke `<url>/objects/batch`).

### Membuat klien

```php
use Pitmaster\Lfs\LfsClient;

// From a git remote URL (appends /info/lfs)
$client = LfsClient::fromRemoteUrl('https://github.com/user/repo.git');
// LFS URL: https://github.com/user/repo.git/info/lfs

// Direct URL
$client = new LfsClient('https://lfs.example.com/repo', timeout: 60);
```

Metode `fromRemoteUrl()` mengikuti konvensi standar: menambahkan `/info/lfs` ke URL git (menambahkan `.git` jika belum ada). Hanya URL remote HTTP(S) yang menghasilkan URL LFS yang dapat digunakan; remote SSH bergaya SCP (`git@host:repo.git`) tidak bisa digunakan.

`LfsClient` menggunakan wrapper stream HTTP milik PHP (`allow_url_fopen` harus diaktifkan). Wrapper ini tidak mengirim kredensial dan mengabaikan peta `header` yang dikembalikan bersama setiap tindakan, sehingga hanya berfungsi dengan server LFS dan URL penyimpanan yang tidak memerlukan autentikasi. Wrapper ini tidak terikat pada handle repositori, sehingga tetap membuat permintaan jaringan saat Anda bekerja dengan repositori yang dibuka menggunakan `Pitmaster::processFreeOptions()`.

### Mengunduh file

```php
// Single file download
$content = $client->download($pointer->oid, $pointer->size);
file_put_contents('large-file.bin', $content);
```

Di balik layar, `download()`:
1. Mengirim permintaan batch untuk operasi `download`
2. Menerima URL unduhan dari server
3. Mengambil konten file dari URL tersebut

Byte yang diunduh tidak diperiksa terhadap OID atau ukuran.

### Unduhan batch

```php
$results = $client->batchDownload([
    ['oid' => 'abc123...', 'size' => 12345],
    ['oid' => 'def456...', 'size' => 67890],
]);

foreach ($results as $result) {
    if ($result['error'] !== null) {
        echo "Error: {$result['error']}\n";
        continue;
    }

    if ($result['href'] !== null) {
        // Download from $result['href']
    }
}
```

### Mengunggah file

```php
$client->upload($pointer->oid, $pointer->size, $fileContent);
```

Pengunggahan mengirim permintaan batch untuk operasi `upload`. Jika server sudah memiliki objek tersebut, server tidak mengembalikan URL unggahan sehingga operasi tidak melakukan apa pun. Hasil permintaan unggah itu sendiri tidak diperiksa, jadi kegagalan unggah tidak dilaporkan dan tindakan `verify` tidak dipanggil.

### Unggahan batch

```php
$results = $client->batchUpload([
    ['oid' => 'abc123...', 'size' => 12345],
]);

foreach ($results as $result) {
    if ($result['href'] !== null) {
        // Server needs this object, upload to $result['href']
    }
    // No href means server already has it
}
```

## Protokol API batch

Batch LFS API menggunakan JSON melalui HTTP:

### Permintaan

```
POST <lfs-url>/objects/batch
Content-Type: application/vnd.git-lfs+json
Accept: application/vnd.git-lfs+json

{
    "operation": "download",
    "transfers": ["basic"],
    "objects": [
        {"oid": "abc123...", "size": 12345}
    ]
}
```

### Respons

```json
{
    "objects": [
        {
            "oid": "abc123...",
            "size": 12345,
            "actions": {
                "download": {
                    "href": "https://storage.example.com/abc123...",
                    "header": {
                        "Authorization": "Bearer token"
                    }
                }
            }
        }
    ]
}
```

## Mengintegrasikan LFS dengan operasi repositori

Saat membaca repositori yang menggunakan LFS, konten blob mungkin berupa pointer LFS, bukan data file sebenarnya. Untuk mendapatkan konten sebenarnya:

```php
$blob = $repo->readObject($hash);

if (LfsPointer::isPointer($blob->content)) {
    $pointer = LfsPointer::parse($blob->content);
    $remoteUrl = $repo->config()->get('remote.origin.url');
    $client = LfsClient::fromRemoteUrl($remoteUrl);
    $realContent = $client->download($pointer->oid, $pointer->size);
} else {
    $realContent = $blob->content;
}
```

## Penanganan error

Permintaan batch yang gagal atau tidak valid, serta permintaan unduhan yang gagal, melempar `ProtocolException`. `download()` juga melempar error jika server tidak mengembalikan URL unduhan untuk objek tersebut, tanpa pesan error dari server. `batchDownload()` dan `batchUpload()` tidak melempar error untuk kesalahan per objek; keduanya mengembalikan kesalahan tersebut di field `error`.

```php
use Pitmaster\Exceptions\ProtocolException;

try {
    $content = $client->download($oid, $size);
} catch (ProtocolException $e) {
    // Server unreachable, invalid response, no download URL
    echo "LFS error: {$e->getMessage()}\n";
}
```

## Properti pointer LFS

Class `LfsPointer` hanya dapat dibaca:

```php
$pointer->oid;      // string: SHA-256 hash (hex)
$pointer->size;     // int: file size in bytes
$pointer->version;  // string: spec version URL
```
