Lewati ke konten

Submodul

Git submodul menyematkan satu repositori di dalam repositori lain. Submodul direpresentasikan dalam tree induk sebagai entri gitlink (mode 160000) yang menunjuk ke commit tertentu di repositori submodul. URL dan jalur submodul dideklarasikan dalam file .gitmodules.

Konsep

Dalam tree repositori induk:

Kode
100644 blob abc123...  README.md
040000 tree def456...  src/
160000 commit 789012... lib/external    <-- gitlink entry (submodule)

Entri gitlink mencatat commit submodul mana yang harus di-checkout. Repositori submodul yang sebenarnya berada di .git/modules/<name>/.

SubmoduleManager

PHP
use Pitmaster\Submodule\SubmoduleManager;

$manager = new SubmoduleManager(
    $repo->objectDatabase(),
    $repo->workDir(),
    $repo->gitDir(),
);

Daftar submodul

PHP
$submodules = $manager->list();

foreach ($submodules as $sub) {
    echo "{$sub->name}: {$sub->url} at {$sub->path}\n";
    if ($sub->branch !== null) {
        echo "  tracks branch: {$sub->branch}\n";
    }
}

Objek nilai submodul

PHP
use Pitmaster\Submodule\Submodule;

$sub->name;     // Submodule name (from .gitmodules section header)
$sub->path;     // Path in the working tree
$sub->url;      // Remote URL
$sub->branch;   // Tracked branch (nullable)

Mengurai .gitmodules

File .gitmodules mendeklarasikan konfigurasi submodul:

Ini
[submodule "lib/external"]
    path = lib/external
    url = https://github.com/vendor/lib.git
    branch = main

[submodule "tools/helper"]
    path = tools/helper
    url = https://github.com/vendor/helper.git
PHP
$content = file_get_contents('.gitmodules');
$submodules = SubmoduleManager::parseGitmodules($content);

foreach ($submodules as $sub) {
    echo "{$sub->name} -> {$sub->url}\n";
}

Parser menangani:

  • Header bagian: [submodule "<name>"]
  • Pasangan kunci-nilai: path = ..., url = ..., branch = ...
  • Komentar (baris yang diawali #)
  • Spasi dan indentasi

Entri tanpa path dan url akan dilewati. Nilai tidak dihapus tanda kutipnya.

Entri gitlink adalah entri tree dengan mode 160000. Entri tersebut menunjuk ke commit (bukan tree atau blob) di repositori submodul.

PHP
$tree = $repo->readObject($treeHash);

foreach ($tree->entries as $entry) {
    if ($entry->isGitlink()) {
        echo "Submodule at {$entry->name}: commit {$entry->hash->hex}\n";
    }
}

Deteksi TreeEntry

PHP
$entry->isGitlink();  // true if mode is '160000'
$entry->mode;         // '160000'

Menginisialisasi submodul

PHP
$manager->init();

Untuk setiap submodul yang dideklarasikan di .gitmodules, proses ini:

  1. Membuat direktori .git/modules/<name>/ yang kosong
  2. Membuat direktori jalur submodul jika belum ada
  3. Mencatat submodule.<name>.active = true dan submodule.<name>.url di .git/config

Proses ini tidak mengambil atau melakukan checkout apa pun, dan tidak menulis file .git submodul.

Memperbarui submodul

PHP
$manager->update($repo->head()->tree);

Untuk setiap submodul yang dipatok dalam tree yang diberikan, update() menyalin direktori git repositori sumber ke .git/modules/<name>/, mengganti file kerja submodul dengan file dari sumber, menulis file .git yang menunjuk ke direktori modul, menetapkan core.worktree, dan melepaskan HEAD pada commit yang dipatok.

URL submodul harus berupa jalur lokal: absolut, atau relatif terhadap tree kerja induk (git menyelesaikan URL relatif berdasarkan remote induk URL). URL dengan skema (https://...) melempar RuntimeException('Submodule update currently supports only local paths: ...').

Setelah pembaruan, Anda dapat membuka setiap submodul sebagai repositori terpisah:

PHP
$subRepo = Pitmaster::open($repo->workDir() . '/lib/external');

Memeriksa commit yang dipatok

PHP
$headCommit = $repo->head();
$pinnedId = $manager->pinnedCommit('lib/external', $headCommit->tree);

if ($pinnedId !== null) {
    echo "lib/external is pinned to commit {$pinnedId->hex}\n";
}

Metode pinnedCommit() menelusuri hierarki tree untuk menemukan entri gitlink pada jalur yang diberikan dan mengembalikan hash commit targetnya.

Status submodul

PHP
$headCommit = $repo->head();
$statuses = $manager->status($headCommit->tree);

foreach ($statuses as $status) {
    $expected = $status['expected'] ?? 'not pinned';
    $actual = $status['actual'] ?? 'not initialized';
    $dirty = $status['dirty'] ? ' (dirty)' : '';

    echo "{$status['name']}: expected={$expected}, actual={$actual}{$dirty}\n";
}

Kolom status:

  • name: nama submodul
  • path: jalur di tree kerja
  • expected: hash commit yang dipatok di tree induk (null jika tidak ada)
  • actual: HEAD submodul saat ini (null jika belum diinisialisasi)
  • dirty: true jika kedua commit diketahui dan berbeda. Perubahan di dalam tree kerja submodul tidak diperiksa.

Struktur direktori

Kode
project/
  .git/
    modules/
      lib/
        external/          # Submodule's actual git directory (.git/modules/<name>)
        HEAD
        objects/
        refs/
        ...
  .gitmodules              # Submodule declarations
  lib/
    external/
      .git                 # File: "gitdir: ../../.git/modules/lib/external"
      src/
      README.md
  src/
  README.md

Alur kerja submodul

PHP
// 1. Open the parent repo
$repo = Pitmaster::open('/path/to/project');

// 2. List submodules
$manager = new SubmoduleManager(
    $repo->objectDatabase(),
    $repo->workDir(),
    $repo->gitDir(),
);

$submodules = $manager->list();

// 3. Populate the submodules pinned at HEAD (local source paths only)
$manager->update($repo->head()->tree);

// 4. Check status
$statuses = $manager->status($repo->head()->tree);

// 5. Open a submodule as its own repo
$subRepo = Pitmaster::open($repo->workDir() . '/lib/external');
$subHead = $subRepo->head();
echo "Submodule HEAD: {$subHead->id->hex}\n";