A submodule splits a project into smaller reusable parts or vendors a dependency; the goal is a seamless recursive experience where clone, checkout, and commit descend into subs by default. The method reuses Beagle's own machinery: a submodule is just another project in the parent's Store, checked out as a secondary worktree at the gitlink path. Git stays byte-exact — a 160000 gitlink and a root .gitmodules blob name the pin and upstream. be recurses by forking a child into each mount.
A submodule is a secondary worktree of a separate project: the parent tree's gitlink pins which commit, .gitmodules names where to fetch it, and recursion is process-level — be forks a child into each mount.
<wt>/<path>/.be is a file anchoring back to the parent's shared store (see Store).160000 gitlink sha; the root .gitmodules blob supplies the upstream fetch URL.BERecurseInto: fork + chdir(<wt>/<sub>) + execvp self, so each child runs against its own HOME.repo anchor, so a sub needs no separate object pool of its own..gitmodules; later verbs read it from the mount's .be/wtlog or shard refs.
Recursion is the default — every verb descends into mounted subs; --nosub opts out for one invocation. Clones flow top-down, commits bottom-up.
be get ssh://host/repo?ref clones recursively: after the parent checkout, each sub is mounted and reset to its pinned sha.be get --nosub … skips the mount loop and prints submodule(s) skipped; the parent's gitlinks stay unmounted.be post 'msg' commits post-order: dirty subs commit first, then the parent stages each sub's new tip as a gitlink bump.be head recurses pre-order, reporting ahead/behind and dirty state per sub alongside the parent's own diff.be put <subpath> stages one gitlink bump by reading the sub-wt's pinned tip from its .be anchor; no recursion.
Four headers split by dog: dog/git/SUBS is the pure .gitmodules parser/synth, keeper/SUBS enumerates off a tree, sniff/SUBS drives mounts, beagle/SUBS orchestrates recursion.
dog/git/SUBS.h: SUBSu8sParse / SUBSu8sFind / SUBSu8bSynth — pure .gitmodules parse, find-by-path, synth.keeper/SUBS.h: KEEPSubsAt(tree,ts,verb,out) emits a ULOG row <url>?<path>#<pin> per 160000 entry, in decl order.sniff/SUBS.h: SNIFFSubMount drives a mount (anchor + fetch + checkout); SNIFFSubReadTip / SNIFFSubIsMount help.beagle/SUBS.h: BESubsHere enumerates declared subs with mount state; BERecurseInto forks a child be into a mount.beagle/SUBS.h GET helpers: BEGetKeeperSubs, BEGetSubMount, BEGetSubUnmount, BEGetDrainSubs, BEGetDrainRemoved.
Beagle reads and writes submodules exactly as git does — 160000 gitlinks in the parent tree and a gitconfig-format .gitmodules blob — so a beagle-cloned repo round-trips through git untouched.
.gitmodules, is authoritative for the mount set: a section whose path has no 160000 entry is skipped..gitmodules parse tolerates quoted names, #/; comments, CRLF, whitespace; sections missing path/url are skipped.SUBSu8bSynth mirrors git submodule add: one [submodule "<path>"] section per pair, tab-indented path = then url =.A submodule is just another project shard in the parent's store, mounted as a secondary worktree that points back; there is no separate submodule store class.
<parent_root>/.be/<title>/, a shard beside the parent's, sharing its keeper (see Store)..gitmodules URL basename, .git and trailing / stripped (…/libabc.git → libabc).<wt>/<path>/.be is a file holding one row <ts>\trepo\tfile://<parent_root>/.be/ — the secondary-wt anchor.<parent>/.be/<sub>/.be/<subsub>/ and deeper share one pool..gitmodules URL is the fallback.
The parent captures each sub's TLV hunk report and relays every hunk into its sequential stream, rebasing only the path prefix — one be lists affected files across the whole tree, for every verb.
HUNKu8sRelay (dog/HUNK) drains a child TLV stream, rebases each URI under a prefix, re-emits via HUNKMode.BERelaySub (beagle/SUBS) forks be <verb> --tlv in a mount, captures stdout, relays the hunks path-prefixed.be, head, get, post, patch, put, delete list affected sub files, path-prefixed, in one stream.*NONE; recursion passes -q and treats it as a no-op, so it never fails a bare be put/delete.A mounted sub sits detached at the gitlink pin; a parent POST commits it onto a synthetic branch so the new commit is a real ref tip, never a GC-orphan.
?/<subproj>/.<parentproj>[/<parentbranch>] — the parent's coordinate, dot-marked, as the sub's branch.?/leaf/.sub/.proj/br; trunk → ?/sub/.proj.160000 gitlink still pins the raw commit sha; the synthetic branch is just the keep-alive anchor in the sub's shard.be post on a detached sub auto-creates that branch at the pin and attaches the wt; later posts fast-forward it.be post //host?/proj/br recurses the push, pushing each sub's ?/subproj/.proj/br; the parent records the sha.be post in a detached sub (no parent driving it) still refuses — no coordinate to derive the branch from.