
# Klonowanie, pobieranie i wysyłanie

Pitmaster obsługuje trzy podstawowe operacje sieciowe gita: clone (początkowe pobranie), fetch (pobranie zmian) i push (wysyłanie zmian). Adresy URL SSH (`ssh://...`, `user@host:path`) korzystają z transportu SSH; inne adresy URL używają inteligentnego HTTP, a clone i fetch przechodzą na prosty HTTP, jeśli inteligentna wymiana zakończy się niepowodzeniem (zob. [Protokoły](https://pitmaster.inline0.dev/pl/docs/network/protocols)).

Wszystkie trzy operacje uruchamiają transporty sieciowe, więc zgłaszają `RuntimeException` w przypadku uchwytu otwartego z `['processes' => false]` lub `Pitmaster::processFreeOptions()`.

## Klonowanie

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

Nowe repozytorium zawsze używa SHA-1. Jeśli klonowanie się nie powiedzie, a katalog docelowy wcześniej nie istniał, częściowo utworzony katalog zostanie usunięty.

### Przebieg klonowania

1. **Inicjalizacja**: utwórz nowe, puste repozytorium w ścieżce docelowej
2. **Konfiguracja zdalnego repozytorium**: zapisz URL jako `remote.origin.url` i skonfiguruj refspec pobierania
3. **Wyszukanie referencji**: `GET /info/refs?service=git-upload-pack`, aby sprawdzić, co ma serwer
4. **Negocjacja**: wyślij linie `want` dla wszystkich zdalnych referencji (zduplikowane skróty są pomijane)
5. **Odbiór pakietu**: pobierz plik pakietu zawierający wszystkie żądane obiekty
6. **Zapis pakietu**: zapisz plik pakietu w `.git/objects/pack/`
7. **Indeksowanie pakietu**: wygeneruj plik `.idx` na potrzeby szybkiego wyszukiwania
8. **Konfiguracja referencji**: utwórz zdalne gałęzie śledzące (`refs/remotes/origin/*`) i tagi
9. **Ustawienie HEAD**: skieruj HEAD na domyślną gałąź (na podstawie możliwości serwera `symref=HEAD:...` albo `refs/heads/main`, jeśli serwer jej nie wysyła) oraz ustaw `branch.<name>.remote` i `branch.<name>.merge`
10. **Przełączenie**: przełącz się na tę gałąź, co zapisuje drzewo robocze i indeks

Błędy podczas końcowego przełączenia są przechwytywane i ignorowane, więc klonowanie może zakończyć się pustym lub częściowym drzewem roboczym ([#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();
```

### Utworzenie drzewa roboczego

Po odebraniu pakietu Pitmaster wywołuje `$repo->checkout()` dla domyślnej gałęzi. Zapisuje to wszystkie pliki z drzewa zatwierdzenia HEAD w katalogu roboczym i tworzy indeks na podstawie wpisów drzewa, dzięki czemu `status()` zgłasza czyste drzewo robocze.

## Pobieranie zmian

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

### Przebieg pobierania zmian

1. **Odczyt zdalnego URL**: z `.git/config` (`remote.<name>.url`)
2. **Wyszukanie referencji**: pobierz bieżący stan wszystkich zdalnych referencji
3. **Negocjacja żądanych i posiadanych obiektów**:
   - Żądane: zdalne referencje pasujące do refspeców `remote.<name>.fetch` (domyślnie `+refs/heads/*:refs/remotes/<name>/*`), których obiektów brakuje lokalnie (wszystkie przy podaniu głębokości)
   - Posiadane: końcówki wszystkich lokalnych referencji
4. **Odbiór pakietu**: pobierz tylko nowe obiekty
5. **Zapis pakietu**: zapisz w `.git/objects/pack/` i utwórz jego indeks
6. **Aktualizacja referencji śledzących**: zaktualizuj `refs/remotes/<remote>/<branch>` dla każdej pasującej zdalnej gałęzi i zapisz każdą ogłoszoną referencję tagu. Obiekty tagów nie są żądane osobno, więc nowy tag z adnotacją może zostać zapisany jako referencja do niepobranego obiektu ([#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()` szuka nazw jako `refs/heads/<name>`, `refs/tags/<name>`, a następnie samej nazwy, dlatego skrótowa forma `origin/main` nie zostanie znaleziona.

Pitmaster żąda pakietów cienkich, których nie potrafi jeszcze indeksować ([#16](https://github.com/inline0/pitmaster/issues/16)). Serwer odpowiadający na przyrostowe pobieranie pakietem cienkim powoduje, że `fetch()` zgłasza `PackParseException`.

### Brak aktualizacji

Jeśli wszystkie obiekty wskazywane przez zdalne referencje już istnieją lokalnie, pakiet nie jest żądany, a `fetch()` kończy działanie po wyszukaniu referencji. W takim przypadku referencje śledzące również nie są aktualizowane, nawet jeśli zdalne gałęzie przesunęły się do zatwierdzeń, które już masz ([#23](https://github.com/inline0/pitmaster/issues/23)).

## Wysyłanie zmian

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

### Przebieg wysyłania zmian

1. **Odczyt zdalnego URL**: z `.git/config`
2. **Określenie zakresu wysyłania**: rozwiąż lokalną gałąź do skrótu jej zatwierdzenia
3. **Wyszukanie zdalnych referencji**: pobierz bieżący stan zdalnej gałęzi (aby obliczyć stary skrót)
4. **Sprawdzenie możliwości szybkiego przewinięcia**: jeśli zdalna gałąź nie jest przodkiem lokalnej, zgłoś `RuntimeException("Push rejected: non-fast-forward update to <branch>")` przed wysłaniem czegokolwiek
5. **Wysłanie aktualizacji referencji**: `<old-hash> <new-hash> refs/heads/<branch>`
6. **Wysłanie danych pakietu**: pakiet zawierający obiekty potrzebne zdalnemu repozytorium
7. **Sprawdzenie wyniku**: serwer odpowiada powodzeniem lub niepowodzeniem dla każdej referencji

Hook `pre-push` uruchamia się przed wysłaniem aktualizacji, chyba że hooki są wyłączone.

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

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

### Wymagania dotyczące wysyłania zmian

- Bieżąca gałąź musi istnieć lokalnie
- Zdalne repozytorium musi być skonfigurowane z URL
- Zdalne repozytorium musi akceptować wysyłanie zmian (uwierzytelnianie, uprawnienia, zasady dotyczące zmian bez szybkiego przewinięcia)

### Wymuszone wysyłanie zmian

Pitmaster wysyła stary skrót z bieżącej reklamy zdalnej referencji, a `push()` lokalnie odrzuca aktualizacje bez szybkiego przewinięcia. Aby nadpisać zdalną gałąź, użyj 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 (szczegóły pobierania)

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

Negocjacja żądanych i posiadanych obiektów tworzy żądanie 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
```

Serwer odpowiada potwierdzeniami, a następnie danymi pakietu (które mogą być opakowane w side-band).

## ReceivePackClient (szczegóły wysyłania)

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

Format żądania wysyłania:

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

## Obsługa błędów

```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()` może też zgłosić zwykły wyjątek `RuntimeException` (nieznane zdalne repozytorium, uchwyt bez procesów) lub `PackParseException` (pakiet, którego nie można indeksować). Zarówno `ProtocolException`, jak i `PackParseException` rozszerzają `\RuntimeException`, więc przechwycenie `\RuntimeException` obejmuje je wszystkie.

## Konfiguracja zdalnych repozytoriów

Zdalne repozytoria są przechowywane w `.git/config`:

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

Dostęp przez API konfiguracji:

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

## Dane pakietu do wysyłania

Obecnie Pitmaster zbiera obiekty osiągalne z wysyłanych zatwierdzeń, które nie są osiągalne z bieżących zdalnych referencji, i zapisuje je w pakiecie jako obiekty bazowe (bez kompresji delt). Działa to poprawnie, ale tworzy większe pakiety niż `git push`. Kompresja delt podczas zapisu pakietu jest planowaną optymalizacją.
