Update submodules¶
Move managed submodules to their targets, stage the result, and find out why an update was refused.
The workflow¶
lazysubmodules fetch # download branches and tags (network)
lazysubmodules update --dry-run # show what would change
lazysubmodules update # check out, write .lsm.lock, stage
git commit -s # or: lazysubmodules update --commit
update resolves targets in the refs that already exist locally, so
fetch first. update --fetch combines both steps.
lazysubmodules update [<name>...] [--fetch] [--dry-run] [--commit] [--include-prerelease]
Selection. Without names,
updateworks on every managed submodule. With names, only on those. Either way, submodules are processed and reported in.gitmodulesorder.Unmanaged submodules are skipped when no names are given, and refused (exit status 3) when named.
Option |
Effect |
|---|---|
|
Fetch from |
|
Print the plan only. Never fetches, initializes or clones, even with |
|
Create one commit with the result; see Commit updates |
|
Let tag patterns select pre-release tags |
Preview with a dry run¶
A dry run resolves the targets in the local refs and changes nothing:
$ lazysubmodules update --dry-run kernel u-boot theme sdk
would update kernel: v6.6.9 (8106f61) -> v6.6.10 (08dcd0d)
would update u-boot: main (29b295c) -> main (8e9fc7a)
would update theme: v1.0.0 (05f49f3), initialize
sdk: up to date
[exit status 0: success]
kernelwould move tov6.6.10: version sorting puts it abovev6.6.9, andv6.6.11-rc1is a pre-release.themewould be initialized from the repository thatgit submodule deinitleft behind, without network access.sdkkeeps its release candidate, becausev3.0.0-rc.2is still its highest matching tag and the locked tag is never skipped for being a pre-release.
Pre-release tags count only on request:
$ lazysubmodules update --dry-run --include-prerelease kernel sdk
would update kernel: v6.6.9 (8106f61) -> v6.6.11-rc1 (50d5d57)
sdk: up to date
[exit status 0: success]
The [exit status N: …] lines come from the demo, not from
LazySubmodules.
A dry run never uses the network, even with --fetch. A submodule that
would need a clone is listed with the target unknown until fetched:
$ lazysubmodules update --dry-run --fetch fresh
would update fresh: v1.1.0 (f2de3e1) -> unknown until fetched, clone
Update and stage¶
Without --dry-run, update checks out each target, writes the lock
entries and stages the gitlinks, .gitmodules and .lsm.lock:
$ lazysubmodules update kernel u-boot
kernel: v6.6.9 (8106f61) -> v6.6.10 (08dcd0d)
u-boot: main (29b295c) -> main (8e9fc7a)
$ git status --short
M .lsm.lock
m apps/app
M bootloader/u-boot
M kernel
m tools/nested
The submodule HEAD is detached at the target commit, which is normal for
submodules. Commit the staged result yourself, or use --commit.
Important
verify checks the committed state. Until the staged update is
committed, it fails for the updated submodules:
$ lazysubmodules verify kernel
kernel: failed (1 of 7 checks)
gitlink: HEAD of the superproject records 8106f614767a, locked 08dcd0dc8f98
lazysubmodules: verification failed: kernel
Uninitialized submodules¶
update initializes submodules that are not checked out:
Offline, when the submodule’s Git directory still exists, for example after
git submodule deinit. The line then ends withinitialized, as forthemein the demo.With a clone, only when
--fetchis given. Without it,updaterefuses, because a clone uses the network:
$ lazysubmodules update fresh
lazysubmodules: fresh: refused: submodule is not initialized (use --fetch)
[exit status 3: refused]
$ lazysubmodules update --fetch fresh
Submodule 'fresh' (https://git.example.invalid/fresh.git) registered for path 'third_party/fresh'
Cloning into '$DEMO/firmware/third_party/fresh'...
Submodule path 'third_party/fresh': checked out 'f2de3e1ec47a9de1bd0316651efbfce990587c49'
fresh: v1.1.0 (f2de3e1), cloned
[exit status 0: success]
All or nothing¶
Before it changes anything, update checks every selected submodule and
reports every problem, one per line. If a single submodule is refused,
nothing is checked out, written or staged, and the exit status is 3:
$ lazysubmodules update
lazysubmodules: fresh: refused: submodule is not initialized (use --fetch)
lazysubmodules: app: refused: submodule has uncommitted changes
lazysubmodules: broken: refused: ref not found in local refs: tag v9.9.9 does not exist or does not point to a commit
[exit status 3: refused]
$ git status --short
m apps/app
m tools/nested
The index is untouched; the two m lines are the change in app and
the nested submodule of tools, which were there before.
update refuses when:
a submodule working tree has uncommitted changes. Untracked files do not count, nor does a nested submodule that is only checked out at another commit;
the resolved ref does not exist locally (run
fetch, or use--fetch);a submodule is not initialized, its Git directory is missing, and
--fetchwas not given;--commitis given and the commit would include unrelated changes, or the index has unresolved merge conflicts;an unmanaged submodule is named explicitly;
a submodule cannot be updated safely: its path leads through a symbolic link, the index records no submodule at its path, or it would be initialized but has no usable URL, or something other than a Git repository is in the place of its Git directory.
Fix each problem, then run the update again. In the demo:
$ git -C apps/app status --short
M NEWS
$ git -C apps/app restore NEWS
$ git -C libs/broken tag --list
v1.0.0
$ lazysubmodules set broken --tag v1.0.0
broken: tracks tag v1.0.0
[exit status 0: success]
$ lazysubmodules update --commit broken
broken: v1.0.0 (52c4bdb), recorded again
committed e232291a68bdc567d1fb218ebcf063a15800a291
[exit status 0: success]
$ lazysubmodules update --dry-run
lazysubmodules: fresh: refused: submodule is not initialized (use --fetch)
[exit status 3: refused]
Troubleshooting lists every refusal with its fix.
With --fetch¶
--fetch runs checks that need no fetched refs, such as uncommitted
changes, before it fetches. The fetch itself then clones and initializes
submodules as needed, and the remaining checks run on the fetched refs.
A submodule refused at that point still stops the update, but the clones
and fetched refs stay:
$ git -C apps/app restore NEWS
$ lazysubmodules update --fetch
Submodule 'theme' (https://git.example.invalid/theme.git) registered for path 'docs/theme'
Submodule path 'docs/theme': checked out '05f49f3f8b5a18447c1e10f18f28fc7e221d8110'
Submodule 'fresh' (https://git.example.invalid/fresh.git) registered for path 'third_party/fresh'
Cloning into '$DEMO/firmware/third_party/fresh'...
Submodule path 'third_party/fresh': checked out 'f2de3e1ec47a9de1bd0316651efbfce990587c49'
lazysubmodules: broken: refused: ref not found in local refs: tag v9.9.9 does not exist or does not point to a commit
Here theme and fresh are now checked out at the commits the
superproject records, but no submodule was moved to a new target, the
lock file is unchanged and nothing is staged.
Reading the output¶
update prints one line per submodule:
kernel: v6.6.9 (8106f61) -> v6.6.10 (08dcd0d)
u-boot: up to date
theme: v1.0.0 (05f49f3), initialized
Left side: what the superproject records, in the index, or in
HEADwith--commit. It shows the locked ref and commit,<commit> (unlocked)without a lock entry,nonewithout a gitlink, and the bare commit when a lock entry exists but the superproject records another commit, as after a checkout inside the submodule.Right side: the target. When the tracking mode changes, both sides show the mode.
up to date: nothing to do for this submodule.
Notes at the end of a line explain what else happened:
Note |
Meaning |
|---|---|
|
The submodule was not checked out before |
|
The submodule was checked out at another commit than the recorded one |
|
The superproject records the same commit again, for example after |
|
The superproject records the target already, but the working tree copy of the file does not: the lock entry differs or is missing, or the native |
|
Only with |
A dry run starts each line with would update, and its notes read
initialize, clone, HEAD is <commit>, record again,
restore <files> and discard the staged change. Without managed
submodules, update prints no managed submodules.
This output is meant for people and may change. Scripts should read
status --porcelain=v1 instead; see Scripts and CI.
Interrupting an update¶
Ctrl+C, SIGTERM and SIGHUP cancel the update and terminate the
git processes it started.
An update interrupted before staging puts back what it changed: submodules it already checked out return to their previous commit, and nothing is staged.
Once the result is staged, only the commit remains. When
git commitis interrupted or fails, for example in a hook, the update stays staged.The command exits with the status of the failure, usually 5, because a
gitcommand was killed. A second signal ends LazySubmodules at once, without waiting for the clean-up.
See Signals for the details.
See also¶
update: every option and message.
Commit updates:
--commitand its message.Safety model: why
updaterefuses.Mirrors, credentials and offline work: what
--fetchneeds.