How LazySubmodules works¶
Four records describe each submodule, and LazySubmodules keeps them in
agreement using nothing but the git command.
Four records¶
Plain Git knows two things about a submodule: the gitlink, the commit that the superproject records, and the checkout, the commit that is checked out in the submodule. LazySubmodules adds two records of its own:
- Tracking configuration
lsm-modeandlsm-refin.gitmodules: what the submodule should follow, such as the tag patternv6.6.*. You change it withset.- Lock entry
mode,refandcommitin.lsm.lock: what the configuration resolved to at the last update, such as tagv6.6.10at commit08dcd0dc8f98….updateandaddwrite it.- Gitlink
The commit that the superproject records, in the index and, once committed, in
HEAD.- Checkout
The commit that is checked out in the submodule, usually as a detached HEAD.
When everything is in order, the lock commit, the gitlink and the checkout are the same commit, and the lock entry is what the configuration selects today.
The four records and the commands that read and write them.
Each command looks at a different part:
statuscompares the tracking configuration, the lock entry and the checkout with the local refs: is the configured ref still available, and would an update select another target? It does not read the gitlink, which is why a staged or committed gitlink that disagrees with the lock entry shows up inverifyand not here. The result is one state per submodule; see States.verifycompares the lock entry with the gitlink in the committedHEAD, with the checkout and, for tags, with the tag the lock names. It also checks that the lock entry still matches the configuration. See Checks.updatereads the configuration and writes the other three: it checks out the target, writes the lock entry and stages the gitlink.
Why a lock file¶
A gitlink says which commit the superproject uses, but not why. With a lock entry, the superproject also records the tag or branch that led to that commit. That has two uses:
Moved tags become visible. A release tag should never move, but it can be force-pushed. When the local tag no longer points to the locked commit,
statusreportsdriftandverifyfails. See Detect moved tags.Updates are predictable. In
tag-patternmode, the locked tag stays a candidate, so an update never goes back to an older tag; see Never downgrade.
Both files are plain git-config files that are committed with the gitlinks, so their history is the history of your dependencies. The format is described in Files: .gitmodules and .lsm.lock.
What update does¶
The steps of an update. Dashed steps run only with their option.
Select. The named submodules, or every managed one, in
.gitmodulesorder.Check. Every selected submodule is checked: uncommitted changes, a path that cannot be updated safely, and the syntax of the configured ref. Without
--fetch, the target is resolved in the local refs here as well. One refusal stops the whole update before anything is fetched or checked out; a dry run stops here and prints the plan.Fetch. Only with
--fetch: fetch fromorigin, and clone submodules that need it. The targets are resolved afterwards, in the refs the fetch brought in, so a target that is still missing is refused at this point — after the clones, which stay.Check out each target, initializing submodules where needed.
Write the lock entries, and the native
branchkey where the tracking mode needs it.Stage the gitlinks,
.gitmodulesand.lsm.lockwithgit add.Commit. Only with
--commit: one commit withgit commit -sand a generated message.
An interruption before step 6 puts back what steps 4 and 5 changed: the
submodules it moved return to their previous commit, and the lock entries
and native branch keys get their previous values back. Submodules that
the update initialized stay initialized. Once the result is staged, only
the commit remains; when it fails, the update stays staged.
Safety model explains the checks in step 2.
Everything through git¶
LazySubmodules has no Git implementation of its own. It runs the git
command for every read and write, with arguments passed directly and
never through a shell.
Configuration applies. Mirrors (
url.<base>.insteadOf), credential helpers, proxies, SSH settings, hooks and signing keys work as they do for Git itself.Files are read and written with
git config -f..gitmodulesand.lsm.lockare never parsed by hand.Output is parsed in the C locale. Every
gitinvocation setsLC_ALL=C, so the language of your system does not matter.Git 2.39 is the floor. LazySubmodules uses no newer Git features, and it uses the classic
git configoptions (--get,--unset-all,--list) instead of the subcommands that Git 2.46 added. The test suite runs against Git 2.39.5 as well as current Git.
Inside the binary¶
The program is written in Go and built as one static binary. It has two front ends over one core:
Layer |
Responsibility |
|---|---|
|
Thin wrapper over the |
|
Read and write the keys in |
|
Read and write |
|
Resolution, update, status and verify: all business logic |
|
The stable machine-readable output |
|
The terminal interface; it calls |
|
The command line: flag parsing and exit codes |
The terminal interface contains no business logic. Every action is a
call into internal/core, the same code that the command line uses, so
both front ends behave the same.
See also¶
Files: .gitmodules and .lsm.lock: the format of both files.
States: how
statusturns the records into a state.Safety model: what
updaterefuses, and why.CONTRIBUTING.md: the architecture rules for contributors.