git-nested
An alternative to git-submodule and git-subtree for managing nested repositories.
Overview
git-nested lets you set up a monorepo from multiple repositories. Each git repository is placed as a copy in a subdirectory of your project. But it's not just a copy—you can pull upstream changes and push local modifications with simple, intuitive commands that keep your git history clean.
How it Works
git-nested copies source code from external repositories into your project, but it's more than a simple copy.
It uses git operations and a .gitnested metadata file to track the relationship with the upstream repository.
This allows you to run subsequent commands (pull, push, etc.) on the copied code as if you're working with the original repository directly.
Why git-nested?
- Self-contained: Your repository becomes a monorepo. No need for additional access rights to nested repositories.
- Simple: Intuitive commands that feel like native git
- Clean: Keeps your git history squeaky clean (single commit per operation)
- Just Works: Users get everything with a normal
git clone- no special setup needed - Flexible: Different branches can have different nested repos in different states
- Safe: Easy to try and reset without breaking anything
Nested-in-Nested Support
git-nested now supports nested repositories within nested repositories (recursive nesting) through .gitnested.levelN files:
- When you clone a nested repository that itself contains nested repositories, git-nested automatically creates
.gitnested.level2,.gitnested.level3, etc. files - These level files allow you to pull/push sub-nested repositories directly from the parent repository
- The nesting level is automatically detected based on existing level files in the repository
- Example:
parent/nested1/nested2can be pulled directly withgit nested pull nested1/nested2using its.gitnested.level2file
Limitations
- git-nested squashes the commits during a
git nested pullinto one commit. Otherwise the tool cannot determine at a later point of time, that the commit was pulled or not. This feature might be added in future, so that the commit message is adapted to indicate that a commit has been pulled via git-nested.
Quick Start
Requirements
- Git >= 2.23
- Python >= 3.10 (for Python-based installation)
Installation
Method 1: Via pip (Recommended)
pip install git+https://github.com/thorsten-klein/git-nested
Method 2: Standalone Executable (no Python needed)
Every release ships one archive per platform: a single file with git-nested, its dependencies and a Python interpreter inside. Only git itself has to be installed.
| Platform | Asset |
|---|---|
| Linux (x86_64) | git-nested-<version>-x64-linux.tar.xz |
| macOS (Apple silicon) | git-nested-<version>-arm64-macos.tar.xz |
| Windows (x86_64) | git-nested-<version>-x64-windows.zip |
# Find the Linux asset of the latest release
ASSET=$(curl -sSL https://api.github.com/repos/thorsten-klein/git-nested/releases/latest | grep -o 'https://[^"]*x64-linux\.tar\.xz')
# Download the asset and unpack it
curl -sSL "$ASSET" | tar -xJ
sudo install git-nested /usr/local/bin/ # any directory on PATH works
The file has to keep the name git-nested (git-nested.exe on Windows) —
that is how git nested ... finds it. Each archive also contains a versioned
copy of the binary (e.g. git-nested-1.2.3) that git-nested points to.
The Linux binary is built on x86_64 with glibc 2.28, so it runs on any x86_64 Linux from that version onwards (Ubuntu 20.04, Debian 10, RHEL/AlmaLinux 8 and newer). Not on musl distros such as Alpine.
The macOS binary is not signed, so macOS quarantines it after a download. Run
xattr -d com.apple.quarantine git-nested once to let it start.
Method 3: From Source with Shell Integration
Adds git-nested to PATH and enables tab completion:
git clone https://github.com/thorsten-klein/git-nested /path/to/git-nested
echo 'source /path/to/git-nested/.rc' >> ~/.bashrc
source ~/.bashrc
.rc works for bash and zsh. Fish has its own:
git clone https://github.com/thorsten-klein/git-nested /path/to/git-nested
echo 'source /path/to/git-nested/.fish.rc' >> ~/.config/fish/config.fish
Method 4: From Source (Manual)
git clone https://github.com/thorsten-klein/git-nested /path/to/git-nested
export PATH="/path/to/git-nested/bin:$PATH"
Note: Add the export command to your shell profile (~/.bashrc, ~/.zshrc) to make it permanent.
Shell Completion
Method 3 turns completion on for you. With any other installation method, add the matching line to your shell profile:
# ~/.bashrc
eval "$(git-nested completion bash)"
# ~/.zshrc (after compinit)
eval "$(git-nested completion zsh)"
# ~/.config/fish/config.fish
git-nested completion fish | source
The script is generated by git-nested itself from the same tables its argument parser is built from, so the offered commands, flags and values always match the version you have installed. It completes nested repository subdirectories and branch names too, by asking git.
Manual Page
git nested --help opens the manual page, so git has to be able to find it.
Methods 3 and 4 ship it in man/, and .rc puts that on MANPATH for you;
otherwise add it yourself:
export MANPATH="/path/to/git-nested/man:$MANPATH"
The standalone tarball contains the same page under man/man1/.
Per-command help needs nothing set up: git nested pull --help and the rest
print their options and a few examples directly.
Usage
# Clone a nested repository
git nested clone https://github.com/user/nested path/to/nested
# Pull updates from upstream
git nested pull path/to/nested
# Push local changes upstream
git nested push path/to/nested
# Check status of all nested repos
git nested status
Commands
Getting help
git nested --help # the manual page
git nested pull --help # one command: what it does, its options, examples
How much it tells you
These work with every command. They only affect what git-nested says about
its work, which goes to stderr -- a result you would pipe somewhere (status,
diff, config, completion) goes to stdout and is never gated.
git nested pull ext/lib -q # only warnings and errors
git nested pull ext/lib # the default: what changed
git nested pull ext/lib -v # plus each step as it is taken
git nested pull ext/lib -vv # plus every git command that is run
Colour is used when the output is a terminal. NO_COLOR=1 turns it off,
FORCE_COLOR=1 keeps it on when piping.
git nested clone
Clone an external repository into a subdirectory of your project.
git nested clone --help
Example:
git nested clone https://github.com/user/lib ext/lib -b main
git nested init
Turn an existing subdirectory into a nested repository.
git nested init --help
Example:
git nested init ext/mylib -r https://github.com/user/mylib
git nested pull
Update a nested repo with the latest upstream changes.
git nested pull --help
Example:
git nested pull ext/lib
git nested pull --all # Pull all nested repos
git nested push
Push local changes back to the upstream repository.
git nested push --help
Example:
git nested push ext/lib
git nested push --all # Push all nested repos
git nested status
Show the status of nested repositories.
git nested status --help
git nested fetch
Fetch remote content for a nested repository.
git nested fetch --help
git nested diff
Show the local diff of a nested repo compared to upstream. Fetches the latest upstream content first, so it always compares against the freshest available state. Requires that there are no uncommitted changes in the outer repository.
git nested diff --help
Example:
git nested diff ext/lib
git nested diff --all # Diff all nested repos
git nested branch
Create a branch with local nested commits for manual conflict resolution.
git nested branch --help
git nested commit
Add a nested branch to current history as a single commit.
git nested commit --help
git nested clean
Remove temporary branches, refs, and remotes created during nested operations.
git nested clean --help
git nested config
Read or update the .gitnested configuration of a nested repository.
git nested config <subdir> # print every field the file sets
git nested config <subdir> <key> # print one field
git nested config <subdir> <key> <value> # set one field
| Key | Meaning | Writable |
|---|---|---|
remote |
the upstream repository | yes |
branch |
the upstream branch | yes |
method |
how upstream history is joined: merge or rebase |
yes |
parent |
the commit the nested history hangs off | yes |
commit |
the upstream commit currently nested | no |
filter |
the paths of the upstream repository that are nested | no |
cmdver |
the git-nested version that last wrote this file | no |
The read-only keys are written by the nested operations themselves; setting one
by hand would describe a state the repository is not in. A write stages the
changed .gitnested file, it does not commit it.
Example:
git nested config ext/lib method rebase
git nested completion
Print the shell completion script. With no argument the shell is detected from the parent process.
git nested completion [bash|zsh|fish]
See Shell Completion for how to wire it up.
git nested version
Display version information.
git nested version
git nested --version
Why git-nested is Better
Comparison with git-submodule
| git-submodule | git-nested |
|---|---|
| Users must manually initialize submodules | Users get everything with git clone |
| Pulling doesn't update submodules automatically | No special commands needed |
| Breaks if remote repo disappears | Everything in your repo history |
| Removing/renaming requires many manual steps | Different branches automatically have correct nested state |
| Dependency on external repositories | Moving/renaming remotes doesn't break your repo |
Comparison with git-subtree
| git-subtree | git-nested |
|---|---|
| Must remember remote URL for every command | Remote/branch saved in .gitnested file |
| Verbose command syntax | Clean, intuitive commands |
| Collaborators aren't aware of subtrees | .gitnested file clearly indicates nested repos |
| Creates messy history with merge commits | Clean history with single commits |
| No state file to track remote/branch | Metadata file tracks all necessary information |
| Becomes slow with many commits | Optimized performance |
Key Benefits
For Users
- Get everything with one
git clone - No need to install git-nested
- No special commands or knowledge required
- Works with normal git workflow
For Collaborators
- Only install git-nested if you need to push/pull nested repos
- No access to upstream nested repositories required
- Simple, intuitive commands for contributing changes upstream
- Tab completion support
For Maintainers
- Create a self-contained repository
- Make atomic changes across multiple nested repositories
- No configuration required
Working with Nested Repos
The .gitnested File
Each nested repository has a .gitnested metadata file that tracks its relationship with upstream:
remote: https://github.com/user/repo # where the content comes from
branch: main # the branch tracked there
commit: abc123... # the last upstream commit taken
parent: def456... # your commit the two were last equal at
method: merge # how pulls join upstream: merge or rebase
cmdver: 1.0.0 # the git-nested version that wrote this
filter: [] # paths to limit the nested repo to
Read and write it with git nested config rather than
by hand.
This file:
- Is committed to your parent repository
- Is not pushed to the nested repository's upstream
- Tracks the upstream location and current state
- Enables seamless pull/push operations
Conflict Resolution
If a pull or push operation encounters merge conflicts, git-nested will guide you through manual resolution:
git nested fetch <subdir> # Fetch the latest changes
git nested branch <subdir> # Create a branch for manual resolution
# Resolve conflicts manually in your editor
git nested commit <subdir> # Commit the resolved changes
git nested clean <subdir> # Clean up temporary branches
Development
Everything about working on git-nested -- setting up, running the tests, the checks that have to pass, the commit message convention -- is in CONTRIBUTING.md. The short version:
uv sync # install everything
uv run poe all # the full gate: lint, types, security, complexity, tests
uv run poe test # just the tests
docs/diagrams.md shows the git commands each subcommand runs, which is the fastest way to understand what one actually does.
Authors
git-nested:
- Thorsten Klein - Python rewrite and enhancements
Original git-subrepo authors:
- Ingy döt Net - Original concept and implementation
- Magnus Carlsson - Contributor
- Austin Morgan - Contributor
License
MIT. See LICENSE.
Acknowledgments
This project is based on git-subrepo by Ingy döt Net. git-nested is a Python rewrite with some modified features and improvements.
Resources
- GitHub: https://github.com/thorsten-klein/git-nested
- Issues: https://github.com/thorsten-klein/git-nested/issues
- Documentation: https://github.com/thorsten-klein/git-nested#readme
Contributing
Pull requests are welcome. See CONTRIBUTING.md for how to set up, what has to pass before a change can be merged, and how commits should be worded.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file git_nested-1.1.1.tar.gz.
File metadata
- Download URL: git_nested-1.1.1.tar.gz
- Upload date:
- Size: 214.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3390eb15d7cf1e6de7b5b9764f97e1375f6da07d1ba1c611239eaa4afa5dd2e6
|
|
| MD5 |
958bab97201ab58affded87c3a8c8036
|
|
| BLAKE2b-256 |
763f6ed60f3638ff2429b6381e907ee6ddf5ef5ab27653056317c01b458969ee
|
Provenance
The following attestation bundles were made for git_nested-1.1.1.tar.gz:
Publisher:
publish.yml on thorsten-klein/git-nested
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
git_nested-1.1.1.tar.gz -
Subject digest:
3390eb15d7cf1e6de7b5b9764f97e1375f6da07d1ba1c611239eaa4afa5dd2e6 - Sigstore transparency entry: 2748096477
- Sigstore integration time:
-
Permalink:
thorsten-klein/git-nested@2f34845e953695900f9213058f41e85a5b51d6ea -
Branch / Tag:
refs/tags/1.1.1 - Owner: https://github.com/thorsten-klein
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2f34845e953695900f9213058f41e85a5b51d6ea -
Trigger Event:
push
-
Statement type:
File details
Details for the file git_nested-1.1.1-py3-none-any.whl.
File metadata
- Download URL: git_nested-1.1.1-py3-none-any.whl
- Upload date:
- Size: 69.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f7921bc46edd6835cfdf5ad87d5cd00eb8f70eeae370c862b4d7b5fb0deca107
|
|
| MD5 |
a717ee34cbcd33d273d80c54da87b81b
|
|
| BLAKE2b-256 |
5baf58ecff659155fea03444b224c736efc30c113f2b14d27c5e56f847704796
|
Provenance
The following attestation bundles were made for git_nested-1.1.1-py3-none-any.whl:
Publisher:
publish.yml on thorsten-klein/git-nested
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
git_nested-1.1.1-py3-none-any.whl -
Subject digest:
f7921bc46edd6835cfdf5ad87d5cd00eb8f70eeae370c862b4d7b5fb0deca107 - Sigstore transparency entry: 2748096600
- Sigstore integration time:
-
Permalink:
thorsten-klein/git-nested@2f34845e953695900f9213058f41e85a5b51d6ea -
Branch / Tag:
refs/tags/1.1.1 - Owner: https://github.com/thorsten-klein
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2f34845e953695900f9213058f41e85a5b51d6ea -
Trigger Event:
push
-
Statement type: