
# Git LFS

Το Git Large File Storage (LFS) αντικαθιστά τα μεγάλα αρχεία του αποθετηρίου σας με μικρά αρχεία δεικτών. Το πραγματικό περιεχόμενο αποθηκεύεται σε ξεχωριστό διακομιστή LFS. Το Pitmaster μπορεί να αναλύει και να δημιουργεί δείκτες LFS και να επικοινωνεί με διακομιστές LFS μέσω του batch API. Δεν διαβάζει το `.gitattributes` ούτε εκτελεί τα φίλτρα clean και smudge του LFS: το `add()` αποθηκεύει τα αρχεία όπως είναι και το `checkout()` γράφει αρχεία δεικτών. Η ανάκτηση του πραγματικού περιεχομένου επαφίεται στον κώδικά σας, όπως φαίνεται παρακάτω.

## Αρχεία δεικτών LFS

Όταν ένα αρχείο παρακολουθείται από το LFS, το git αποθηκεύει ένα αρχείο δείκτη αντί για το πραγματικό περιεχόμενο:

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

Ο δείκτης περιέχει:
- `version`: την έκδοση προδιαγραφής LFS URL
- `oid`: το hash SHA-256 του πραγματικού περιεχομένου αρχείου (με πρόθεμα `sha256:`)
- `size`: το πραγματικό μέγεθος αρχείου σε byte

### Ανάλυση δεικτών

```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()` επιστρέφει `null` όταν λείπει η γραμμή `version`, `oid sha256:` ή `size`. Δεν επικυρώνει τις τιμές (γίνεται δεκτό μη δεκαεξαδικό OID ή μη αριθμητικό μέγεθος).

### Εντοπισμός δεικτών

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

Το `isPointer()` ελέγχει αν υπάρχουν τα πεδία έκδοσης URL, `oid sha256:` και `size`, χωρίς πλήρη ανάλυση.

### Δημιουργία δεικτών

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

### Σειριοποίηση δεικτών

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

## Πελάτης LFS

Το `LfsClient` επικοινωνεί με διακομιστές LFS χρησιμοποιώντας το batch API (POST στο `<url>/objects/batch`).

### Δημιουργία πελάτη

```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()` ακολουθεί την καθιερωμένη σύμβαση: προσθέτει το `/info/lfs` στο git URL (προσθέτοντας το `.git` αν λείπει). Μόνο τα απομακρυσμένα URL HTTP(S) παρέχουν αξιοποιήσιμο LFS URL· τα remote σε μορφή SCP SSH (`git@host:repo.git`) δεν το παρέχουν.

Το `LfsClient` χρησιμοποιεί το περιτύλιγμα ροής HTTP του PHP (πρέπει να είναι ενεργοποιημένο το `allow_url_fopen`). Δεν στέλνει διαπιστευτήρια και αγνοεί τον χάρτη `header` που επιστρέφεται με κάθε ενέργεια, επομένως λειτουργεί μόνο με διακομιστές LFS και URL αποθήκευσης που δεν απαιτούν έλεγχο ταυτότητας. Δεν συνδέεται με χειριστή αποθετηρίου, οπότε πραγματοποιεί αιτήματα δικτύου ακόμη κι όταν εργάζεστε με αποθετήριο που άνοιξε με `Pitmaster::processFreeOptions()`.

### Λήψη αρχείων

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

Στο παρασκήνιο, το `download()`:
1. Στέλνει αίτημα batch για τη λειτουργία `download`
2. Λαμβάνει ένα URL λήψης από τον διακομιστή
3. Ανακτά το περιεχόμενο του αρχείου από αυτό το URL

Τα byte που λαμβάνονται δεν ελέγχονται ως προς το OID ή το μέγεθος.

### Λήψη σε δέσμη

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

### Μεταφόρτωση αρχείων

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

Η μεταφόρτωση στέλνει αίτημα batch για τη λειτουργία `upload`. Αν ο διακομιστής έχει ήδη το αντικείμενο, δεν επιστρέφει URL μεταφόρτωσης και η λειτουργία δεν κάνει τίποτα. Το αποτέλεσμα του ίδιου του αιτήματος μεταφόρτωσης δεν ελέγχεται, επομένως μια αποτυχημένη μεταφόρτωση δεν αναφέρεται και δεν καλείται η ενέργεια `verify`.

### Μεταφόρτωση σε δέσμη

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

## Πρωτόκολλο batch API

Το batch API του LFS χρησιμοποιεί JSON μέσω HTTP:

### Αίτημα

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

### Απάντηση

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

## Ενσωμάτωση LFS στις λειτουργίες αποθετηρίου

Κατά την ανάγνωση αποθετηρίου που χρησιμοποιεί LFS, το περιεχόμενο των blob μπορεί να είναι δείκτες LFS αντί για πραγματικά δεδομένα αρχείων. Για να λάβετε το πραγματικό περιεχόμενο:

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

## Χειρισμός σφαλμάτων

Ένα αποτυχημένο ή μη έγκυρο αίτημα batch και ένα αποτυχημένο αίτημα λήψης προκαλούν `ProtocolException`. Το `download()` προκαλεί επίσης σφάλμα όταν ο διακομιστής δεν επιστρέφει URL λήψης για το αντικείμενο, χωρίς το μήνυμα σφάλματος του διακομιστή. Τα `batchDownload()` και `batchUpload()` δεν προκαλούν σφάλμα για σφάλματα ανά αντικείμενο· τα επιστρέφουν στο πεδίο `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

Η κλάση `LfsPointer` είναι μόνο για ανάγνωση:

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