git-shadow 0.0.3

Standalone shadow copies of git repos (or parts of them) in a working directory.
git-shadow-0.0.3 is not a library.

git-shadow

Crates.io CI License: MIT OR Apache-2.0

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:

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 or the Apache License, Version 2.0, at your option.