Checkpoint Lab — Submodules, Subtrees, Nested Dependencies, and Multi-Repository Tradeoffs
Checkpoint an exact submodule pin and update, prove it from fresh clones, repeat the dependency with a squashed subtree, and write a topology decision memo for real operational scenarios.
Learning objectives
- Create and update an exact submodule dependency pin in local parent/dependency repositories.
- Predict and verify gitlink behavior before and after the dependency advances.
- Prove reproducibility through normal and recursive clean clones.
- Vendor the same dependency with subtree and verify ordinary-clone behavior.
- Write a decision memo selecting submodule, subtree, monorepo, or package dependency for three scenarios.
1. Checkpoint scenario — one parent, one source dependency, two composition models
You will create a local dependency and parent repository, add the
dependency as a submodule, advance it deliberately, and prove a
fresh clone reconstructs the exact pin. Then a separate repository
will vendor the same dependency with git subtree.
Finally you will write a decision memo choosing among submodule,
subtree, monorepo, and package dependency for three realistic
situations.
2. Predictions before state changes
-
After
submodule add, what mode shouldgit ls-files --stage deps/libraryshow? - After the dependency moves from v1 to v2 but before the parent stages the submodule path, which OID should the parent's committed gitlink still contain?
- Will a normal fresh parent clone populate the dependency automatically?
-
Will a subtree clone need
git submodule updateto see vendored files?
3. Create the disposable dependency and parent remotes
Git Bash, Bash, or zsh
mkdir git-composition-checkpoint
cd git-composition-checkpoint
git init --bare dependency.git
git clone dependency.git dependency-work
cd dependency-work
git switch -c trunk
git config user.name "Checkpoint Dependency"
git config user.email "dependency@example.invalid"
printf "api=1\nbehavior=v1\n" > library.conf
git add library.conf
git commit -m "Create dependency v1"
DEP_V1=$(git rev-parse HEAD)
git push -u origin trunk
cd ..
git --git-dir=dependency.git symbolic-ref HEAD refs/heads/trunk
git init --bare parent.git
git clone parent.git parent-work
cd parent-work
git switch -c trunk
git config user.name "Checkpoint Parent"
git config user.email "parent@example.invalid"
printf "# Parent Application\n" > README.md
git add README.md
git commit -m "Create parent baseline"
git push -u origin trunk
cd ..
git --git-dir=parent.git symbolic-ref HEAD refs/heads/trunk
PowerShell setup alternative
New-Item -ItemType Directory git-composition-checkpoint | Out-Null
Set-Location git-composition-checkpoint
git init --bare dependency.git
git clone dependency.git dependency-work
Set-Location dependency-work
git switch -c trunk
git config user.name "Checkpoint Dependency"
git config user.email "dependency@example.invalid"
@('api=1','behavior=v1') | Set-Content library.conf
git add library.conf
git commit -m "Create dependency v1"
$DEP_V1 = git rev-parse HEAD
git push -u origin trunk
Set-Location ..
git --git-dir=dependency.git symbolic-ref HEAD refs/heads/trunk
git init --bare parent.git
git clone parent.git parent-work
Set-Location parent-work
git switch -c trunk
git config user.name "Checkpoint Parent"
git config user.email "parent@example.invalid"
Set-Content README.md '# Parent Application'
git add README.md
git commit -m "Create parent baseline"
git push -u origin trunk
Set-Location ..
git --git-dir=parent.git symbolic-ref HEAD refs/heads/trunk
4. Add and pin dependency v1 as a submodule
cd parent-work
git -c protocol.file.allow=always submodule add ../dependency.git deps/library
git diff --cached -- .gitmodules deps/library
git ls-files --stage deps/library
git submodule status
git rev-parse :deps/library
git add .gitmodules deps/library
git commit -m "Pin dependency v1"
git push
PARENT_V1=$(git rev-parse HEAD)
Verify prediction 1: the index mode is
160000.
Verify prediction 2 baseline: the committed gitlink
equals DEP_V1.
test "$(git rev-parse HEAD:deps/library)" = "$DEP_V1"
5. Advance the dependency independently
cd ../dependency-work
printf "api=1\nbehavior=v2\n" > library.conf
git add library.conf
git commit -m "Create dependency v2"
DEP_V2=$(git rev-parse HEAD)
git push
cd ../parent-work
The dependency remote has moved. The parent has not. This is intentional decoupling.
git rev-parse HEAD:deps/library
git -C deps/library rev-parse HEAD
Both still show v1 because the nested checkout has not been advanced yet.
6. Select v2 inside the submodule, inspect pointer drift, then commit it
git -C deps/library fetch origin
git -C deps/library switch --detach "$DEP_V2"
git status --short
git diff --submodule=log -- deps/library
git rev-parse HEAD:deps/library
git -C deps/library rev-parse HEAD
Verify prediction 2: until you stage/commit the submodule path, the committed superproject gitlink still points to v1 even though the nested working directory is at v2.
git add deps/library
git diff --cached --submodule=log -- deps/library
git commit -m "Update dependency pin to v2"
git push
PARENT_V2=$(git rev-parse HEAD)
test "$(git rev-parse HEAD:deps/library)" = "$DEP_V2"
7. Fresh clone without recursion: prove the pin exists but content is not populated
cd ..
git clone parent.git verify-plain
cd verify-plain
git rev-parse HEAD:deps/library
git submodule status
ls deps/library
cd ..
Verify prediction 3: the clone knows the v2 gitlink, but the dependency checkout is uninitialized.
8. Fresh recursive clone: prove source reproducibility
GIT_ALLOW_PROTOCOL=file git clone --recurse-submodules parent.git verify-recursive
cd verify-recursive
SUPER_PIN=$(git rev-parse HEAD:deps/library)
NESTED_HEAD=$(git -C deps/library rev-parse HEAD)
printf "pin=%s\n" "$SUPER_PIN"
printf "nested=%s\n" "$NESTED_HEAD"
test "$SUPER_PIN" = "$NESTED_HEAD"
cat deps/library/library.conf
git submodule status
cd ..
The expected file contains behavior=v2. This proves
that the superproject commit plus obtainable dependency repository
reconstructs the intended source revision.
9. Vendor the same dependency in a separate subtree repository
git init -b trunk subtree-parent
cd subtree-parent
git config user.name "Checkpoint Subtree"
git config user.email "subtree@example.invalid"
printf "# Subtree Parent\n" > README.md
git add README.md
git commit -m "Create subtree baseline"
git subtree add --prefix=vendor/library --squash ../dependency.git trunk
git status --short
git log --graph --decorate --oneline --max-count=12
cat vendor/library/library.conf
SUBTREE_PARENT=$(git rev-parse HEAD)
cd ..
Verify prediction 4:
vendor/library/library.conf is ordinary
parent-repository content. A normal clone of
subtree-parent needs no submodule initialization.
git clone subtree-parent subtree-verify
cat subtree-verify/vendor/library/library.conf
git -C subtree-verify submodule status
10. Compare the two reproducibility contracts
| Question | Submodule checkpoint | Subtree checkpoint |
|---|---|---|
| Where is dependency version recorded? | Gitlink OID in parent commit | Vendored file tree in parent commit |
| Need second repository at checkout? | Yes | No |
| Dependency history independent? | Yes | Original source is independent; imported state/history also becomes part of parent representation |
| Fresh clone command | Recursive clone/update for populated dependency | Ordinary clone |
| Access boundary | Can require separate dependency permission | Consumer needs only parent repo for vendored files |
11. Write the decision memo
Create DEPENDENCY-TOPOLOGY-MEMO.md outside the
disposable repositories and answer these three scenarios:
- Industrial firmware: proprietary controller library is owned by a separate team, has separate permissions, but the product build must pin an exact source commit. Choose among submodule, subtree, monorepo, package dependency and justify.
- Small audited C library: upstream changes twice per year; downstream users must be able to build after a normal clone even when upstream is offline. Choose and justify.
- Internal Python SDK: multiple services consume a stable API and should upgrade independently through CI. Choose and justify.
Your memo must address maintainability, access control, reproducibility, CI complexity, ownership, and update review—not only personal preference.
12. Produce a compact provenance report
printf "dependency_v1=%s\n" "$DEP_V1"
printf "dependency_v2=%s\n" "$DEP_V2"
printf "parent_v1=%s\n" "$PARENT_V1"
printf "parent_v2=%s\n" "$PARENT_V2"
printf "subtree_parent=%s\n" "$SUBTREE_PARENT"
git --git-dir=parent.git show "$PARENT_V2":.gitmodules
git --git-dir=parent.git ls-tree "$PARENT_V2" deps/library
This is the kind of evidence CI/release automation should preserve: parent commit, dependency pin, dependency source configuration, and build/test results.
13. Verification checklist
-
The parent tracks
.gitmodulesas a normal file anddeps/libraryas mode160000. - Parent v1 pins exactly
DEP_V1. - Advancing the dependency remote alone does not change the parent gitlink.
- Selecting dependency v2 creates visible submodule “new commits” state before staging.
- Parent v2 pins exactly
DEP_V2. - A normal clone contains the gitlink but not a populated dependency checkout.
- A recursive clone checks out exactly the v2 pin.
- The subtree parent contains vendored dependency files after an ordinary clone.
- No force push, history rewrite, or global transport-policy weakening was used.
- The decision memo addresses three scenarios with operational tradeoffs.
14. Cleanup
Git Bash / Bash / zsh
cd ..
pwd
rm -rf git-composition-checkpoint
PowerShell
Set-Location ..
Get-Location
Remove-Item -Recurse -Force git-composition-checkpoint
15. Knowledge check
Question 1. Why did parent v1 remain reproducible after dependency trunk advanced to v2?
Question 2. Why was a recursive clone required for the submodule verification?
Question 3. A developer builds v2 locally but CI still builds v1. What should you inspect first?
Question 4. Why does the subtree verification need no dependency repository checkout?
Question 5. Which topology best fits a stable SDK consumed independently by many services?
16. What Chapter 13 adds to a production Git operating model
You can now identify exactly where a multi-repository dependency version lives, materialize and verify nested pins, update a submodule without pointer drift, review dependency changes as first-class supply-chain events, vendor source with subtree when ordinary checkout simplicity matters, and choose a topology from operational constraints rather than habit.
17. Chapter checkpoint summary
Reproducibility comes from an explicit immutable dependency state plus a documented way to obtain it. For submodules that state is a gitlink commit; for subtree it is the imported parent tree/history; for packages/artifacts it is a released version/digest. Architecture decides which boundary is appropriate.
Authoritative references
gitsubmodules
git-submodule
git-clone
git-subtree source documentation
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.