
# Клониране, извличане и изпращане

Pitmaster поддържа трите основни мрежови операции на git: клониране (първоначално изтегляне), извличане (инкрементално изтегляне) и изпращане (качване). URL адресите SSH (`ssh://...`, `user@host:path`) използват транспорта SSH; другите URL адреси използват smart HTTP, а клонирането и извличането преминават към dumb HTTP, ако smart обменът се провали (вижте [Протоколи](https://pitmaster.inline0.dev/bg/docs/network/protocols)).

И трите стартират мрежови транспорти, затова хвърлят `RuntimeException` при обект, отворен с `['processes' => false]` или `Pitmaster::processFreeOptions()`.

## Клониране

```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` и задайте fetch refspec
3. **Откриване на препратки**: `GET /info/refs?service=git-upload-pack`, за да разберете какво има на сървъра
4. **Договаряне**: изпратете редове `want` за всички препратки на отдалеченото хранилище (премахват се дубликатите по хеш)
5. **Получаване на pack**: изтеглете pack файла, съдържащ всички поискани обекти
6. **Записване на pack**: запишете pack файла в `.git/objects/pack/`
7. **Индексиране на pack**: генерирайте файла `.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();
```

### Материализиране на работното дърво

След получаването на pack файла 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. **Договаряне на искани/налични обекти**:
   - Искани: препратки на отдалеченото хранилище, съвпадащи с refspec-овете `remote.<name>.fetch` (по подразбиране `+refs/heads/*:refs/remotes/<name>/*`), чиито обекти липсват локално (всички, ако е зададена дълбочина)
   - Налични: всеки локален връх на препратка
4. **Получаване на pack**: изтеглете само новите обекти
5. **Записване на pack**: запишете в `.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 заявява тънки pack файлове, които все още не може да индексира ([#16](https://github.com/inline0/pitmaster/issues/16)). Ако сървърът отговори на инкрементално извличане с тънък pack файл, `fetch()` хвърля `PackParseException`.

### Вече е актуално

Ако всички обекти, към които сочат препратките на отдалеченото хранилище, вече съществуват локално, не се заявява pack файл и `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 (вътрешни механизми за извличане)

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

Сървърът връща потвърждения, а след това пакетните данни (възможно е да са обвити в странична лента).

## ReceivePackClient (вътрешни механизми за изпращане)

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

Форматът на заявката за изпращане:

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

## Пакетни данни за изпращане

В момента Pitmaster събира обектите, достижими от изпращаните комити, но недостижими от текущите препратки на отдалеченото хранилище, и ги записва в пакет като базови обекти (без делта компресия). Това е функционално коректно, но създава по-големи пакети от `git push`. Делта компресията при запис на пакети е планирана оптимизация.
