Worktree
Git worktree memungkinkan beberapa direktori kerja berbagi satu repositori. Setiap worktree memiliki checkout sendiri (HEAD, indeks, file kerja), tetapi berbagi penyimpanan objek, konfigurasi, dan packed-refs dengan repositori utama. Pitmaster dapat membuat, mencantumkan, mengunci, dan menghapus worktree tertaut, serta membukanya sebagai repositori. Pemangkasan, pemindahan, dan perbaikan worktree belum diimplementasikan.
Konsep
Repositori git biasa memiliki satu tree kerja:
project/
.git/ (directory)
src/
README.md
Dengan worktree, Anda dapat memiliki checkout tambahan:
project/ (main worktree)
.git/ (the actual git directory)
worktrees/
review/ (metadata for linked worktree)
HEAD
commondir
gitdir
/tmp/review/ (linked worktree)
.git (file, not directory)
src/
README.md
File .git worktree tertaut berisi jalur relatif terhadap worktree (bukan direktori):
gitdir: ../../path/to/project/.git/worktrees/review
Membuat worktree
$repo = Pitmaster::open('/path/to/project');
// Create a linked worktree on a new branch
$wt = $repo->addWorktree('/tmp/review', 'feature/review');
echo $wt->path; // '/tmp/review'
echo $wt->branch; // 'feature/review'
echo $wt->gitDir; // '/path/to/project/.git/worktrees/review'
var_dump($wt->isMain); // bool(false)
var_dump($wt->isDetached); // bool(false)
addWorktree() melakukan hal berikut:
- Membuat cabang jika belum ada (dari HEAD atau commit
$fromyang ditentukan) - Membuat direktori metadata di
.git/worktrees/<name>/ - Menulis file
.gitdi direktori worktree - Menulis file metadata
HEAD,commondir, dangitdir - Melakukan checkout tree cabang ke direktori worktree
- Menulis indeks untuk worktree
Jika cabang sudah ada, $from diabaikan. Tidak seperti git, addWorktree() tidak menolak cabang yang sudah di-checkout di worktree lain, dan tidak menjalankan hook post-checkout.
Dengan nama metadata eksplisit
Gunakan argumen opsional name jika beberapa worktree tertaut dapat memiliki nama dasar checkout yang sama.
$wt = $repo->addWorktree(
'/tmp/sandboxes/app',
'feature/review',
name: 'sandbox-app',
);
Nama metadata tersebut digunakan di bawah .git/worktrees/<name>/, bukan basename($path).
Dari commit tertentu
$wt = $repo->addWorktree('/tmp/hotfix', 'hotfix/urgent', $repo->resolve('v1.0.0'));
Mencantumkan worktree
$worktrees = $repo->worktrees();
foreach ($worktrees as $wt) {
$type = $wt->isMain ? 'main' : 'linked';
$branch = $wt->branch ?? 'detached';
$locked = $wt->isLocked ? ' [locked]' : '';
echo "{$wt->path} ({$type}) [{$branch}]{$locked}\n";
}
Daftar selalu menyertakan worktree utama terlebih dahulu, diikuti worktree tertaut. Panggil worktrees() pada handle repositori utama: pada handle worktree tertaut, entri utama saat ini melaporkan jalur worktree tertaut itu sendiri (#14).
Menghapus worktree
$repo->removeWorktree('review'); // by metadata name
$repo->removeWorktree('/tmp/review'); // or by worktree path
// Force remove (even if locked)
$repo->removeWorktree('review', force: true);
Menghapus worktree:
- Memeriksa apakah worktree terkunci (gagal kecuali
$forcebernilai true) - Menghapus file
.gitdari direktori worktree - Menghapus direktori metadata dari
.git/worktrees/
File tree kerja tidak otomatis dihapus. Cabang tetap ada.
Membuka worktree tertaut
Anda dapat membuka worktree tertaut seperti repositori lainnya:
$repo = Pitmaster::open('/tmp/review');
// Pitmaster detects the .git file and follows the gitdir reference
echo $repo->isLinkedWorktree(); // true
echo $repo->gitDir(); // '/path/to/project/.git/worktrees/review'
echo $repo->commonGitDir(); // '/path/to/project/.git'
echo $repo->workDir(); // '/tmp/review'
Operasi repositori menyelesaikan jalur sebagai berikut:
- Objek: dibaca dari dan ditulis ke direktori git bersama (
project/.git/objects/) - Konfigurasi: dibaca dari direktori git bersama (
project/.git/config) - Packed-refs: dibaca dari direktori git bersama
- HEAD: per-worktree (setiap worktree melacak cabangnya sendiri), bersama dengan
ORIG_HEAD,MERGE_HEAD, dan ref operasi lainnya - Indeks: per-worktree (setiap worktree memiliki area staging sendiri)
- Ref longgar: cabang dan tag dibagikan di direktori bersama (
project/.git/refs/) - Hook: dibaca dari direktori git per-worktree, sehingga hook bersama tidak dijalankan (#12)
Stash belum menangani worktree tertaut: identitas dibaca dari direktori per-worktree, yang tidak memiliki konfigurasi, dan reflog ditulis di sana (#14).
Objek nilai worktree
use Pitmaster\Worktree\Worktree;
$wt->name; // Metadata name (null for the main worktree)
$wt->path; // Working directory path
$wt->gitDir; // Per-worktree git dir
$wt->branch; // Branch name (null if detached)
$wt->head; // ObjectId of HEAD commit
$wt->isMain; // true for the main worktree
$wt->isDetached; // true if HEAD is detached
$wt->isLocked; // true if worktree is locked
$wt->lockReason; // Lock reason, or null when there is none (an empty reason is also null)
WorktreeManager
Untuk pengelolaan worktree tingkat rendah. WorktreeManager::add() hanya menulis metadata dan file .git: tidak membuat cabang, melakukan checkout file, atau menulis indeks. Gunakan $repo->addWorktree() untuk checkout yang dapat digunakan. Cabang harus sudah ada (atau berupa nama ref lengkap seperti refs/tags/v1.0.0, yang melepaskan HEAD).
use Pitmaster\Worktree\WorktreeManager;
$manager = new WorktreeManager($repo->commonGitDir(), $mainWorkDir);
// List
$worktrees = $manager->list();
// Add
$wt = $manager->add('/tmp/review', 'feature/review');
$wt = $manager->add('/tmp/review', 'feature/review', 'review-app');
// Remove
$manager->remove('review');
$manager->remove('review', force: true);
// Lock/unlock (metadata name only)
$manager->lock('review', 'Do not delete, code review in progress');
$manager->unlock('review');
Mengunci worktree
// Lock (prevents removal)
$manager->lock('review', 'Code review in progress');
// Attempt to remove locked worktree
try {
$manager->remove('review');
} catch (\RuntimeException $e) {
// "Worktree is locked: review (Code review in progress)"
}
// Force remove ignores the lock
$manager->remove('review', force: true);
// Unlock
$manager->unlock('review');
Kunci disimpan sebagai file di .git/worktrees/<name>/locked. Isi file adalah alasan penguncian (boleh kosong).
Mendeteksi worktree
// Check if a path is a linked worktree
if (Pitmaster::isWorktree('/tmp/review')) {
echo "This is a linked worktree\n";
}
// Resolve the common git dir from any path
$common = Pitmaster::commonGitDir('/tmp/review');
// '/path/to/project/.git'
// Resolve a .git file to the actual git directory
$gitDir = WorktreeManager::resolveGitFile('/tmp/review');
// '/path/to/project/.git/worktrees/review'
Tata letak file
Metadata worktree utama untuk worktree tertaut
.git/worktrees/<name>/
HEAD Per-worktree HEAD (symbolic ref or detached hash)
commondir Relative path to the shared git dir ("../..")
gitdir Path back to the worktree's .git file, as passed to addWorktree()
locked Present if locked (content = reason)
index Per-worktree index/staging area
File .git worktree tertaut
gitdir: <relative path to>/project/.git/worktrees/<name>
Berbagi sumber daya
| Sumber daya | Lokasi | Dibagikan? |
|---|---|---|
| Objek | $commonDir/objects/ |
Ya |
| Konfigurasi | $commonDir/config |
Ya |
| Packed-refs | $commonDir/packed-refs |
Ya |
| Hook | $gitDir/hooks/ |
Tidak: Pitmaster tidak menjalankan hook bersama di worktree tertaut |
| HEAD | $gitDir/HEAD |
Tidak (per-worktree) |
| Indeks | $gitDir/index |
Tidak (per-worktree) |
| Ref longgar | $commonDir/refs/ |
Ya (HEAD dan ref operasi seperti MERGE_HEAD bersifat per-worktree) |