# git-shadow
[](https://crates.io/crates/git-shadow)
[](https://github.com/andre-a-alves/git-shadow/actions/workflows/ci.yml)
[](https://github.com/andre-a-alves/git-shadow#license)
Run git commands against shadow repositories that live inside your working directory.
A shadow repo is a separate git repository checked out inside another project — useful for keeping vendored code, generated outputs, or loosely related projects alongside your main repo without making them submodules.
## Installation
```
cargo install git-shadow
```
This installs the `git-shadow` binary. Git automatically treats any `git-<name>` binary on your `PATH` as a subcommand, so it becomes available as `git shadow`.
## Configuration
Configuration is stored outside the project, under your OS config directory:
- Linux: `$XDG_CONFIG_HOME/git-shadow`, or `~/.config/git-shadow` when `XDG_CONFIG_HOME` is not set
- macOS: `~/Library/Application Support/git-shadow`
- Windows: `%APPDATA%\git-shadow`
`git-shadow` identifies the current project from the nearest Git repository's `remote.origin.url`.
The remote URL is normalized into a repo-shaped config path. For example, a parent repo with this origin:
```
git@github.com:andre-a-alves/git-shadow.git
```
uses this config file on Linux when `XDG_CONFIG_HOME` is not set:
```
~/.config/git-shadow/github.com/andre-a-alves/git-shadow/config.toml
```
The config file must use version `1`:
```toml
version = 1
[shadows.foobar]
repo = "git@github.com:example/foobar.git"
mapping = ".vendor/foobar/"
```
- **`version`** — the config file version; currently only `1` is supported
- **`shadows.foobar`** — the nickname you use on the command line
- **`repo`** — the remote URL for the shadow repository
- **`mapping`** — path to the directory containing the shadow git repository, relative to the parent repo root
You can define as many `[shadows.<nickname>]` entries as you need. You don't have to write this file by hand — `git shadow clone` creates and extends it for you.
## Usage
```
git shadow <shadow-name> <git-command> [args...]
```
`git-shadow` finds the nearest Git repository, loads its OS config file, then runs the given git command inside the shadow's `mapping` directory. You can run it from anywhere inside your project.
```
# List branches of the shadow repo
git shadow foobar branch
# Pull latest changes
git shadow foobar pull
# View recent commits
git shadow foobar log --oneline
# Check status
git shadow foobar status
```
Any git command and its arguments are passed through as-is.
### Listing shadows
```
git shadow list [--global]
```
`git shadow list` shows the shadows configured for the current repo — one per line with the nickname, remote URL, and mapping. With `--global`, it instead walks the whole config directory and lists every configured shadow, grouped under a `<host>/<owner>/<repo>:` header identifying the parent repo.
### Syncing shadows
```
git shadow sync [<shadow-name>]
```
`git shadow sync` clones every configured shadow that is not already present into its `mapping` directory (a missing or empty directory counts as not present). Pass a shadow name to sync just that one.
Shadows that are already cloned are left untouched, but their `remote.origin.url` is checked against the configured `repo` — a mismatch prints a warning. If a mapping directory exists and is non-empty but isn't a git repository, it is skipped with a warning. Any warning or failed clone makes the command exit non-zero.
`sync` also makes sure every present shadow is listed in the parent repo's exclude file (see below), so shadow directories never show up in the parent's `git status`.
### Cloning a new shadow
```
git shadow clone <repo-url> [<directory>] [--name <nickname>]
```
`git shadow clone` clones a repo into the parent repository and registers it as a shadow — creating the config file if it doesn't exist yet. The nickname and directory both default to the repository name from the URL:
```
# Clones into ./foobar, registers as [shadows.foobar]
git shadow clone git@github.com:example/foobar.git
# Clones into .vendor/fb, registers as [shadows.fb]
git shadow clone git@github.com:example/foobar.git .vendor/fb --name fb
```
The directory is resolved relative to where you run the command (like `git clone`), and the stored `mapping` is computed relative to the parent repo root. If the nickname or mapping is already taken, or the target directory is non-empty, the command refuses and changes nothing. Existing config file content is preserved — the new entry is appended.
`clone` also adds the new directory to the parent repo's exclude file.
### Removing a shadow
```
git shadow remove <shadow-name> [--delete]
git shadow rm <shadow-name> [--delete] # alias
```
`git shadow remove` deletes the shadow's entry from the config file and its line from the exclude file's managed block. The cloned directory is left on disk by default — pass `--delete` to remove it as well (careful: this discards any unpushed work in the shadow).
Because `list`, `sync`, `clone`, `remove`, `rm`, and `help` are subcommands, they are reserved and cannot be used as shadow nicknames.
### The exclude file
`clone` and `sync` keep shadow directories out of the parent repo's `git status` by adding them to `.git/info/exclude` (the local, uncommitted counterpart to `.gitignore`). Entries live in a managed block, and anything outside it is never touched:
```
# >>> git-shadow (managed) >>>
/foobar/
/.vendor/dep2/
# <<< git-shadow (managed) <<<
```
`clone` and `sync` only add entries; `git shadow remove` deletes a shadow's entry. If you edit the config by hand instead, clean up the block yourself.
## License
This project is licensed under either the [MIT License](LICENSE-MIT) or the [Apache License, Version 2.0](LICENSE-APACHE), at your option.