wtg manages feature branches that span multiple repos. When a feature touches several repos at once, wtg checks out a shared branch across all of them as git worktrees and wires them together with a go.work file — giving you an isolated, ready-to-build workspace per feature without cloning anything new.
New to worktrees? The GitKraken worktree guide is a good primer.
go install github.com/geoffamey/wtg@latestbash — add to ~/.bashrc:
source <(wtg completion bash)
wcd() { cd "$(wtg path "$1")"; }zsh — add to ~/.zshrc:
source <(wtg completion zsh)
wcd() { cd "$(wtg path "$1")"; }fish — add to ~/.config/fish/conf.d/wtg.fish:
if status is-interactive
wtg completion fish | source
function wcd
cd (wtg path $argv[1])
end
complete -c wcd -f -a '(wtg path --generate-shell-completion 2>/dev/null)'
endwcd <workspace> is a shell helper that cds into the workspace root. Since
cd must run in the current shell it can't be a standalone command. The fish
complete line gives wcd the same workspace-name tab completion as wtg path.
Run wtg config init to scaffold a commented config file at
~/.config/wtg/config.toml:
wtg config initIt writes every setting commented out with its default; uncomment and edit the
lines you want to override. wtg config prints the resolved file, and
wtg config path prints its path. A minimal config looks like:
[discovery]
root_dir = "~/repos" # where wtg scans for git repos
max_depth = 2
[spaces]
root_dir = "~/workspaces" # where workspaces are created
[git]
branch_prefix = "" # prepended to workspace names, e.g. "yourname/"YAML is still accepted: a file ending in .yaml/.yml loads via its extension,
so an existing config.yaml keeps working.
discovery.root_dir should contain your regular repo clones, each sitting on
their default branch (main, master, etc.) and otherwise left untouched.
wtg creates worktrees alongside them — it never modifies the main clones.
Override with --config <path> or the WTG_CONFIG environment variable.
The optional always section applies the same setup to every new space:
[always]
repos = ["shared-tooling"] # symlinked into every new space
files = ["~/.config/wtg/CLAUDE.md"] # copied into every new space root
run = "~/.config/wtg/on-event" # executable run after create/add/remove/deletealways.repos symlinks shared repos in (no feature-branch worktree),
always.files seeds template files, and always.run invokes a hook script on
space lifecycle events with the space context in WTG_* environment variables.
See docs/always.md for the full behaviour, the event/variable
reference, and examples.
# Create a workspace for a new feature across three repos
wtg new my-feature api payments frontend
# Jump in
wcd my-feature
# ... do your work, then clean up
wtg delete my-feature --delete-branchCreate a workspace. At least one repo must be specified. For each repo, wtg
creates or checks out a branch named <branch_prefix><workspace> as a linked
worktree. A go.work file is written automatically for repos that have a
go.mod.
wtg new my-feature api payments frontend
wtg new my-feature api --branch yourname/main # check out an existing branchBranch behaviour per repo:
| Branch state | Action |
|---|---|
| Does not exist | Created from the repo's default branch |
| Exists, not checked out | Checked out in the new worktree |
| Exists, already checked out | Error |
Delete a workspace and remove its worktrees. Prompts for confirmation if any repo has uncommitted changes or unpushed commits.
wtg delete my-feature # remove worktrees, keep branches
wtg delete my-feature -d # also delete branches if merged
wtg delete my-feature -D # force-delete branchesAdd repos to an existing workspace. Creates worktrees on the workspace's branch
and updates go.work.
wtg add my-feature infra loggingRemove repos from a workspace. Prompts if there are uncommitted changes or
unpushed commits. Use wtg delete to remove the whole workspace.
wtg remove my-feature logging
wtg remove my-feature logging -d # also delete the branchShow workspace status. Without arguments, shows all workspaces — the one
containing the current directory is listed first. Pass --long / -l to
expand file-level changes per repo.
wtg status
wtg status my-feature
wtg status my-feature --longmy-feature ~/workspaces/my-feature
api [geoff/my-feature] ✓ clean ↑2
payments [geoff/my-feature] ! 2 modified
frontend [geoff/my-feature] ✓ clean
Run a command in each repo's worktree sequentially. Execution continues even if a command fails — all repos are attempted and failures are reported at the end.
wtg exec my-feature -- git status
wtg exec my-feature -- go test ./...
wtg exec my-feature -- git push origin HEADThese operate on your main repo clones, not workspace worktrees. Useful for keeping clones up to date before starting a new feature.
Fetch and fast-forward each repo's default branch. Repos with local changes are skipped with a warning.
wtg repo sync # sync all repos
wtg repo sync api payments # sync specific reposShow branch, dirty status, and ahead/behind counts for each main repo clone.
wtg repo status
wtg repo status --long # also show remote URL and local path