@cldmv/git-embedded
v1.0.0
Published
Manage embedded git repositories (anonymous gitlinks) without .gitmodules. Provides hooks that restore standard git-command ergonomics for embedded children while keeping the child's origin URL out of the public parent repo.
Maintainers
Readme
@cldmv/git-embedded
Manage embedded git repositories (anonymous gitlinks) without .gitmodules. Provides hooks that restore standard git-command ergonomics for embedded child repos while keeping the child's origin URL out of the public parent repo.
What this is
Git uses gitlinks internally to track sub-repositories: a tree entry of mode 160000 pointing at a specific commit SHA in another repository. Submodules are built on top of gitlinks, with a registry file (.gitmodules) that records the child's URL alongside the gitlink. The URL is what makes git clone --recurse-submodules, git submodule update, and submodule.recurse=true checkout-flavored automation work — but it's also what publicly advertises the child repo's existence and location.
A gitlink without a .gitmodules entry is sometimes called an "anonymous submodule" or "embedded git repo." It's a fully supported git state: the gitlink still pins a specific child SHA, git still recognizes the child as a submodule boundary, and the parent commits a clean reference to the child's pinned version. What's missing is git's automatic update behavior, because the submodule machinery requires registration to act.
This package fills that gap with two git hooks:
reference-transaction— blocksgit checkout,git switch,git reset,git pull,git merge,git rebase,git bisect, andgit cherry-pickwhen any embedded child repo has uncommitted changes. Prevents the silent inconsistent-state failure mode where the parent moves to a new commit but the child stays behind, dirty.update-embedded-repos— installed aspost-checkout,post-merge, andpost-rewrite. After a HEAD-moving operation, walks every gitlink in the new HEAD and updates the embedded child to its pinned SHA, using the child's ownoriginremote to fetch missing commits.
Together, the two hooks make embedded gitlinks behave like properly-registered submodules for the common workflow operations — without ever recording the child's URL in the public parent repo.
Why this matters
The motivating use case is OSS repositories that want comprehensive test coverage running in CI but cannot afford to publish the full test suite. Tests are a high-fidelity behavioral specification of the code under test; modern AI tooling makes clean-room reimplementation from tests fast and effective. Hiding the tests is the most direct mitigation. See docs/use-case-private-tests.md for the full motivation, threat model, and licensing strategy.
The mechanism is general, though. The hooks don't know or care that the embedded repo is a test suite — they work for any gitlink. Other plausible uses: private vendor directories, license-restricted dependencies, encrypted asset trees, internal tooling sub-repos.
Install
npm install -g @cldmv/git-embeddedThat places a git-embedded executable in npm's global bin directory, which makes git's subcommand discovery surface it as git embedded ….
Usage
Run inside the parent repo (the one that holds the embedded gitlinks):
git embedded doctor # inspect environment; takes no action
git embedded install-hooks # install hooks into this repo's .git/hooks
git embedded uninstall-hooks # remove hooks installed by this CLIinstall-hooks adapts to whatever's already in place:
- Nothing configured — offers to install a small dispatcher script at
~/.config/git/hooks/_dispatch, link every standard hook name to it, and setgit config --global core.hooksPathto that directory. Then drops this package's hook scripts into the repo's.git/hooks/. The dispatcher chains to per-repo hooks, so every other repo on the machine keeps working as before. - Canonical dispatcher already present — installs only the per-repo hook scripts; the existing dispatcher activates them.
- Dispatcher present but missing required entries — offers to add the missing symlinks (with explicit confirmation), then installs per-repo hooks.
- Foreign hook manager detected (Husky, lefthook, simple-git-hooks, pre-commit) — refuses to install and prints instructions for hand-integrating, since clobbering those tools' generated files would be reverted on their next run.
- Non-conforming dispatcher / bare
.githooks/— refuses and prints integration options; the CLI never rewrites someone else's dispatcher.
Flags
--no-symlinks use hard links instead of symbolic links
(avoids the Windows UAC prompt; same-volume only)
--yes skip confirmation prompts
--dispatcher-dir <path> override the default ~/.config/git/hooksOther subcommands
git embedded install-template # install hooks into git config init.templateDir/hooks
# so new repos start with them already wired
git embedded print-hook-script <name>
# emit a packaged hook script to stdout
# (post-checkout / post-merge / post-rewrite /
# reference-transaction / update-embedded-repos / _dispatch)Quick start: embed a child repo
After the hooks are installed:
git clone <private-child-url> embedded-child
git add embedded-child
git commit -m "embed child"
# silence the harmless 'embedded git repository' warning if desired
git config advice.addEmbeddedRepo falseThe committed parent tree now contains a gitlink at embedded-child pinning the child's current HEAD. No .gitmodules is created; the child's URL never lands in the public repo.
Manual install (no CLI)
If you'd rather wire things up by hand:
mkdir -p .githooks
cp /path/to/git-embedded/hooks/reference-transaction .githooks/
cp /path/to/git-embedded/hooks/update-embedded-repos .githooks/
chmod +x .githooks/*
ln -sf update-embedded-repos .githooks/post-checkout
ln -sf update-embedded-repos .githooks/post-merge
ln -sf update-embedded-repos .githooks/post-rewrite
git config core.hooksPath .githooksDocumentation
docs/design.md— how the hooks work, coverage matrix, limitations, comparison to standard submodules.docs/use-case-private-tests.md— the OSS-tests-in-private-repo motivation, threat model, licensing strategy, why anonymous gitlinks rather than alternatives.
Compatibility
- Git 2.28 or newer for the
reference-transactionhook (released July 2020). Theupdate-embedded-reposhook works on older git but loses its guard. - Node 20.19+ for the CLI. The hooks themselves are shell scripts with no Node dependency at hook execution time.
- Linux / macOS / Windows. On Windows, symlink creation needs admin elevation (a one-shot UAC prompt the CLI requests). Pass
--no-symlinksto use hard links instead and skip the prompt.
Links
- npm: @cldmv/git-embedded
- GitHub: CLDMV/git-embedded
- Issues: GitHub Issues
- Releases: GitHub Releases
License
Apache-2.0 © Shinrai / CLDMV. See LICENSE.
