Neovim plugin for gtr (git worktree runner). Manage git worktrees from Neovim with a picker UI, commands, and statusline integration.
- Neovim >= 0.10
- gtr CLI installed and in PATH
- snacks.nvim (picker backend)
{
"malkoG/gtr.nvim",
opts = {},
keys = {
{ "<leader>gw", function() require("gtr").list() end, desc = "Git Worktrees" },
},
}require("gtr").setup({
-- Path to gtr binary (nil = auto-detect from PATH)
cmd = nil,
-- Picker backend: "snacks" (telescope/fzf-lua planned)
picker = "snacks",
-- How :GtrGo changes directory
-- "global" = :cd, "tab" = :tcd, "window" = :lcd
cd_strategy = "tab",
-- Keymaps inside the picker
picker_actions = {
{ key = "<CR>", action = "go" },
{ key = "<C-n>", action = "new" },
{ key = "<C-e>", action = "editor" },
{ key = "<C-d>", action = "rm" },
{ key = "<C-r>", action = "run" },
},
-- Statusline
statusline = {
icon = "",
show_status = true,
},
-- Output display for :GtrRun, :GtrDoctor
output_display = "float", -- "float" | "split" | "notify"
-- Callbacks
on_worktree_new = nil, -- fun(branch: string, path: string)
on_worktree_rm = nil, -- fun(branch: string)
on_worktree_go = nil, -- fun(branch: string, path: string)
})| Command | Description |
|---|---|
:GtrList |
Open worktree picker |
:GtrNew <branch> [flags] |
Create a new worktree |
:GtrGo <branch> |
Change cwd to worktree |
:GtrRm <branch> [flags] |
Remove a worktree (with confirmation) |
:GtrEditor <branch> |
Open worktree in external editor |
:GtrRun <branch> <cmd...> |
Run a command inside a worktree |
:GtrMv <old> <new> |
Rename worktree and branch |
:GtrCopy <target> |
Copy configured files to worktree |
:GtrClean [flags] |
Remove stale worktrees |
:GtrDoctor |
Health check |
All branch-accepting commands support tab completion.
The picker (:GtrList or Lua require("gtr").list()) shows all worktrees with:
- Active worktree marked with
* - Branch name, status, and path
- Preview showing git log of commits ahead of the default branch
| Key | Action |
|---|---|
<CR> |
Switch to worktree (change cwd) |
<C-n> |
Create new worktree |
<C-e> |
Open in external editor |
<C-d> |
Remove worktree |
<C-r> |
Run command in worktree |
Add "gtr_worktree" to your lualine config:
require("lualine").setup({
sections = {
lualine_b = { "branch", "gtr_worktree", "diff", "diagnostics" },
},
})Use require("gtr.statusline").get() in your custom statusline.
local gtr = require("gtr")
gtr.list() -- Open picker
gtr.new("feature/foo") -- Create worktree
gtr.new("feature/foo", { -- Create with flags
from = "develop",
["no-fetch"] = true,
})
gtr.go("feature/foo") -- Change cwd to worktree
gtr.rm("feature/foo") -- Remove worktree
gtr.editor("feature/foo") -- Open in external editor
gtr.run("feature/foo", {"npm", "test"}) -- Run command
gtr.mv("old-name", "new-name") -- Rename
gtr.copy("feature/foo") -- Copy files
gtr.clean() -- Clean stale worktrees
gtr.clean({ merged = true }) -- Clean merged worktrees
gtr.doctor() -- Health checkWhen switching worktrees via the picker or :GtrGo, all buffers are refreshed:
- Fugitive/git buffers are wiped. These cache git directory state internally and cannot be reliably refreshed in place. Reopen with
:Gitafter switching. - Oil buffers are wiped. Oil caches directory listings tied to the old worktree path. Reopen with
:Oilafter switching. - Regular file buffers are force-reloaded (
:edit!) to pick up content from the new worktree. - Lualine/statusline is refreshed via
BufEnter+fugitive#DidChange().
This ensures a clean slate — no stale git state leaking across worktrees.
Fugitive is automatically refreshed when switching worktrees. The plugin clears stale b:git_dir on all buffers, calls FugitiveDetect() + fugitive#DidChange(), and fires BufEnter so the statusline branch updates correctly. Gitgutter also picks up the change via FugitiveChanged.
Run :checkhealth gtr to verify your setup (gtr binary, git, Neovim version, picker backend, git repo detection).