Υπομονάδες
Οι υπομονάδες Git ενσωματώνουν ένα αποθετήριο μέσα σε ένα άλλο. Στο δέντρο του γονικού αποθετηρίου, μια υπομονάδα αναπαρίσταται ως εγγραφή gitlink (λειτουργία 160000) που δείχνει σε συγκεκριμένο commit του αποθετηρίου της. Το URL και η διαδρομή της υπομονάδας δηλώνονται στο αρχείο .gitmodules.
Βασικές έννοιες
Στο δέντρο του γονικού αποθετηρίου:
100644 blob abc123... README.md
040000 tree def456... src/
160000 commit 789012... lib/external <-- gitlink entry (submodule)
Η εγγραφή gitlink καταγράφει ποιο commit της υπομονάδας πρέπει να γίνει checkout. Το ίδιο το αποθετήριο της υπομονάδας βρίσκεται στο .git/modules/<name>/.
SubmoduleManager
use Pitmaster\Submodule\SubmoduleManager;
$manager = new SubmoduleManager(
$repo->objectDatabase(),
$repo->workDir(),
$repo->gitDir(),
);
Λίστα υπομονάδων
$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";
}
}
Αντικείμενο τιμής υπομονάδας
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)
Ανάλυση .gitmodules
Το αρχείο .gitmodules δηλώνει τις ρυθμίσεις των υπομονάδων:
[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
$content = file_get_contents('.gitmodules');
$submodules = SubmoduleManager::parseGitmodules($content);
foreach ($submodules as $sub) {
echo "{$sub->name} -> {$sub->url}\n";
}
Ο αναλυτής χειρίζεται:
- Κεφαλίδες ενότητας:
[submodule "<name>"] - Ζεύγη κλειδιού-τιμής:
path = ...,url = ...,branch = ... - Σχόλια (γραμμές που αρχίζουν με
#) - Κενά και εσοχές
Οι εγγραφές χωρίς και τα δύο path και url παραλείπονται. Οι τιμές δεν αφαιρούν τα εισαγωγικά.
Εγγραφές Gitlink
Οι εγγραφές gitlink είναι εγγραφές δέντρου με λειτουργία 160000. Δείχνουν σε commit (όχι σε δέντρο ή blob) του αποθετηρίου της υπομονάδας.
$tree = $repo->readObject($treeHash);
foreach ($tree->entries as $entry) {
if ($entry->isGitlink()) {
echo "Submodule at {$entry->name}: commit {$entry->hash->hex}\n";
}
}
Ανίχνευση TreeEntry
$entry->isGitlink(); // true if mode is '160000'
$entry->mode; // '160000'
Αρχικοποίηση υπομονάδων
$manager->init();
Για κάθε υπομονάδα που δηλώνεται στο .gitmodules, εκτελούνται τα εξής:
- Δημιουργείται ένας κενός κατάλογος
.git/modules/<name>/ - Δημιουργείται ο κατάλογος της διαδρομής της υπομονάδας, αν δεν υπάρχει
- Καταγράφονται τα
submodule.<name>.active = trueκαιsubmodule.<name>.urlστο.git/config
Δεν γίνεται λήψη ή checkout και δεν γράφεται το αρχείο .git της υπομονάδας.
Ενημέρωση υπομονάδων
$manager->update($repo->head()->tree);
Για κάθε υπομονάδα που είναι καθηλωμένη στο συγκεκριμένο δέντρο, το update() αντιγράφει τον κατάλογο git του πηγαίου αποθετηρίου στο .git/modules/<name>/, αντικαθιστά τα αρχεία εργασίας της υπομονάδας με εκείνα της πηγής, γράφει το αρχείο .git που δείχνει στον κατάλογο της μονάδας, ορίζει το core.worktree και αποσυνδέει το HEAD στο καθηλωμένο commit.
Το URL της υπομονάδας πρέπει να είναι τοπική διαδρομή: απόλυτη ή σχετική προς τον κατάλογο εργασίας του γονέα (το git επιλύει αντίθετα τα σχετικά URL βάσει του απομακρυσμένου URL του γονέα). Ένα URL με σχήμα (https://...) προκαλεί RuntimeException('Submodule update currently supports only local paths: ...').
Μετά την ενημέρωση, μπορείτε να ανοίξετε κάθε υπομονάδα ως ξεχωριστό αποθετήριο:
$subRepo = Pitmaster::open($repo->workDir() . '/lib/external');
Έλεγχος καθηλωμένων commit
$headCommit = $repo->head();
$pinnedId = $manager->pinnedCommit('lib/external', $headCommit->tree);
if ($pinnedId !== null) {
echo "lib/external is pinned to commit {$pinnedId->hex}\n";
}
Η μέθοδος pinnedCommit() διασχίζει την ιεραρχία του δέντρου για να βρει την εγγραφή gitlink στη συγκεκριμένη διαδρομή και επιστρέφει το hash του commit-στόχου.
Κατάσταση υπομονάδας
$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";
}
Πεδία κατάστασης:
name: όνομα υπομονάδαςpath: διαδρομή στον κατάλογο εργασίαςexpected: hash commit που έχει καθηλωθεί στο γονικό δέντρο (null αν απουσιάζει)actual: τρέχον HEAD της υπομονάδας (null αν δεν έχει αρχικοποιηθεί)dirty: true όταν είναι γνωστά και τα δύο commit και διαφέρουν. Δεν ελέγχονται οι αλλαγές μέσα στον κατάλογο εργασίας της υπομονάδας.
Δομή καταλόγων
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
Ροή εργασίας υπομονάδων
// 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";