A fast, concurrent, Rust-based reimplementation of git-subrepo — a Git submodule alternative that keeps your history clean and your workflow simple.
Fully compatible with the original .gitrepo format. Drop-in replacement, but with extra features.
| git-submodule | git-subtree | git-subrepo | |
|---|---|---|---|
| Users don't need extra steps after clone | ✗ | ✓ | ✓ |
| Collaborators need the tool | ✗ | ✓ | only to push/pull |
| Clean main history | ✗ | ✗ | ✓ |
| Works across branches | ✗ | ✗ | ✓ |
| Move/rename subdir JustWorks™ | ✗ | ✓ | ✓ |
| Async parallel operations | ✗ | ✗ | ✓ |
| Interactive conflict resolution | ✗ | ✗ | ✓ |
- Users just
git cloneyour repo — subrepo content is already there, no extra steps. - Collaborators see a
.gitrepofile in any subrepo directory — it's self-documenting. - Owners get a clean, linear history. Upstream changes are squashed to one commit.
- Branching, renaming, moving subdirs all Just Work™.
- Parallel fetch/pull across all subrepos (
-a/--all). - Interactive prompts for conflicts, merge strategy, and rebase recovery.
cargo install --path .
# or
cargo build --release && cp target/release/git-subrepo ~/.local/bin/Requires: git ≥ 2.23, Rust ≥ 1.75.
# Add an external repo as a subrepo
git subrepo clone https://github.com/you/mylib lib/mylib
# Pull upstream changes
git subrepo pull lib/mylib
# Push your local changes back upstream
git subrepo push lib/mylib
# Status of all subrepos (fetches first, shows dirty info)
git subrepo statusgit subrepo clone <url> [<subdir>] [-b <branch>] [-M <merge|rebase>] [-m <msg>]
Clones an external repo into <subdir>. The entire upstream history is squashed into one commit. A .gitrepo file is created in the subdir to track the remote, branch, and pinned commit.
If <subdir> is omitted, it is inferred from the URL (like git clone).
git subrepo clone https://github.com/org/lib # → lib/
git subrepo clone https://github.com/org/lib ext/lib # → ext/lib/
git subrepo clone https://github.com/org/lib -b dev # track 'dev' branchgit subrepo pull [<subdir>] [-b <branch>] [-r <remote>] [-M <merge|rebase>] [-m <msg>]
Fetches the latest upstream commits and merges (or rebases) them with your local changes in the subdir. The result is squashed into a single commit on your mainline.
git subrepo pull lib/mylib # pull for one subrepo
git subrepo pull -a # pull all subrepos in parallel
git subrepo pull lib/mylib -M rebase # use rebase strategyOn merge conflict, the worktree is left open for manual resolution:
Resolve conflicts in the worktree, then finalise with:
cd '/path/to/.git/tmp/subrepos/lib/mylib'
# fix conflicts, stage them, then:
git merge --continue
git subrepo commit lib/mylib
git subrepo push [<subdir>] [-b <branch>] [-r <remote>] [-s] [-f]
Pushes the local subrepo branch back to the upstream remote. The branch must already include the upstream HEAD (i.e. you must pull before you can push, unless it's a first push after init).
git subrepo push lib/mylib
git subrepo push lib/mylib -b feature-branch # push to a different branch
git subrepo push -a # push all subreposgit subrepo fetch [<subdir>] [-b <branch>] [-r <remote>]
Fetches upstream commits into refs/subrepo/<subdir>/fetch. Does not modify your working tree.
git subrepo branch [<subdir>] [-f]
Scans mainline history and creates a branch subrepo/<subdir> containing only commits that touched <subdir>. Useful for manual merge/rebase workflows.
git subrepo commit <subdir> [<ref>] [-m <msg>]
After resolving a conflict by hand, use this to take the HEAD of subrepo/<subdir>, put its content into the subdir, and add a single commit to your mainline.
git subrepo status [<subdir>] [--no-fetch] [--no-dirty]
Shows the state of all subrepos. By default:
- Fetches upstream first (skip with
--no-fetch) - Shows unpushed local commits and new upstream commits (skip with
--no-dirty)
Example output:
Git subrepo 'lib/mylib':
Remote URL: https://github.com/org/mylib
Tracking Branch: main (a1b2c3d4) [remote HEAD: e5f6g7h8 (2 commits ahead)]
Pull Parent: deadbeef (0 commits ago)
Unpushed: 3 unpushed commits → git subrepo push lib/mylib
a1b2c3d add feature
...
Upstream: 2 new upstream commits → git subrepo pull lib/mylib
e5f6g7h fix bug
...
git subrepo sync
Finds groups of subrepos that track the same remote URL and branch but have different pinned commits. Shows a diff of the content between them and offers to sync them up interactively.
git subrepo fix
Scans all top-level subrepos for common issues:
| Issue | Auto-fixable? |
|---|---|
parent= not in HEAD history (caused by rebase) |
✓ |
Missing required .gitrepo fields |
✗ |
Stale refs/subrepo/*/ refs pointing to missing objects |
✓ |
Pinned commit= not reachable locally |
✗ |
Presents a multi-select prompt to choose which fixes to apply.
git subrepo init <subdir> [-r <remote>] [-b <branch>] [-M <merge|rebase>]
Promotes an existing subdirectory into a tracked subrepo. The content stays as-is; a .gitrepo file is added.
git subrepo clean [<subdir>] [-f]
Removes temporary branches, refs, and remotes created by fetch and branch. Use --force to also remove refs. Use -a to clean all subrepos.
git subrepo config <subdir> <key> [<value>]
git subrepo config lib/mylib method rebase # change merge strategy
git subrepo config lib/mylib parent <sha> # manually repair parent after rebase
git subrepo config lib/mylib remote <url> # update remote URL| Option | Short | Description |
|---|---|---|
--all |
-a |
Operate on all top-level subrepos |
--ALL |
-A |
Operate on all subrepos including nested |
--force |
-f |
Force the operation (meaning varies per command) |
--quiet |
-q |
Suppress output |
--verbose |
-v |
More output |
--no-edit |
-n |
Use auto-generated commit message (no editor) |
--verify |
-V |
Run git hooks on commit (default: --no-verify) |
--fetch |
-F |
Fetch before operation |
Every subrepo directory contains a .gitrepo file that tracks its state:
; DO NOT EDIT (unless you know what you are doing)
;
; This subdirectory is a git "subrepo", and this file is maintained by the
; git-subrepo command. See https://github.com/soraxas/git-subrepo-rs
;
[subrepo]
remote = https://github.com/org/mylib
branch = main
commit = a1b2c3d4e5f6... # upstream commit currently pinned
parent = deadbeef1234... # main-repo HEAD at time of last pull
method = merge
cmdver = 0.0.1commit— the upstream commit that is currently checked inparent— the main repo commit that was HEAD when this subrepo was last pulled/cloned. Used to find local-only commits when pushing.method—merge(default) orrebase
The .gitrepo file is committed to your repo but is not pushed to the upstream subrepo remote.
Subrepos can themselves contain subrepos. For example:
app/
├── lib/barlib/ ← subrepo of app
│ └── baz/bazlib/ ← subrepo of barlib (nested)
Important: there is no special handling at the app level for barlib's nested subrepos. From app's perspective, lib/barlib/baz/bazlib/ is just regular files inside lib/barlib/.
To upstream changes to a nested subrepo:
git subrepo push lib/barlib— push your changes to barlib's upstream- In your local clone of
barlib,git pullthengit subrepo push baz/bazlib
When pulling a nested subrepo directly (e.g. git subrepo pull lib/barlib/baz/bazlib), a warning is shown because the parent= field will reference the main repo's HEAD, not barlib's commit history. Use git subrepo fix if this causes issues after a rebase.
When a pull or push fails due to merge conflicts, the subrepo worktree is left open at:
.git/tmp/subrepos/<subdir>/
Resolve conflicts there, then:
cd .git/tmp/subrepos/lib/mylib
# edit conflicting files
git add .
git merge --continue # or: git rebase --continue
git subrepo commit lib/mylibIf you rebase your main branch, the parent= SHA stored in .gitrepo may no longer be in HEAD history. When this happens, git subrepo pull will:
- Detect the problem and show a diagnosis
- Search for the correct new parent (via
.gitrepolog history) - Show how many commits behind HEAD the candidate is, and how many subdir files differ
- Prompt: Auto-repair / Force / Abort
You can also run git subrepo fix to scan all subrepos for this and other issues at once.
| Feature | bash git-subrepo | git-subrepo-rs |
|---|---|---|
| Implementation | Bash | Rust |
Parallel --all operations |
Sequential | Async parallel (DFS-ordered) |
| Rebase parent recovery | Manual | Interactive auto-repair |
| Merge conflict UX | Error + instructions | Worktree left open + step-by-step |
sync command |
✗ | ✓ |
fix command |
✗ | ✓ |
.gitrepo format |
Same | Same (fully compatible) |
cmdver field |
0.4.x |
0.0.x |