Lewati ke konten

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

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

Repositori baru selalu menggunakan SHA-1. Jika clone gagal dan tujuan belum ada sebelumnya, direktori yang baru dibuat sebagian akan dihapus.

Alur clone

  1. Inisialisasi: buat repositori kosong baru di jalur tujuan
  2. Konfigurasikan remote: simpan URL sebagai remote.origin.url dan siapkan refspec fetch
  3. Temukan refs: GET /info/refs?service=git-upload-pack untuk mengetahui isi server
  4. Negosiasi: kirim baris want untuk semua refs remote (dideduplikasi berdasarkan hash)
  5. Terima pack: unduh file pack yang berisi semua objek yang diminta
  6. Tulis pack: simpan file pack ke .git/objects/pack/
  7. Indeks pack: buat file .idx untuk pencarian cepat
  8. Siapkan refs: buat branch pelacakan remote (refs/remotes/origin/*) dan tag
  9. Atur HEAD: arahkan HEAD ke branch default (dari kapabilitas symref=HEAD:... server, atau refs/heads/main jika server tidak mengirimnya), lalu atur branch.<name>.remote dan branch.<name>.merge
  10. 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).

PHP
$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

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

Alur fetch

  1. Baca URL remote: dari .git/config (remote.<name>.url)
  2. Temukan refs: dapatkan status terkini semua refs remote
  3. 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
  4. Terima pack: unduh hanya objek baru
  5. Tulis pack: simpan ke .git/objects/pack/ dan buat indeksnya
  6. 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)
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() 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

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

Alur push

  1. Baca URL remote: dari .git/config
  2. Tentukan yang akan dikirim: resolve branch lokal ke hash commit-nya
  3. Temukan refs remote: dapatkan status terkini branch remote (untuk menghitung hash lama)
  4. Periksa fast-forward: jika branch remote bukan leluhur branch lokal, timbulkan RuntimeException("Push rejected: non-fast-forward update to <branch>") sebelum mengirim apa pun
  5. Kirim pembaruan ref: <old-hash> <new-hash> refs/heads/<branch>
  6. Kirim data pack: pack yang berisi objek yang dibutuhkan remote
  7. Periksa hasil: server merespons dengan status berhasil/gagal untuk setiap ref

Hook pre-push dijalankan sebelum pembaruan dikirim, kecuali hook dinonaktifkan.

PHP
// 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:

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 (internal fetch)

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

Negosiasi want/have menyusun permintaan pkt-line:

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

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 permintaan push:

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

Penanganan error

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() 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:

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

Akses melalui config 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');

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.