Skip to content

Repository files navigation

git-wt Coverage Code to Test Ratio Test Execution Time

A Git subcommand that makes git worktree simple.

Usage

$ git wt                            # List all worktrees
$ git wt --json                     # List all worktrees in JSON format
$ git wt <branch|worktree|path>     # Switch to worktree (create worktree/branch if needed)
$ git wt -b <branch> <worktree>     # Create worktree with a different branch name
$ git wt -d <branch|worktree|path>  # Delete worktree and branch (safe)
$ git wt -D <branch|worktree|path>  # Force delete worktree and branch
$ git wt -m [<old>] <new>           # Rename worktree directory and branch (safe)
$ git wt -M [<old>] <new>           # Force rename (overwrite existing branch, allow moving dirty/locked worktrees)

The target can be specified as:

  • branch: a git branch name — eg. git wt feature-branch
  • worktree: a directory name relative to wt.basedir (default .wt) — eg. git wt some-worktree-folder-name
  • path: a filesystem path (absolute or relative to the current working directory) to an existing worktree — eg. git wt ../sibling, git wt /absolute/path

Note

git-wt has no subcommands. The first non-flag argument is always a target name, never a command, so git wt list creates a worktree named list instead of listing anything. Run git wt with no arguments to list worktrees.

When deleting, the same target types apply: git wt -d feature-branch, git wt -d ., git wt -d ../sibling

-d is the safe form and stops short when something would be lost:

  • If the worktree has modified or untracked files, nothing is deleted. A directory shared through wt.symlink counts as untracked unless the ignore pattern matches the link itself.
  • If the branch is not fully merged, the worktree is removed but the branch is kept. git-wt reports this and still exits 0, so read the message rather than only the exit code.

-D skips both checks and removes the worktree together with its branch.

Use -m (-M to force) to rename a worktree's directory and branch in a single operation. With one argument, the current worktree is renamed; with two, an explicit worktree is renamed:

$ git wt -m new-name                 # rename the current worktree to "new-name"
$ git wt -m old-name new-name        # rename worktree "old-name" to "new-name"

When invoked through the shell integration on the current worktree, the shell wrapper cds to the new path (the same way -d returns to the main repository root after deleting the current worktree).

Use -b/--branch to give the branch a different name from the worktree directory:

$ git wt -b user/my-feature my-feature

You can later switch to the worktree by either branch name or directory name:

$ git wt user/my-feature  # switch by branch name
$ git wt my-feature       # switch by directory name

Note

The default branch (e.g., main, master) is protected from accidental deletion or rename. Pass --allow-delete-default to override the protection.

  • If the default branch has a worktree, -d removes the worktree but keeps the branch by default; -m/-M refuses to rename it by default.
  • If the default branch has no worktree, deletion is refused by default.
  • In every case, --allow-delete-default lifts the protection and lets the destructive operation proceed against the default branch.

Install

go install:

$ go install github.com/k1LoW/git-wt@latest

homebrew tap:

$ brew install k1LoW/tap/git-wt

manually:

Download binary from releases page

Shell Integration

Add the following to your shell config to enable worktree switching and completion:

zsh (~/.zshrc):

eval "$(git wt --init zsh)"

bash (~/.bashrc): (experimental)

eval "$(git wt --init bash)"

fish (~/.config/fish/config.fish): (experimental)

git wt --init fish | source

powershell ($PROFILE): (experimental)

Invoke-Expression (git wt --init powershell | Out-String)

Important

The shell integration creates a git() wrapper function to enable automatic directory switching with git wt <branch>. This wrapper intercepts only git wt <branch> commands and passes all other git commands through unchanged. If you have other tools or customizations that also wrap the git command, there may be conflicts.

The cd is performed by that wrapper function, so it only exists in a shell that sourced the script above. The binary itself always prints the resulting worktree path as the last line of stdout, which is what the wrapper reads. Git's own progress output and hook output both go to stderr, so scripts, editors, and other tools can rely on the last line:

$ WT=$(git wt --nocd feature-branch | tail -1)
$ git -C "$WT" status

