https://github.com/f440/git-zone
create git worktrees for branches, PRs, and refs
https://github.com/f440/git-zone
cli git github tmux
Last synced: about 2 months ago
JSON representation
create git worktrees for branches, PRs, and refs
- Host: GitHub
- URL: https://github.com/f440/git-zone
- Owner: f440
- License: mit
- Created: 2025-09-04T11:42:00.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2026-03-28T13:46:17.000Z (3 months ago)
- Last Synced: 2026-03-28T16:29:16.674Z (3 months ago)
- Topics: cli, git, github, tmux
- Language: TypeScript
- Homepage:
- Size: 118 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# git-zone
`git-zone` is a command-line tool for creating, listing, and removing Git worktrees with a simpler workflow than raw `git worktree` commands.
It is designed for the common cases developers reach for every day:
- open a worktree for an existing branch
- create a new branch in its own worktree
- inspect all worktrees in a repository at a glance
- remove a worktree by path, branch name, or directory name
- open a worktree from a tag or commit
## Installation
Install from npm:
```bash
npm install -g @f440/git-zone
```
After installation, the command is available as:
```bash
git-zone
```
### Zsh completion
`git-zone` ships with a `zsh` completion file at `completions/_git-zone`.
For a global npm install, add the package completion directory to your `fpath` and initialize completions:
```bash
fpath+=("$(npm root -g)/@f440/git-zone/completions")
autoload -Uz compinit && compinit
```
When working from a local checkout of this repository, point `fpath` at the repository copy instead:
```bash
fpath+=("/path/to/git-zone/completions")
autoload -Uz compinit && compinit
```
After reloading your shell, `git-zone` completes subcommands, flags, Git refs for `add`, and existing worktree targets for `remove`.
For local development from this repository:
```bash
npm install
bun run build
npm install -g .
```
## Quick Start
Create a detached worktree from the current `HEAD` explicitly:
```bash
git-zone add HEAD --detach
```
Create a worktree for an existing branch:
```bash
git-zone add feature/login-fix
```
Create a new branch from `main` in a new worktree:
```bash
git-zone add main -b spike/new-idea
```
Show all worktrees for the current repository:
```bash
git-zone list
git-zone list --json
```
Remove a worktree:
```bash
git-zone remove pr-123
git-zone remove /full/path/to/worktree
git-zone remove feature/login-fix -b
```
## Commands
### `git-zone add `
Creates a new worktree for the current repository.
The target can be:
- a local branch
- a remote-tracking branch such as `origin/main`
- a tag
- a commit or revision
When the target is a local branch, the new worktree checks out that branch.
When the target is a plain branch name and only a matching remote-tracking branch exists, `git-zone` creates a local tracking branch by default.
When the target is an explicit remote branch, tag, commit, or `HEAD`, the new worktree is created in detached HEAD state unless you choose a branch explicitly.
Use `-b` to create a new branch, `-B` to reset or reuse a branch name, `--detach` or `-d` to force detached HEAD, and `-f` or `--force` to ask Git to allow a branch that is already checked out in another worktree:
```bash
git-zone add main -b spike/new-idea
git-zone add origin/main -B feature/from-remote
git-zone add HEAD --detach
git-zone add HEAD -d
git-zone add main -f
```
`-f` does not bypass existing local branch collisions for `-b`, default branch names guessed from remote-tracking branches, or zone path collisions.
Worktree placement is controlled by the Git config key `zone.workspace.pathTemplate`.
The template supports `${repo}`, `${workspace}`, and environment variables such as `${HOME}`.
Relative paths are resolved from the main worktree root.
`${workspace}` must be the final path segment.
Undefined environment variables are rejected as configuration errors.
```bash
git config zone.workspace.pathTemplate '../.zone/${repo}/${workspace}'
```
```bash
git config zone.workspace.pathTemplate '${HOME}/.local/share/git-zone/${workspace}'
```
If you want compatibility with Claude Code's `claude --worktree` layout, configure:
```bash
git config zone.workspace.pathTemplate '.claude/worktrees/${workspace}'
```
### Optional: `gh zone` alias for pull requests
If you use GitHub CLI, you can install an optional `gh zone` alias that fetches a pull request head ref and opens it with `git-zone`.
```sh
gh alias set --shell zone - <<'SH'
sel=$1
shift || { echo "usage: gh zone [git-zone args...]" >&2; exit 1; }
repo_of_remote() {
url=$(git remote get-url "$1" 2>/dev/null) || return 1
case "$url" in
https://github.com/*) repo=${url#https://github.com/} ;;
git@github.com:*) repo=${url#git@github.com:} ;;
ssh://git@github.com/*) repo=${url#ssh://git@github.com/} ;;
*) return 1 ;;
esac
printf '%s\n' "${repo%.git}"
}
base_remote() {
for r in $(git remote); do
[ "$(git config --get "remote.$r.gh-resolved" 2>/dev/null)" = "base" ] || continue
[ -z "$found" ] || return 1
found=$r
done
[ -n "$found" ] && printf '%s\n' "$found"
}
pick_remote_for_repo() {
want=$1
for r in $(git remote); do
[ "$(repo_of_remote "$r")" = "$want" ] || continue
if [ "$(git config --get "remote.$r.gh-resolved" 2>/dev/null)" = "base" ]; then
printf '%s\n' "$r"
return
fi
[ -z "$first" ] && first=$r || {
echo "gh zone: multiple remotes match $want; mark one as gh-resolved=base" >&2
return 1
}
done
[ -n "$first" ] && printf '%s\n' "$first" || {
echo "gh zone: no git remote matches $want" >&2
return 1
}
}
meta=$(gh pr view "$sel" --json number,headRefName --jq '[.number,.headRefName] | @tsv') || exit $?
pr=$(printf '%s\n' "$meta" | cut -f1)
branch=$(printf '%s\n' "$meta" | cut -f2-)
case "$sel" in
https://github.com/*/pull/*|http://github.com/*/pull/*)
path=${sel#https://github.com/}
path=${path#http://github.com/}
owner=${path%%/*}
rest=${path#*/}
repo=${rest%%/*}
remote=$(pick_remote_for_repo "$owner/$repo") || exit 1
;;
*)
remote=$(git config --get checkout.defaultRemote 2>/dev/null || true)
[ -n "$remote" ] || remote=$(base_remote 2>/dev/null || true)
[ -n "$remote" ] || remote=origin
;;
esac
git fetch "$remote" "refs/pull/$pr/head" || exit $?
for a in "$@"; do
case "$a" in
-b|-B|--detach|-d) exec git zone add FETCH_HEAD "$@" ;;
esac
done
exec git zone add FETCH_HEAD -b "$branch" "$@"
SH
```
Usage:
```sh
gh zone 12345
gh zone https://github.com/owner/repo/pull/12345
gh zone 12345 -B my/local-branch
```
### `git-zone list`
Shows every worktree registered for the current repository.
The output includes:
- the current worktree marker
- the checked out branch or `detached`
- the current short commit SHA
- upstream branch information
- ahead/behind status
- whether the worktree is clean or dirty
- the absolute path
Use `--json` for machine-readable output that is easier to consume from tools such as `fzf` and `jq`. The command returns a JSON array of worktree objects.
### `git-zone remove ...`
Removes one or more worktrees from the current repository.
Each target can be resolved by:
- full path
- local branch name
- worktree directory name
Use `-b` or `--delete-branch` to delete the corresponding local branch after removing the worktree.
Use `-f` or `--force` to force removal when Git would normally refuse it.
## Hooks
`git-zone` can run user-defined commands when a worktree is added or removed.
Configure hooks with Git config:
```bash
git config zone.hooks.postAdd './scripts/zone-post-add'
git config zone.hooks.preRemove './scripts/zone-pre-remove'
git config zone.hooks.postRemove './scripts/zone-post-remove'
```
Hook commands are executed by `/bin/sh -c` from the main worktree directory.
`postAdd` runs after a worktree has been created successfully.
`preRemove` runs before a worktree is removed. A non-zero exit aborts removal for that target.
`postRemove` runs after a worktree has been removed successfully.
Hooks receive the following environment variables:
- `ZONE_EVENT`
- `ZONE_MAIN_WORKTREE`
- `ZONE_WORKTREE_PATH`
- `ZONE_ZONE_NAME`
- `ZONE_BRANCH`
Example `postAdd` hook:
```bash
#!/bin/sh
set -eu
ln -sf "$ZONE_MAIN_WORKTREE/.env.local" "$ZONE_WORKTREE_PATH/.env.local"
```
Example `postRemove` hook:
```bash
#!/bin/sh
set -eu
tmux kill-session -t "zone-$ZONE_ZONE_NAME" || true
```
Example `preRemove` hook:
```bash
#!/bin/sh
set -eu
docker compose -p "zone-$ZONE_ZONE_NAME" down
```
## Examples
Create worktrees from different targets:
```bash
git-zone add main
git-zone add origin/main
git-zone add v1.2.3
git-zone add abc1234
```
Create a branch directly into a new worktree:
```bash
git-zone add origin/main -b feature/from-remote
```
Remove by branch name or directory name:
```bash
git-zone remove feature/login-fix
git-zone remove pr-123
```
## Help
```bash
git-zone --help
git-zone add --help
git-zone list --help
git-zone remove --help
```
## Release
`git-zone` is published to npm as `@f440/git-zone` through GitHub Actions trusted publishing.
Trusted Publisher should be configured in npm for:
- GitHub user: `f440`
- Repository: `git-zone`
- Workflow filename: `publish.yml`
To publish a new version:
```bash
npm version patch --no-git-tag-version
git commit -am "Prepare vX.Y.Z release"
git push origin main
npm run release:draft
```
Then open the draft GitHub Release, review the generated notes, and click Publish release.
The `publish.yml` workflow runs tests, builds the CLI, verifies the release tag matches `package.json`, and publishes to npm.