
# Git LFS

Git Large File Storage (LFS) saugykloje didelius failus pakeičia mažais rodyklių failais. Tikrasis failų turinys saugomas atskirame LFS serveryje. Pitmaster gali analizuoti ir kurti LFS rodykles bei bendrauti su LFS serveriais naudodama paketinį API. Ji neskaito `.gitattributes` ir nevykdo LFS clean bei smudge filtrų: `add()` failus saugo tokius, kokie jie yra, o `checkout()` įrašo rodyklių failus. Tikrojo turinio gavimas paliekamas jūsų kodui, kaip parodyta toliau.

## LFS rodyklių failai

Kai failas sekamas per LFS, git vietoj tikrojo turinio saugo rodyklės failą:

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

Rodyklėje yra:
- `version`: LFS specifikacijos versija URL
- `oid`: tikrojo failo turinio maiša SHA-256 (prieš ją rašoma `sha256:`)
- `size`: tikrojo failo dydis baitais

### Rodyklių analizė

```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()` grąžina `null`, jei trūksta `version`, `oid sha256:` arba `size` eilutės. Reikšmės netikrinamos (priimamas ir ne šešioliktainis OID, ir ne skaitinis dydis).

### Rodyklių aptikimas

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

`isPointer()` patikrina, ar yra versijos URL, `oid sha256:` ir `size` laukai, bet neatlieka išsamios analizės.

### Rodyklių kūrimas

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

### Rodyklių serializavimas

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

## LFS klientas

`LfsClient` bendrauja su LFS serveriais naudodama paketinį API (POST į `<url>/objects/batch`).

### Kliento kūrimas

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

`fromRemoteUrl()` metodas atitinka standartinę konvenciją: prie git URL prideda `/info/lfs` (pridedama `.git`, jei jos nėra). Tinkamą LFS URL suteikia tik HTTP(S) nuotolinių saugyklų URL; SCP formato SSH nuotolinės saugyklos (`git@host:repo.git`) netinka.

`LfsClient` naudoja PHP srauto apvalkalą HTTP (`allow_url_fopen` turi būti įjungta). Jis nesiunčia prisijungimo duomenų ir nepaiso su kiekvienu veiksmu grąžinamo `header` susiejimo, todėl veikia tik su LFS serveriais ir saugyklos URL adresais, kuriems nereikia autentifikavimo. Jis nesusietas su saugyklos deskriptoriumi, todėl siunčia tinklo užklausas net dirbant su saugykla, atidaryta naudojant `Pitmaster::processFreeOptions()`.

### Failų atsisiuntimas

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

Viduje `download()`:
1. Siunčia paketinę užklausą `download` operacijai
2. Iš serverio gauna atsisiuntimo URL
3. Atsisiunčia failo turinį iš to URL

Atsisiųsti baitai netikrinami pagal OID ar dydį.

### Paketinis atsisiuntimas

```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']
    }
}
```

### Failų įkėlimas

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

Įkeliant siunčiama paketinė užklausa `upload` operacijai. Jei serveris objektą jau turi, jis negrąžina įkėlimo URL, todėl nieko nevyksta. Pats įkėlimo užklausos rezultatas netikrinamas, taigi nepavykęs įkėlimas nepranešamas, o veiksmas `verify` neiškviečiamas.

### Paketinis įkėlimas

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

## Paketinio API protokolas

LFS paketinis API naudoja JSON per HTTP:

### Užklausa

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

### Atsakymas

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

## LFS integravimas į saugyklos operacijas

Skaitant LFS naudojančią saugyklą, blob turinys gali būti LFS rodyklės, o ne tikrieji failų duomenys. Norėdami gauti tikrąjį turinį:

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

## Klaidų tvarkymas

Nepavykusi arba netinkama paketinė užklausa ir nepavykusi atsisiuntimo užklausa išmeta `ProtocolException`. `download()` taip pat išmeta klaidą, kai serveris objektui negrąžina atsisiuntimo URL, ir nenurodo serverio klaidos pranešimo. `batchDownload()` ir `batchUpload()` dėl atskirų objektų klaidų klaidų neišmeta – jos grąžinamos lauke `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";
}
```

## LFS rodyklės savybės

Klasė `LfsPointer` yra tik skaitoma:

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