If you want only completion without the git() wrapper (no automatic directory switching), use the --nocd option:

eval "$(git wt --init zsh --nocd)"

You can also use --nocd with git wt <branch> to create/switch to a worktree without changing the current directory:

$ git wt --nocd feature-branch
/path/to/worktree/feature-branch  # prints path but stays in current directory

Configuration

Configuration is done via git config. All config options can be overridden with flags for a single invocation.

wt.basedir / --basedir

Worktree base directory.

$ git config wt.basedir "../{gitroot}-worktrees"
# or override for a single invocation
$ git wt --basedir="/tmp/worktrees" feature-branch

Supported template variables:

  • {gitroot}: repository root directory name

Default: .wt

Note

When placing worktrees inside the repository (e.g., .wt), be aware of these limitations:

  • Configuration files loaded multiple times: Tools that traverse parent directories (e.g., Claude Code reading CLAUDE.md) may load configuration files from both the worktree and the main repository.
  • Linters/formatters scanning worktree directories: Some tools may scan worktree directories, causing slower performance or unexpected results.

Placing worktrees inside the .git directory (e.g., .git/wt) resolves these issues, as most tools ignore .git.

wt.copyignored / --copyignored

Copy files ignored by .gitignore (e.g., .env) to new worktrees.

$ git config wt.copyignored true
# or override for a single invocation
$ git wt --copyignored feature-branch
$ git wt --copyignored=false feature-branch  # explicitly disable

Default: false

wt.copyuntracked / --copyuntracked

Copy untracked files (not yet added to git) to new worktrees.

$ git config wt.copyuntracked true
# or override for a single invocation
$ git wt --copyuntracked feature-branch
$ git wt --copyuntracked=false feature-branch  # explicitly disable

Default: false

wt.copymodified / --copymodified

Copy modified files (tracked but with uncommitted changes) to new worktrees.

$ git config wt.copymodified true
# or override for a single invocation
$ git wt --copymodified feature-branch
$ git wt --copymodified=false feature-branch  # explicitly disable

Default: false

wt.copy / --copy

Always copy files matching patterns, even if they are gitignored. Uses .gitignore syntax.

$ git config --add wt.copy "*.code-workspace"
$ git config --add wt.copy ".vscode/"
# or override for a single invocation (multiple patterns supported)
$ git wt --copy "*.code-workspace" --copy ".vscode/" feature-branch

This is useful when you want to copy specific IDE files (like VS Code workspace files) without enabling wt.copyignored for all gitignored files.

Patterns are matched against gitignored and untracked files.

Note

The worktree base directory (wt.basedir) is always excluded from file copying, regardless of copy options. This prevents circular copying when basedir is inside the repository (e.g., .worktrees/).

wt.nocopy / --nocopy

Exclude files matching patterns from copying. Uses .gitignore syntax.

$ git config --add wt.nocopy "*.log"
$ git config --add wt.nocopy "vendor/"
# or override for a single invocation (multiple patterns supported)
$ git wt --copyignored --nocopy "*.log" --nocopy "vendor/" feature-branch

Supported patterns (same as .gitignore):

  • *.log: wildcard matching
  • vendor/: directory matching
  • **/temp: match in any directory
  • /config.local: relative to git root

Note

If the same file matches both wt.copy and wt.nocopy, wt.nocopy takes precedence.

wt.symlink / --symlink

Symlink matching top-level directories to the source instead of copying them. Uses .gitignore syntax.

Because the directory is shared rather than duplicated, worktree creation stays fast no matter how large it is. The flip side is that every worktree sees the same contents, so an install in one worktree changes all of them.

$ git config --add wt.copy "node_modules/"
$ git config --add wt.symlink "node_modules/"
# or override for a single invocation (multiple patterns supported)
$ git wt --copy "node_modules/" --symlink "node_modules/" feature-branch

Important

wt.symlink only redirects directories that are already going to be copied, so on its own it does nothing. Pair it with wt.copy (as above), or with wt.copyignored / wt.copyuntracked if the directory is already covered by those.

Note

