Context switching without the mess.
gji is a Git worktree CLI for people who jump between tasks all day. It gives each branch or PR its own directory, so you stop doing stash, pop, reinstall cycles, and fragile branch juggling.
Standard branch switching gets annoying when you are:
- fixing one bug while reviewing another branch
- hopping between feature work and PR checks
- using multiple terminals, editors, or AI agents at the same time
gji keeps those contexts isolated in separate worktrees with deterministic paths.
Current source install:
git clone https://github.com/sjquant/gji.git
cd gji
pnpm install
pnpm build
npm install -g .Confirm the CLI is available:
gji --version
gji --helpInside a Git repository:
gji new feature/login-form
gji statusThat creates a linked worktree at a deterministic path:
../worktrees/<repo>/<branch>
gji new, gji go, gji root, and gji remove/gji rm can only change your current directory when shell integration is installed. Without shell integration, the raw CLI prints the target path so it stays script-friendly.
For zsh:
echo 'eval "$(gji init zsh)"' >> ~/.zshrc
source ~/.zshrcAfter that:
gji new feature/login-form
gji go feature/login-form
gji root
gji rm feature/login-formchanges your shell directory directly.
If you reinstall or upgrade gji, refresh the shell function:
eval "$(gji init zsh)"For scripts or explicit piping:
gji new feature/login-form
gji go --print feature/login-form
gji root --printgji new and gji remove print their destination paths in raw CLI mode, but in a shell-integrated session they change directory directly.
Start a task:
gji new feature/refactor-authStart a detached scratch worktree:
gji new --detachedCheck what is active:
gji status
gji lsPull a PR into its own worktree:
gji pr 123
gji pr #123
gji pr https://github.com/owner/repo/pull/123Sync the current worktree with the latest default branch:
gji syncSync every worktree in the repository:
gji sync --allClean up stale linked worktrees interactively:
gji cleanFinish a single worktree explicitly:
gji remove feature/refactor-auth
# or
gji rm feature/refactor-authAfter removal, the shell-integrated command returns you to the repository root.
gji --versionprints the installed CLI versiongji init [shell]prints shell integration forzsh,bash, orfishgji new [branch] [--detached]creates a branch and linked worktree; with shell integration it moves into the new worktree, and--detachedcreates a detached worktree insteadgji pr <ref>accepts123,#123, or a full PR/MR URL, extracts the numeric ID, then fetchesorigin/pull/<number>/headand creates a linkedpr/<number>worktreegji go [branch] [--print]jumps to an existing worktree when shell integration is installed, or prints the matching worktree path otherwisegji root [--print]jumps to the main repository root when shell integration is installed, or prints it otherwisegji status [--json]prints repository metadata, worktree health, and upstream divergencegji sync [--all]fetches from the configured remote and rebases or fast-forwards worktrees onto the configured default branchgji ls [--json]lists active worktrees in a table or JSONgji cleaninteractively prunes one or more linked worktrees, including detached entries, while excluding the current worktreegji remove [branch]andgji rm [branch]remove a linked worktree and delete its branch when present; with shell integration they return to the repository rootgji configreads or updates global defaults
gji is usable without setup, but it supports defaults through:
- global config at
~/.config/gji/config.json - repo-local config at
.gji.json
Repo-local values override global defaults.
Supported keys:
branchPrefixsyncRemotesyncDefaultBranchhooks
Example:
{
"branchPrefix": "feature/",
"syncRemote": "upstream",
"syncDefaultBranch": "main"
}Behavior:
- if
syncRemoteis unset,gji syncdefaults toorigin - if
syncDefaultBranchis unset,gji syncresolves the remote default branch fromHEAD
hooks runs shell commands at key points in the worktree lifecycle. Configure it in .gji.json or ~/.config/gji/config.json:
{
"hooks": {
"afterCreate": "pnpm install",
"afterEnter": "echo switched to {{branch}}",
"beforeRemove": "pnpm run cleanup"
}
}Hook keys:
afterCreate— runs after a new worktree is created, whether viagji neworgji prafterEnter— runs after switching to a worktree viagji gobeforeRemove— runs before a worktree is removed viagji remove
Each hook receives context in two ways:
Template variables (substituted into the command string):
| Variable | Value |
|---|---|
{{branch}} |
branch name, or empty string for detached worktrees |
{{path}} |
absolute path to the worktree |
{{repo}} |
repository directory name |
Environment variables (available to the hook process):
| Variable | Value |
|---|---|
GJI_BRANCH |
branch name, or empty string for detached worktrees |
GJI_PATH |
absolute path to the worktree |
GJI_REPO |
repository directory name |
Hooks run inside the worktree directory. A non-zero exit emits a warning but does not abort the command.
Global and project-level hooks are merged per key — project values override global values for the same key, while keys only present in the global config still apply:
// ~/.config/gji/config.json
{ "hooks": { "afterCreate": "nvm use", "afterEnter": "echo hi" } }
// .gji.json
{ "hooks": { "afterCreate": "pnpm install" } }
// effective hooks
{ "afterCreate": "pnpm install", "afterEnter": "echo hi" }gji ls --json returns branch/path entries:
gji ls --jsongji status --json returns a top-level object with:
repoRootcurrentRootworktrees
Each worktree entry contains:
branch: branch name ornullfor detached worktreescurrentpathstatus:cleanordirtyupstream: one of{ "kind": "detached" }{ "kind": "no-upstream" }{ "kind": "tracked", "ahead": number, "behind": number }
gjiworks from either the main repository root or any linked worktree- the current worktree is never offered as a
gji cleanremoval candidate gji praccepts GitHub, GitLab, and Bitbucket-style PR/MR links, but still fetches fromoriginusing GitHub-stylerefs/pull/<number>/head
MIT