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
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
- Inicjalizacja: utwórz nowe, puste repozytorium w ścieżce docelowej
- Konfiguracja zdalnego repozytorium: zapisz URL jako
remote.origin.urli skonfiguruj refspec pobierania - Wyszukanie referencji:
GET /info/refs?service=git-upload-pack, aby sprawdzić, co ma serwer - Negocjacja: wyślij linie
wantdla wszystkich zdalnych referencji (zduplikowane skróty są pomijane) - Odbiór pakietu: pobierz plik pakietu zawierający wszystkie żądane obiekty
- Zapis pakietu: zapisz plik pakietu w
.git/objects/pack/ - Indeksowanie pakietu: wygeneruj plik
.idxna potrzeby szybkiego wyszukiwania - Konfiguracja referencji: utwórz zdalne gałęzie śledzące (
refs/remotes/origin/*) i tagi - Ustawienie HEAD: skieruj HEAD na domyślną gałąź (na podstawie możliwości serwera
symref=HEAD:...alborefs/heads/main, jeśli serwer jej nie wysyła) oraz ustawbranch.<name>.remoteibranch.<name>.merge - 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).
$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
// 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
- Odczyt zdalnego URL: z
.git/config(remote.<name>.url) - Wyszukanie referencji: pobierz bieżący stan wszystkich zdalnych referencji
- 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
- Żądane: zdalne referencje pasujące do refspeców
- Odbiór pakietu: pobierz tylko nowe obiekty
- Zapis pakietu: zapisz w
.git/objects/pack/i utwórz jego indeks - 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)
// 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
$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
- Odczyt zdalnego URL: z
.git/config - Określenie zakresu wysyłania: rozwiąż lokalną gałąź do skrótu jej zatwierdzenia
- Wyszukanie zdalnych referencji: pobierz bieżący stan zdalnej gałęzi (aby obliczyć stary skrót)
- 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 - Wysłanie aktualizacji referencji:
<old-hash> <new-hash> refs/heads/<branch> - Wysłanie danych pakietu: pakiet zawierający obiekty potrzebne zdalnemu repozytorium
- 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.
// 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:
// 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)
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)
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
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:
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
Dostęp przez API konfiguracji:
$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ą.