Chapter 13Lesson 05~155 minutes

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.

CheckpointFresh-clone proofSubtree vendorDecision memo

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

  1. After submodule add, what mode should git ls-files --stage deps/library show?
  2. 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?
  3. Will a normal fresh parent clone populate the dependency automatically?
  4. Will a subtree clone need git submodule update to 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:

  1. 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.
  2. 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.
  3. 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 .gitmodules as a normal file and deps/library as mode 160000.
  • 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

Verify the current path before recursive deletion. Preserve the decision memo elsewhere if you want to keep it.

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.

Next chapter

Sparse Checkout, Partial Clone, Shallow Clone, LFS, and Large Repository Access

Chapter 14 asks a different scaling question: once the repository topology is chosen, how can Git reduce working-tree size, history depth, transferred objects, or large-binary pressure without confusing those mechanisms?

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.