A symlink is not a directory, so a trailing-slash .gitignore pattern such as node_modules/ does not match the link and git reports it as untracked. That also makes git wt -d refuse to remove the worktree. Drop the trailing slash in .gitignore, or add the bare name to .git/info/exclude.

wt.hook / --hook

Commands to run after creating a new worktree. Hooks run in the new worktree directory.

$ git config --add wt.hook "npm install"
$ git config --add wt.hook "go generate ./..."
# or override for a single invocation (multiple hooks supported)
$ git wt --hook "npm install" feature-branch

Note

  • Hooks only run when creating a new worktree, not when switching to an existing one.
  • If a hook fails, execution stops immediately and git wt exits with an error (shell integration will not cd to the worktree).

wt.deletehook / --deletehook

Commands to run before deleting a worktree. Hooks run in the worktree directory before it is removed, so you can perform cleanup (e.g., push branches).

$ git config --add wt.deletehook "git push origin --delete $(git branch --show-current)"
# or override for a single invocation (multiple hooks supported)
$ git wt -D --deletehook "npm run cleanup" feature-branch

Note

  • Hooks only run when deleting a worktree, not when deleting a branch without a worktree.
  • If a hook fails, execution stops immediately and the worktree is preserved.

wt.remover / --remover

Custom command to remove the worktree directory instead of git worktree remove. The worktree path is passed as an argument to the command. After the command completes, git worktree prune is run automatically.

$ git config wt.remover "trash-put"
# or override for a single invocation
$ git wt -D --remover "rm -rf" feature-branch

Default: (not set, uses git worktree remove)

Note

  • If the remover command fails, the worktree is preserved.

wt.nocd / --nocd

Do not change directory to the worktree. Only print the worktree path.

Supported values for wt.nocd config:

  • true or all: Never cd to worktree (both new and existing).
  • create: Only prevent cd when creating new worktrees (allow cd to existing worktrees).
  • false (default): Always cd to worktree.
# Prevent cd only for new worktrees (allow cd to existing)
$ git config wt.nocd create

# Never cd to any worktree
$ git config wt.nocd true

# Use --nocd flag for a single invocation (always prevents cd)
$ git wt --nocd feature-branch

Note

  • The --nocd flag always prevents cd regardless of config value.
  • Using --nocd with --init disables the git() wrapper entirely (only shell completion is output). The wt.nocd config does not affect --init output.

wt.relative / --relative

Append the current subdirectory path to the worktree output path. When running from a subdirectory, the output path will include the subdirectory relative to the repository root (like git diff --relative).

# Enable relative path resolution
$ git config wt.relative true

# Example: running from repo/some/path/
$ git wt feature-branch
/abs/.wt/feature-branch/some/path  # instead of /abs/.wt/feature-branch

# Use --relative flag for a single invocation
$ git wt --relative feature-branch

Default: false

Note

If the subdirectory does not exist in the target worktree, the output falls back to the worktree root path.

Recipes

peco

You can use peco for interactive worktree selection:

$ git wt $(git wt | tail -n +2 | peco | awk '{print $(NF-1)}')

fzf

You can use fzf for interactive worktree selection:

bash/zsh

$ cd $(git-wt | fzf --header-lines=1 | awk '{if ($1 == "*") print $2; else print $1}')

fish

$ cd (git-wt | fzf --header-lines=1 | awk '{if ($1 == "*") print $2; else print $1}')

tmux

When creating a new worktree, open and switch to a new tmux window named {repo}:{branch}. The working directory will be the new worktree:

$ git config wt.nocd create
$ git config --add wt.hook 'tmux neww -c "$PWD" -n "$(basename -s .git `git remote get-url origin`):$(git branch --show-current)"'
  • wt.nocd create: Prevents automatic directory change when creating new worktrees (tmux opens a new window instead), but still allows cd when switching to existing worktrees.
  • wt.hook 'tmux neww ...': Creates a new tmux window (neww) with -c "$PWD" setting the working directory to the new worktree, and -n "..." naming the window as {repo}:{branch}.

About

A Git subcommand that makes `git worktree` simple

Topics

Resources

Stars

552 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages