git-side is a Git subcommand that allows you to version files and directories that should not live in the main repo, using a separate, per-project bare Git repo, completely invisible to the original repo.
It is designed for local-only state, tooling artifacts, and contextual files that must be versioned but must not pollute the primary project history.
In real projects there are files that:
- are intentionally excluded via
.gitignoreor global ignore rules - are local, contextual, or environment-specific
- contain tooling state (AI prompts, build metadata, local configs)
- must be versioned, but not in the main repo
Examples:
BACKLOG.md,TODO.md,NOTES.md— personal project notes.vscode/launch.json— local debug configs not shared with the teamdocker-compose.override.yml— local dev environment overridesscratch/— throwaway experiments and prototypes.claude/,.cursor/rules/— when the team prefers to keep them out
Git can technically track these files, but you should not do it in the main repo.
git-side solves this by introducing a side repo:
- per project
- Git-native
- invisible to the main repo
- using a bare repo, vcsh-style
git-side is NOT for secrets. Files like
.env, API keys, tokens, and credentials should never be committed to any repo — not the main one, not the side one. Use a secrets manager. No exceptions.
For each Git project, git-side creates (lazily) a dedicated bare repo stored outside the project directory:
~/.local/share/git-side/<initial-commit-sha>/The initial commit SHA (git rev-list --max-parents=0 HEAD) is used as the project identifier. It is immutable, exists in every repo, and is stable across clones regardless of filesystem location or remote URL.
You can set a custom base path per project:
git side init --path /mnt/external/side-repos/This stores the mapping in the config directory — no changes to the main repo.
Config and data paths are platform-specific:
- Linux:
~/.config/git-side/(config),~/.local/share/git-side/(repos) - macOS:
~/Library/Application Support/git-side/(both)
The project directory itself is used as the work-tree.
The main repo is never modified:
- no submodules
- no config changes
- no hooks (unless you opt-in with
git side hook install) - no metadata files
git side hook install adds a local hook to automate git side auto. Since .git/hooks/ is not tracked by Git, this remains invisible to the repo and other clones.
Supported hooks include:
post-commit— sync after every commit (default)pre-push— sync before pushingpost-merge— sync after pulling/merging
In git-side, directories are treated as semantic containers.
If you track a directory, git-side assumes responsibility for everything inside it:
- new files
- deleted files
- renamed files
- nested directories
This behavior is implemented by the tool itself, not delegated to Git defaults.
git-side always stages files using:
git add -fThis means:
- .gitignore
- global ignore (core.excludesFile)
- system ignore rules
are intentionally ignored for side-tracked paths.
This is a feature, not a workaround.
cargo build --release
install -m 755 target/release/git-side ~/.local/bin/git-sideMake sure ~/.local/bin is in your $PATH.
git-side is a Git subcommand. Once installed, it is invoked as:
git side <command> [<args>]git side add <path> # track file or directory (forced, bypasses gitignore)
git side rm <path> # untrack path from side repo
git side status # show side repo status
git side commit -m "msg" # commit in side repo
git side log # show side repo history
git side auto # sync side-tracked paths and commit using last main repo message
git side init --path <dir> # set custom base path for this project's side repo
git side hook install [--on <hook>] # install git hook to run auto (default: post-commit)
git side hook uninstall [--on <hook>] # remove git hook
git side info # show info about git-side and current project
git side remote [<args>] # manage remotes (pass-through to git remote)
git side push # push to origin/main (force, local wins)
git side pull # pull from origin/main (force, remote wins)If you know Git, you already know git-side.
# track some files
git side add BACKLOG.md
git side add scratch/
# commit to side repo
git side commit -m "Added personal backlog"
# or piggyback on the main repo's last commit message
git commit -m "Refactor parsing logic"
git side autoUntracked files from the main project are hidden by default.
# add a remote to your side repo
git side remote add origin git@github.com:user/project-side.git
# push (force, local always wins)
git side push
# pull (force, remote always wins)
git side pull
# list remotes
git side remotePush and pull are intentionally simple and conflict-free:
- push uses
--force— your local side repo always wins - pull uses
fetch+reset --hard— the remote always wins
This matches the "local-only state" philosophy. If you need merge semantics, you're probably tracking the wrong files.
- Git-native behavior
- No impact on the main repo
- Per-project isolation
- Deterministic and reproducible
- No dotfiles, no metadata in the project
- Works with existing Git workflows
git-side intentionally does not:
- handle merge conflicts (push/pull are force operations)
- encrypt or secure files
- replace secrets managers
- act as a dotfiles manager
- integrate with the main repo history
It is a local, explicit, opt-in tool. Remote sync is supported but kept simple — no conflict resolution.
The core model is inspired by:
- bare repos
- vcsh — manages multiple Git repos with
$HOMEas work-tree
But applied per project, not per $HOME.
Created by MiPnamic for Solexma.
MIT — see LICENSE.