
# Git LFS

A Git Large File Storage (LFS) a repository nagy fájljait kisméretű mutatófájlokkal helyettesíti. A tényleges fájltartalom külön LFS-kiszolgálón tárolódik. A Pitmaster képes LFS-mutatókat elemezni és létrehozni, valamint a kötegelt API protokollon keresztül kommunikálni az LFS-kiszolgálókkal. Nem olvassa a `.gitattributes` fájlt, és nem futtatja az LFS clean és smudge szűrőket: a `add()` változatlanul tárolja a fájlokat, a `checkout()` pedig mutatófájlokat ír. A tényleges tartalom letöltéséről az Ön kódjának kell gondoskodnia, az alábbi példa szerint.

## LFS-mutatófájlok

Ha egy fájlt az LFS követ, a git a tényleges tartalom helyett mutatófájlt tárol:

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

A mutató tartalma:
- `version`: az LFS-specifikáció verziója, URL
- `oid`: a tényleges fájltartalom SHA-256 hashértéke (előtagja `sha256:`)
- `size`: a tényleges fájl mérete bájtban

### Mutatók elemzése

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

A `parse()` értéke `null`, ha hiányzik a `version`, `oid sha256:` vagy `size` sor. Az értékeket nem ellenőrzi (nem hexadecimális OID vagy nem numerikus méret is elfogadható).

### Mutatók felismerése

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

A `isPointer()` teljes elemzés nélkül ellenőrzi a verzió-, URL- és `oid sha256:`-mezők, valamint a `size` mező meglétét.

### Mutatók létrehozása

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

### Mutatók szerializálása

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

## LFS-kliens

A `LfsClient` a kötegelt API használatával kommunikál az LFS-kiszolgálókkal (POST a következő címre: `<url>/objects/batch`).

### Kliens létrehozása

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

A `fromRemoteUrl()` metódus a szabványos konvenciót követi: a git URL címéhez hozzáfűzi a `/info/lfs` elemet (ha még nincs ott, előbb hozzáadja a `.git` részt). Használható LFS-URL-t csak a HTTP(S) távoli URL-ek adnak; az SCP-formátumú SSH távoli címek (`git@host:repo.git`) nem.

A `LfsClient` a PHP HTTP streamburkolóját használja (a `allow_url_fopen` beállítást engedélyezni kell). Nem küld hitelesítő adatokat, és figyelmen kívül hagyja a műveletenként visszakapott `header` leképezést, ezért csak hitelesítés nélküli LFS-kiszolgálókkal és tárhely-URL-ekkel működik. Nem kötődik repository-kezelőhöz, így hálózati kéréseket akkor is küld, ha a repository-t `Pitmaster::processFreeOptions()` beállítással nyitotta meg.

### Fájlok letöltése

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

A háttérben a `download()` a következőket teszi:
1. Kötegelt kérést küld a `download` művelethez
2. Letöltési URL választ kap a kiszolgálótól
3. Letölti a fájltartalmat ebből a URL-ból

A letöltött bájtokat nem ellenőrzi az OID-vel vagy a mérettel.

### Kötegelt letöltés

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

### Fájlok feltöltése

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

A feltöltés kötegelt kérést küld a `upload` művelethez. Ha a kiszolgálón már megtalálható az objektum, nem ad vissza feltöltési URL elemet, így a műveletnek nincs hatása. Magának a feltöltési kérésnek az eredményét nem ellenőrzi, ezért a sikertelen feltöltést nem jelzi, és a `verify` műveletet sem hívja meg.

### Kötegelt feltöltés

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

## Kötegelt API protokoll

Az LFS kötegelt API protokollja a JSON protokollt használja a HTTP felett:

### Kérés

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

### Válasz

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

## LFS integrálása repository-műveletekkel

LFS-t használó repository olvasásakor a blob tartalma a tényleges fájladatok helyett LFS-mutató lehet. A tényleges tartalom lekéréséhez:

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

## Hibakezelés

A sikertelen vagy érvénytelen kötegelt kérés, illetve a sikertelen letöltési kérés `ProtocolException` hibát dob. A `download()` akkor is hibát dob, ha a kiszolgáló nem ad vissza letöltési URL elemet az objektumhoz; a kiszolgáló hibaüzenetét ilyenkor nem tartalmazza. A `batchDownload()` és a `batchUpload()` objektumonkénti hibák esetén nem dob hibát, hanem a `error` mezőben adja vissza azokat.

```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-mutató tulajdonságai

A `LfsPointer` osztály csak olvasható:

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