Clone, Fetch, Push
Pitmaster mendukung tiga operasi jaringan git mendasar: clone (unduhan awal), fetch (unduhan bertahap), dan push (unggahan). URL SSH (ssh://..., user@host:path) menggunakan transport SSH; URL lainnya menggunakan HTTP pintar, dan clone serta fetch beralih ke HTTP biasa jika pertukaran pintar gagal (lihat Protokol).
Ketiganya memulai transport jaringan, sehingga akan menimbulkan RuntimeException pada handle yang dibuka dengan ['processes' => false] atau Pitmaster::processFreeOptions().
Clone
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);
Repositori baru selalu menggunakan SHA-1. Jika clone gagal dan tujuan belum ada sebelumnya, direktori yang baru dibuat sebagian akan dihapus.
Alur clone
- Inisialisasi: buat repositori kosong baru di jalur tujuan
- Konfigurasikan remote: simpan URL sebagai
remote.origin.urldan siapkan refspec fetch - Temukan refs:
GET /info/refs?service=git-upload-packuntuk mengetahui isi server - Negosiasi: kirim baris
wantuntuk semua refs remote (dideduplikasi berdasarkan hash) - Terima pack: unduh file pack yang berisi semua objek yang diminta
- Tulis pack: simpan file pack ke
.git/objects/pack/ - Indeks pack: buat file
.idxuntuk pencarian cepat - Siapkan refs: buat branch pelacakan remote (
refs/remotes/origin/*) dan tag - Atur HEAD: arahkan HEAD ke branch default (dari kapabilitas
symref=HEAD:...server, ataurefs/heads/mainjika server tidak mengirimnya), lalu aturbranch.<name>.remotedanbranch.<name>.merge - Checkout: checkout branch tersebut, yang menulis pohon kerja dan indeks
Kesalahan saat checkout terakhir ditangkap dan diabaikan, sehingga clone dapat selesai dengan pohon kerja kosong atau sebagian (#19).
$repo = Pitmaster::clone($url, $path);
$head = $repo->head();
echo $head->message;
$branches = $repo->branches();
$files = $repo->status();
Membuat pohon kerja
Setelah menerima pack, Pitmaster memanggil $repo->checkout() untuk branch default. Proses ini menulis setiap file dari pohon commit HEAD ke direktori kerja dan membangun indeks dari entri pohon, sehingga status() melaporkan pohon kerja yang bersih.
Fetch
// 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
Alur fetch
- Baca URL remote: dari
.git/config(remote.<name>.url) - Temukan refs: dapatkan status terkini semua refs remote
- Negosiasikan want/have:
- Want: refs remote yang cocok dengan refspec
remote.<name>.fetch(default+refs/heads/*:refs/remotes/<name>/*) dan objeknya belum ada secara lokal (semuanya jika kedalaman diberikan) - Have: setiap ujung ref lokal
- Want: refs remote yang cocok dengan refspec
- Terima pack: unduh hanya objek baru
- Tulis pack: simpan ke
.git/objects/pack/dan buat indeksnya - Perbarui refs pelacakan: perbarui
refs/remotes/<remote>/<branch>untuk setiap branch remote yang cocok, lalu tulis setiap ref tag yang diumumkan. Objek tag tidak diminta secara terpisah, sehingga tag beranotasi baru dapat ditulis sebagai ref ke objek yang belum diunduh (#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() mencari nama sebagai refs/heads/<name>, refs/tags/<name>, lalu nama itu sendiri, sehingga bentuk singkat origin/main tidak ditemukan.
Pitmaster meminta pack tipis, yang belum dapat diindeksnya (#16). Server yang menjawab fetch bertahap dengan pack tipis akan menyebabkan fetch() menimbulkan PackParseException.
Sudah mutakhir
Jika setiap objek yang dituju refs remote sudah ada secara lokal, pack tidak diminta dan fetch() selesai setelah penemuan ref. Dalam kasus tersebut, refs pelacakan juga tidak diperbarui, meskipun branch remote berpindah ke commit yang sudah Anda miliki (#23).
Push
$repo->push(); // Push current branch to 'origin'
$repo->push('origin', 'main'); // Push a specific branch
$repo->push('upstream', 'feature'); // Push to a named remote
Alur push
- Baca URL remote: dari
.git/config - Tentukan yang akan dikirim: resolve branch lokal ke hash commit-nya
- Temukan refs remote: dapatkan status terkini branch remote (untuk menghitung hash lama)
- Periksa fast-forward: jika branch remote bukan leluhur branch lokal, timbulkan
RuntimeException("Push rejected: non-fast-forward update to <branch>")sebelum mengirim apa pun - Kirim pembaruan ref:
<old-hash> <new-hash> refs/heads/<branch> - Kirim data pack: pack yang berisi objek yang dibutuhkan remote
- Periksa hasil: server merespons dengan status berhasil/gagal untuk setiap ref
Hook pre-push dijalankan sebelum pembaruan dikirim, kecuali hook dinonaktifkan.
// Push the current branch
$repo->push();
// Push a specific branch to a specific remote
$repo->push('origin', 'feature/login');
Persyaratan push
- Branch saat ini harus ada secara lokal
- Remote harus dikonfigurasi dengan URL
- Remote harus menerima push (autentikasi, izin, kebijakan non-fast-forward)
Force push
Pitmaster mengirim hash lama dari iklan ref aktif remote, dan push() menolak pembaruan non-fast-forward secara lokal. Untuk menimpa cabang remote, gunakan 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 (internal fetch)
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);
Negosiasi want/have menyusun permintaan 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
Server merespons dengan acknowledgment, lalu data pack (mungkin dibungkus side-band).
ReceivePackClient (internal push)
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 permintaan push:
<old-hash> <new-hash> refs/heads/main\0<capabilities>\n
0000
PACK<pack data>
Penanganan error
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() juga dapat melempar RuntimeException biasa (remote tidak dikenal, handle tanpa proses) atau PackParseException (pack yang tidak dapat diindeks). ProtocolException dan PackParseException sama-sama mewarisi \RuntimeException, jadi menangkap \RuntimeException akan mencakup semuanya.
Konfigurasi remote
Remote disimpan di .git/config:
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
Akses melalui config API:
$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');
Data pack untuk push
Saat ini, Pitmaster mengumpulkan objek yang dapat dijangkau dari commit yang di-push, tetapi tidak dapat dijangkau dari ref remote saat ini, lalu menuliskannya sebagai objek dasar (tanpa kompresi delta) ke dalam pack. Secara fungsi ini benar, tetapi menghasilkan pack yang lebih besar daripada git push. Kompresi delta saat menulis pack direncanakan sebagai optimasi.