Git identity bound to a directory. The wrong account fails loudly instead of signing a commit quietly.
doctornotices stale hooks — the plugin may be a symlink and always current, while the installed hooks are copies. A drifted copy is now reported instead of passing silently
Earlier in 1.1.0: --sign, gitspace audit, and key-existence checks in
doctor. See CHANGELOG.md.
See CHANGELOG.md.
- Why
- Install
- Update
- Configuration
- Usage
- Commands
- Architecture
- Key Features
- Known Limits
- Contributing
- Security
- License
- Changelog
Working across several GitHub accounts and a private GitLab, the failure mode is not dramatic. A commit gets signed with the wrong address, nobody notices, and it surfaces weeks later in someone else's repository — or in a compliance review.
gitspace makes that hard to do by accident, on three independent layers:
- No global identity.
user.useConfigOnly=trueand no globaluser.email, so git refuses to guess an identity fromusername@hostname. - Hooks.
pre-commitandpre-pushcheck the e-mail, the SSH alias and the activeghaccount against the workspace the repository sits in. - URL rewriting.
url.<alias>.insteadOfforces every remote in a workspace onto that workspace's key.
The third layer is the one that matters. Hooks can be skipped with --no-verify
and overridden by tools that set their own core.hooksPath; the key separation
is enforced by the remote, so a push to a repository your workspace key cannot
reach fails no matter what happens locally.
git clone https://github.com/softspark/gitspace.git \
"${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/gitspace"npx @softspark/gitspace installThat links the plugin into $ZSH_CUSTOM/plugins/gitspace and offers to add
gitspace to plugins=(...) in ~/.zshrc (a backup is written first; pass
--yes to skip the prompt). Nothing runs behind your back — the package ships
ignore-scripts=true and has no postinstall hook, so installation is always an
explicit command.
For a permanent install:
npm install -g @softspark/gitspace && gitspace-installexec zsh
gitspace installgitspace install writes the hooks to ~/.config/git/hooks, seeds
~/.config/git/workspaces.conf and sets user.useConfigOnly. It never
overwrites an existing config.
Requirements: zsh with Oh My Zsh, git 2.13+ (for includeIf), Node.js 18+
for the npm installer only. gh is optional and used
only when a workspace binds a GitHub account.
npm update -g @softspark/gitspace && gitspace installRe-running gitspace install refreshes the hooks; your workspaces are untouched.
Register a workspace and bind an e-mail to it:
gitspace add ~/Workspace/Acme \
--email dev@acme.com \
--gh acme-dev \
--alias github-acme,gitlab-acme \
--name "Jane Doe"Only --email is required. The path may be anywhere — workspaces are matched by
longest path prefix, so a workspace nested inside another wins over its parent.
The SSH aliases must already exist in ~/.ssh/config:
Host github-acme
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_acme
IdentitiesOnly yes
gitspace add refuses to register a workspace that names an alias which does not
exist — a missing alias would silently fall back to the default key, which is the
whole failure this tool exists to prevent.
State lives in ~/.config/git/, deliberately outside the plugin directory:
deployment tooling deletes and re-clones plugin checkouts, and configuration must
survive that.
One workspace per line:
name|path|email|gh-account|ssh-alias[,ssh-alias...]|[sign]
gh-account may be empty for workspaces that do not use GitHub. The sixth field
carries the signing flag and may be absent — rows written before signing existed
keep working.
The chpwd hook reports the identity in force and switches the gh account when
needed. It reads the account back after switching and reports what actually
happened, so a failed switch is visible rather than assumed:
> Acme - committing as dev@acme.com
gh account: acme-dev
wclone git@github.com:acme/thing.git
wclone https://gitlab.example.com/group/sub/thing.git ~/Workspace/Acme/thingPaste a URL in any form — git@host:group/repo.git, https://host/group/repo,
or alias:group/repo. wclone derives the host, picks the workspace alias whose
HostName matches, switches the gh account, verifies access with
git ls-remote and only then clones. Nested GitLab groups work.
It refuses rather than guesses when the target lies outside every workspace, when the workspace has no alias for that host, or when the key cannot reach the repository.
Cloning through wclone rather than git clone matters: includeIf does not
apply while the repository is still being created, so a plain
git clone git@github.com:... would record a remote pointing at the default key.
gitspace doctorVerifies hooks, global settings, per-workspace configs, includeIf wiring, SSH
aliases and gh login state. Exits non-zero when anything is wrong, so it can
gate a provisioning script.
It also compares the installed hooks against the plugin source. The plugin
directory is frequently a symlink to a checkout and therefore always current,
while ~/.config/git/hooks holds copies written by gitspace install — so the
hooks can be two releases behind while everything else reads as up to date. Run
gitspace install after any change under lib/.
| Command | Description |
|---|---|
gitspace install |
hooks, config template, global git settings |
gitspace add <path> --email <addr> |
register a workspace; --gh, --alias, --name, --as, --sign optional |
gitspace list |
show registered workspaces |
gitspace doctor |
verify the whole setup, exit 1 on problems |
gitspace audit [--deep] [--limit N] |
scan repositories for wrong remotes and identity leakage |
gitspace remove <name> |
unregister a workspace (files left on disk) |
wclone <url> [dir] |
clone with the key the target directory implies |
gitspace-install |
npm-side installer: link the plugin, patch ~/.zshrc |
Completion is provided for subcommands, workspace names, SSH aliases and gh
accounts.
gitspace.plugin.zsh chpwd hook, wclone, the gitspace command
_gitspace zsh completion
lib/
guard.sh workspace resolution and identity check, sourced by hooks
pre-commit refuses a commit whose identity does not match
pre-push refuses a push over the wrong key or gh account
templates/
workspaces.conf seed copied on first install
bin/
gitspace-install.mjs npm installer (Node, no dependencies)
tests/run.sh suite, runs against a throwaway HOME
Installed state, outside the plugin tree:
~/.config/git/workspaces.conf workspace table
~/.config/git/hooks/ guard.sh, pre-commit, pre-push
~/.gitconfig-<workspace> identity, hooksPath, URL rewriting
~/.gitconfig includeIf per workspace
Path-prefix resolution. Workspaces are matched by repository path, not by directory name, so they can live anywhere and nest freely. Symlinks are resolved first — git reports physical paths, and a workspace behind a symlink would otherwise never match.
Hook chaining. The hooks run the repository's own pre-commit / pre-push
afterwards, so husky and friends keep working. Chaining goes through --git-dir
rather than --git-path, because the latter honours core.hooksPath and the
hook would exec itself forever.
No credentials touched. gitspace reads the name of the active gh account
and nothing else from that file. SSH keys are referenced by alias; no key file is
ever opened.
Truthful reporting. Every state that can fail to change is read back before being printed.
gitspace add ~/Workspace/Acme --email dev@acme.com --alias github-acme --signCommits and tags are then signed with that workspace's SSH key. The public key is
added to ~/.config/git/allowed_signers, so git log --show-signature verifies
locally and not only on the forge. Add the same key to the forge as a signing
key — GitHub and GitLab keep signing keys separate from authentication keys, and
uploading it once as an auth key is not enough.
Signing is off unless asked for. Turning it on by default would change behaviour for workspaces that already exist.
gitspace audit # cheap: last 50 commits per repository
gitspace audit --deep # whole historydoctor checks the configuration; audit checks the repositories. It reports
remotes that bypass the workspace's aliases and commits authored under another
of your workspace addresses. A colleague's address is not a finding — flagging
every third-party author turns the report into noise.
git commit --no-verifybypasses the hooks. Layer 3 still applies on push.- A repository that sets its own
core.hooksPath(husky does this onnpm install) overrides the hooks for that repository. Layer 3 still applies. includeIfdoes not apply duringgit clone, which is whywcloneexists.- Workspace matching is by path prefix; a workspace nested inside another must be registered separately to win over its parent.
See CONTRIBUTING.md.
See SECURITY.md. Report vulnerabilities to biuro@softspark.eu rather than in a public issue.
Apache-2.0. See NOTICE for attribution requirements.
See CHANGELOG.md.
Built by SoftSpark.