Przejdź do treści

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).

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).

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)
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). 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).

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:

Kod
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:

Kod
<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ą.