Stop juggling branches. Give each one its own directory.
git-work wraps git worktree into a practical CLI for branch-per-directory workflows. Instead of git checkout, each branch gets its own worktree directory, so switching context is just switching directories.
my-project/
|- .bare/ # bare git repo (shared by all worktrees)
|- main/ # worktree for main
|- feature-login/ # worktree for feature/login
`- fix-typo/ # worktree for fix-typo
brew tap beeemT/tap && brew install git-workgit-work prints machine-readable paths to stdout. The gw shell function wraps git-work and cds into returned paths automatically.
# bash / zsh
# add to ~/.bashrc or ~/.zshrc
eval "$(git-work activate bash)"
# fish
# add to ~/.config/fish/config.fish
git-work activate fish | sourceAfter this, use gw commands (examples below).
Clone into git-work layout:
gw clone git@github.com:you/repo.git
cd repo/mainOr convert an existing repo in place:
cd my-project
gw initSwitch/create worktrees:
gw co main # checkout existing worktree
gw co login # fuzzy match, e.g. feature-login
gw co -b feature/new-ui # create new worktreeJump back to previous worktree:
gw co -| Command | Alias | Description |
|---|---|---|
gw activate <shell> |
- | Print shell wrapper function (bash, zsh, fish) |
gw clone <url> [dir] |
gw cl |
Clone repo into .bare/ + initial worktree |
gw init |
- | Convert current repo to git-work layout |
gw checkout <branch> |
gw co |
Switch to existing worktree (supports fuzzy matching) |
gw checkout - |
gw co - |
Switch to previously visited worktree |
gw checkout -b <branch> |
gw co -b |
Create a worktree for branch |
gw rm [--force] [--yes] <branch> |
- | Remove worktree + delete branch (fuzzy + confirmation) |
gw sync [--dry-run] [--force] |
gw s |
Fetch + prune stale worktrees |
gw list |
gw ls |
List worktrees |
| Flag | Description |
|---|---|
--format=json |
Output results as JSON to stderr |
--help |
Show help |
When --format=json is passed, output is written to stderr as JSON:
gw --format=json listOutput format:
{
"path": "/path/to/worktree",
"data": { ... },
"messages": [{"level": "info", "text": "..."}]
}Errors:
{
"error": "error message",
"messages": [{"level": "error", "text": "error message"}]
}Example - list worktrees as JSON:
$ gw --format=json list
{"data":{"worktrees":[{"dir":"main","branch":"main","current":true}]},"messages":[...]}- Creates
<dir>/.bareas a bare repository - Writes
<dir>/.gitpointer file (gitdir: ./.bare) - Adds an initial worktree for the HEAD branch
- Creates missing parent directories and returns the physical worktree path, with symbolic links resolved
Examples:
gw cl git@github.com:you/repo.git
gw clone https://github.com/you/repo.git my-projectConverts a normal repo in place:
- Moves
.git/to.bare/ - Moves working files into
<root>/<current-branch>/ - Creates worktree metadata/pointers
- Restores stashed uncommitted changes
Before conversion, gw init rejects a target path that belongs to an existing worktree registration, even if its directory is missing.
It leaves that registration and its index intact.
If gw init is run again in an existing git-work root, it repairs workspace state (for example, rewriting .git pointer/config and recreating the HEAD worktree directory if missing).
gw co <branch>
gw co -
gw co -b <branch>Behavior:
gw co <branch>: switch to existing worktree by exact/fuzzy matchgw co -: switch to previous worktreegw co -b <branch>: create new worktree (existing local branch, remote branch, or new branch)- If no local worktree matches but a remote branch with exact name exists,
gw co <branch>auto-creates a tracking worktree
Fuzzy matching order:
- Exact directory match
- Substring match
- Jaro-Winkler similarity (threshold 0.85)
Ambiguous matches exit non-zero and list candidates.
gw rm [--force] [--yes] <branch>Behavior:
- Supports fuzzy matching of the branch/worktree argument
- Prompts for confirmation before deleting
--yesskips the confirmation prompt- Refuses to remove the HEAD branch unless
--forceis passed - Checks merge safety against the upstream branch, or
HEADif the upstream no longer exists - If run from inside the removed worktree, returns the HEAD worktree path so
gwcan move you safely
Examples:
gw rm feature-login
gw rm login
gw rm --yes feature-login
gw rm --force old-branchgw sync [--dry-run] [--force]Behavior:
- Runs
git fetch --all --prune - Removes local worktrees whose tracking remote branch no longer exists
- Never prunes HEAD branch
- Refuses to prune branches not merged into the HEAD branch unless
--forceis set, including during--dry-run --dry-runpreviews removals--forceforce-removes stale worktrees/branches
Shows a formatted worktree table:
- directory name
- branch name
*marker for current worktree
When creating a new worktree (gw co -b ... or remote auto-create), git-work runs a post-create hook with mise integration.
- If
miseis not installed, hook is skipped - By default, git-work attempts:
- trust propagation (
mise trust) when source worktree is trusted - task execution:
mise run worktree-setup
- trust propagation (
Configuration (stored in repo git config under .bare):
# disable trust propagation
git -C .bare config git-work.hooks.mise.trust false
# choose custom setup task
git -C .bare config git-work.hooks.mise.task bootstrap
# disable task execution
git -C .bare config git-work.hooks.mise.task ""If the configured task does not exist, git-work logs a warning and continues. If the task fails, worktree creation is rolled back.
- stdout is reserved for machine-readable output (paths)
- human-readable messages/warnings/errors go to stderr
- no state files: behavior is derived from git + filesystem
- branch-to-directory mapping replaces
/with-
mise install
mix deps.get && mix compile
mix test
mix escript.build
./git_work --helpPush a version tag, such as v1.2.3, to start the release workflow automatically.
You can also run the workflow manually from the default branch with an existing version tag.
The workflow builds the tag commit, not a branch with the same name.
It verifies that the tag commit belongs to the default branch before the build.
Configure required reviewers for the release environment in repository settings.
The build job has read-only repository access. The separate publish job requires environment approval when reviewers are configured.
MIT