
# Клонування, отримання, надсилання

Pitmaster підтримує три основні мережеві операції git: клонування (початкове завантаження), отримання (інкрементне завантаження) та надсилання (вивантаження). URL-адреси SSH (`ssh://...`, `user@host:path`) використовують транспорт SSH; інші URL-адреси використовують розумний HTTP, а клонування та отримання переходять до простого HTTP, якщо розумний обмін даними не вдається (див. [Протоколи](https://pitmaster.inline0.dev/uk/docs/network/protocols)).

Усі три операції запускають мережеві транспорти, тому для дескриптора, відкритого з `['processes' => false]` або `Pitmaster::processFreeOptions()`, вони викликають `RuntimeException`.

## Клонування

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

Новий репозиторій завжди використовує SHA-1. Якщо клонування не вдається, а призначення до цього не існувало, частково створений каталог видаляється.

### Процес клонування

1. **Ініціалізація**: створити новий порожній репозиторій за шляхом призначення
2. **Налаштування віддаленого репозиторію**: зберегти URL як `remote.origin.url` і налаштувати специфікацію отримання посилань
3. **Виявлення посилань**: виконати `GET /info/refs?service=git-upload-pack`, щоб дізнатися, що є на сервері
4. **Узгодження**: надіслати рядки `want` для всіх віддалених посилань (без дублікатів за хешем)
5. **Отримання пакета**: завантажити файл пакета з усіма запитаними об’єктами
6. **Запис пакета**: зберегти файл пакета в `.git/objects/pack/`
7. **Індексація пакета**: створити файл `.idx` для швидкого пошуку
8. **Налаштування посилань**: створити гілки для відстеження віддалених гілок (`refs/remotes/origin/*`) і теги
9. **Налаштування HEAD**: спрямувати HEAD на типову гілку (з можливості сервера `symref=HEAD:...` або на `refs/heads/main`, якщо сервер її не надішле), а також налаштувати `branch.<name>.remote` і `branch.<name>.merge`
10. **Вибір гілки**: вибрати цю гілку, щоб створити робоче дерево та індекс

Помилки під час завершального вибору гілки перехоплюються та ігноруються, тому клонування може завершитися з порожнім або частково створеним робочим деревом ([#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();
```

### Створення робочого дерева

Після отримання пакета Pitmaster викликає `$repo->checkout()` для типової гілки. Це записує кожен файл із дерева коміту HEAD у робочий каталог і створює індекс із записів дерева, тож `status()` повідомляє про чисте робоче дерево.

## Отримання

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

### Процес отримання

1. **Читання віддаленого URL**: з `.git/config` (`remote.<name>.url`)
2. **Виявлення посилань**: отримати поточний стан усіх віддалених посилань
3. **Узгодження потрібних і наявних об’єктів**:
   - Потрібні: віддалені посилання, що відповідають специфікаціям `remote.<name>.fetch` (типово `+refs/heads/*:refs/remotes/<name>/*`), об’єкти яких відсутні локально (усі вони, якщо вказано глибину)
   - Наявні: усі локальні вершини посилань
4. **Отримання пакета**: завантажити лише нові об’єкти
5. **Запис пакета**: зберегти в `.git/objects/pack/` і створити його індекс
6. **Оновлення посилань відстеження**: оновити `refs/remotes/<remote>/<branch>` для кожної відповідної віддаленої гілки та записати кожне оголошене посилання на тег. Об’єкти тегів окремо не запитуються, тому новий анотований тег може бути записаний як посилання на незавантажений об’єкт ([#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()` шукає імена як `refs/heads/<name>`, `refs/tags/<name>`, а потім саме ім’я, тому коротку форму `origin/main` не знайдено.

Pitmaster запитує тонкі пакети, які поки не може індексувати ([#16](https://github.com/inline0/pitmaster/issues/16)). Якщо сервер відповідає на інкрементне отримання тонким пакетом, `fetch()` викликає `PackParseException`.

### Уже актуально

Якщо всі об’єкти, на які вказують віддалені посилання, вже є локально, пакет не запитується, а `fetch()` повертається після виявлення посилань. У цьому разі посилання відстеження також не оновлюються, навіть якщо віддалені гілки перемістилися на коміти, які вже є у вас ([#23](https://github.com/inline0/pitmaster/issues/23)).

## Надсилання

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

### Процес надсилання

1. **Читання віддаленого URL**: з `.git/config`
2. **Визначення змін для надсилання**: знайти хеш коміту локальної гілки
3. **Виявлення віддалених посилань**: отримати поточний стан віддаленої гілки (щоб обчислити старий хеш)
4. **Перевірка перемотування вперед**: якщо віддалена гілка не є предком локальної гілки, викликати `RuntimeException("Push rejected: non-fast-forward update to <branch>")` до надсилання будь-яких даних
5. **Надсилання оновлення посилання**: `<old-hash> <new-hash> refs/heads/<branch>`
6. **Надсилання даних пакета**: пакет з об’єктами, потрібними віддаленому репозиторію
7. **Перевірка результату**: сервер відповідає успішним або невдалим результатом для кожного посилання

Хук `pre-push` виконується перед надсиланням оновлення, якщо хуки не вимкнено.

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

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

### Вимоги для надсилання

- Поточна гілка має існувати локально
- Для віддаленого репозиторію має бути налаштовано URL
- Віддалений репозиторій має приймати надсилання (автентифікація, дозволи, політики щодо перемотування вперед)

### Примусове надсилання

Pitmaster надсилає старий хеш із поточного оголошення посилань віддаленого репозиторію, а `push()` відхиляє локальні оновлення, що не є перемотуванням уперед. Щоб перезаписати гілку віддаленого репозиторію, використовуйте 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 (внутрішня логіка 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);
```

Узгодження want/have формує запит 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
```

Сервер відповідає підтвердженнями, а потім даними пакета (можливо, загорнутими в side-band).

## ReceivePackClient (внутрішня логіка 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);
```

Формат запиту push:

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

## Обробка помилок

```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()` також може викинути звичайний виняток `RuntimeException` (невідомий віддалений репозиторій, дескриптор без процесу) або `PackParseException` (пакет, який неможливо індексувати). І `ProtocolException`, і `PackParseException` успадковують `\RuntimeException`, тож перехоплення `\RuntimeException` охоплює їх усі.

## Конфігурація віддалених репозиторіїв

Віддалені репозиторії зберігаються в `.git/config`:

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

Доступ через конфігурацію 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');
```

## Дані пакета для push

Наразі Pitmaster збирає об’єкти, досяжні з комітів, які надсилаються, але недосяжні з поточних посилань віддаленого репозиторію, і записує їх у пакет як базові об’єкти (без дельта-стиснення). Функціонально це правильно, але пакети виходять більшими, ніж у `git push`. Дельта-стиснення під час запису пакетів заплановано як оптимізацію.
