README
¶
timber
timber manages Git worktrees from registered bare repositories.
There is no required “main” worktree. Repositories are stored as bare Git directories, and worktrees are created on demand under a shared root:
<worktree-root>/<repo-name>/<worktree-name>/<repo-name>
Defaults:
- bare repos:
$XDG_DATA_HOME/timber/repos/<repo-name>.git(fallback:~/.local/share/timber/repos/<repo-name>.git) - worktrees:
$TIMBER_WORKTREE_ROOT/<repo-name>/<worktree-name>/<repo-name>(fallback:~/worktrees/<repo-name>/<worktree-name>/<repo-name>)
The worktree name and branch name are identical (including /).
Example:
- repo name:
timber - bare repo:
~/.local/share/timber/repos/timber.git - branch:
nn/my-feature - worktree path:
~/worktrees/timber/nn/my-feature/timber
Use timber migrate inside an existing clone to register it as a bare repo and rehome its worktrees (including the former main checkout) into this layout.
Use timber migrate inside a registered worktree to move that repository’s worktrees into this layout.
Use timber migrate --all to rehome worktrees for every registered repository.
When invoked through the shell wrapper (t migrate), the shell also cds to $HOME after success.
Installation
Install using Go,
go install github.com/nnutter/timber@latest
timber requires Git on PATH.
Herdr Plugin
Install the bundled Herdr plugin with:
timber herdr install
That command writes the plugin to ~/.config/herdr/plugins/timber ($XDG_CONFIG_HOME/herdr/plugins/timber when set) and runs herdr plugin link on the copy.
Copying the files is not enough on its own: Herdr only registers actions after plugin link or plugin install.
The popup runs timber tui --herdr after it adds common tool paths.
The TUI can create a new worktree or open a Herdr space for an existing one.
The command prints a keybinding snippet to add to ~/.config/herdr/config.toml:
[[keys.command]]
key = "prefix+shift+s"
type = "plugin_action"
command = "nnutter.timber.open"
description = "create or open timber space"
Development
This repository uses mise for tools and tasks.
mise install
lefthook install
mise run check
mise run fmt formats Go files.
mise run fmt-check reports formatting that does not match gofumpt.
mise run lint runs golangci-lint, nilaway, and phase-shift.
mise run ci runs check, gitleaks, and govulncheck.
lefthook install enables pre-commit formatting and gitleaks.
Pull requests run mise run ci.
Shell integration
Generate a zsh function that wraps the CLI (default name t):
timber generate zsh
# or: timber generate zsh --name t --out $XDG_DATA_HOME/zsh/site-functions --force
The default command generates the wrapper t, the completion _t, and the autoload helper _t_autoload.
The helper starts with #autoload t, which lets compinit make t available without source or an explicit autoload command.
With --name foo, the command generates foo, _foo, and _foo_autoload, and the helper starts with #autoload foo.
Ensure the output directory is on fpath before zsh runs compinit, then restart zsh or run compinit.
The generated function:
- routes most commands to
timber(t create,t list,t prune, …) - after a successful
t create,cds into the new worktree unless--no-cd,--herdr, or automatic Herdr workspace creation applies - provides a shell-only
switchthatcds into a worktree t switch -c|--createcreates the worktree first, thencds, unless--no-cdor a Herdr space is openedt switch <name>uses that worktree if the name exists in exactly one registered repositoryt switch <Tab>completes worktree names from every registered repository- Unique names complete without
@ - If a name exists in more than one repository, completion offers
<name>@<repo>for each one (artisinal@liaison,artisinal@persona) - after a successful
t removeort migrate,cds to$HOME
t repo add nnutter/timber
t create feature/login@timber # then cd into it
t create @timber # random name, then cd into it
t switch feature/login@timber
t switch feature/login # unique name across repositories
t switch -c feature/new@timber # create then cd
t switch feature/new@timber -c # same; -c can follow the name
t tui # create a worktree or open an existing one
t herdr install # install the Herdr plugin
t herdr space # current worktree
t herdr space feature/login@timber
t create --no-cd other@timber # create only
t remove feature/login # then cd $HOME
t list
t list @timber
Commands
The following aliases are available for the commands below:
| Command | Alias |
|---|---|
list |
ls |
prune |
clean |
remove |
rm |
The nested command groups have these aliases as well:
repo list→repo lsrepo remove→repo rmrepo rename→repo mv
Repository selection
Worktree commands take an optional <worktree>@<repo> qualifier on the name argument.
feature/login@timberselects that worktree in thetimberrepositoryfeature/loginis enough when the name exists in exactly one registered repository@timberselects the repository with no worktree name (creategenerates a random name;listandprunepin that repository)
list and prune use every registered repository unless @<repo> pins one.
create (and switch -c) use the current repository when the cwd is a managed worktree of a registered repo, otherwise an interactive picker.
remove and herdr space with no name target the managed worktree that contains the cwd.
In non-interactive environments commands that need a single repository fail unless @<repo> is set or the cwd auto-detects a managed repo.
Worktree names must not contain @.
timber repo add <url-or-path>
Register a bare repository.
- Schema-less relative paths map to GitHub:
nnutter/timber→https://github.com/nnutter/timber - Full URLs,
git@host:path, and local paths pass through unchanged --nameoverrides the derived repository name (default: basename of the URL)
Example:
timber repo add nnutter/timber
timber repo add --name my-fork git@github.com:me/timber.git
timber repo add /path/to/existing.git
timber repo list
List registered repositories, including each repository's origin URL.
Use -q or --quiet to print only repository names, one name per line.
timber repo remove <name>
Remove a registered bare repository. Refuses if any worktrees remain.
timber repo rename <old-name> <new-name>
Rename a registered bare repository and its managed worktree directories. The command preserves local changes and leaves unmanaged linked worktrees at their existing paths.
The managed worktree path changes from:
<worktree-root>/<old-name>/<worktree-name>/<old-name>
to:
<worktree-root>/<new-name>/<worktree-name>/<new-name>
The t wrapper changes to the new path if the current directory is inside a moved worktree.
The command refuses the rename if the destination repository or a destination worktree path exists.
Example:
timber repo rename timber git-worktree
timber create [name[@repo]]
Create a managed worktree for a branch.
- Qualify the name as
<worktree>@<repo>to select the repository;@<repo>alone generates a random name in that repository - If the name is omitted, generates a random
<adjective>-<noun>name themed around SpaceX, Starlink, and Tesla - If the branch already exists, the worktree is created from that branch
- If the branch does not exist, it is created from the branch pointed at by
origin/HEAD, or if that is unset fromorigin/masterthenorigin/main; set it explicitly with--upstream|-u - When run inside Herdr (
HERDR_ENV=1), automatically open the new worktree in a standard Herdr space - The space contains an
Agenttab that runspiin a pane named after the branch and aShelltab - Use
--herdrto open the space explicitly, or--no-herdrto suppress automatic creation - Opening a Herdr space through
t createimplies--no-cd - Opening a Herdr space requires
herdronPATHand a running Herdr server
Example:
timber create feature/login@timber
timber create @timber
timber create -u origin/v1.2 hotfix/1.2.1
timber create --herdr feature/login@timber
timber tui
Interactively create a managed worktree or open a Herdr space for an existing one.
- First pick Create worktree or Open existing worktree
- Create: pick a registered repository and type a worktree name
- Open: pick an existing managed worktree, then open a new Herdr space
- Select lists use
j/kto move and/to filter - Always lists every registered repository, including when the current directory is already a managed worktree
- Never generates a random name
- Requires an interactive terminal
- When creating inside Herdr (
HERDR_ENV=1), opens a new standard Herdr space unless--no-herdris set - Use
--herdrto open the space explicitly after create
Example:
timber tui
timber tui --herdr
timber herdr install
Install the bundled Herdr plugin into ~/.config/herdr/plugins/timber and register it with herdr plugin link --enabled.
Prints a keybinding snippet for ~/.config/herdr/config.toml.
The plugin files are embedded in the timber binary, so this works without a source checkout.
Requires herdr on PATH.
Example:
timber herdr install
timber herdr space [name[@repo]]
Set up a standard Herdr space for a managed worktree.
By default the command defines the tabs in the current Herdr workspace.
It renames the current tab to Agent and adds a Shell tab.
Use -n | --new to open a new Herdr workspace instead.
The workspace contains two tabs:
Agent: runspiin the worktree in a pane named after the branchShell: opens an interactive shell in the worktree
To show the branch name in the expanded sidebar, customize Herdr's
Sidebar row layouts
and use pane in ui.sidebar.agents.rows:
[ui.sidebar.agents]
rows = [
["state_icon", "workspace"],
["pane", "tab"],
]
If name is omitted, the command uses the managed worktree that contains the current directory.
Qualify the name as <worktree>@<repo> to select a worktree in another repository.
The command requires herdr on PATH and a running Herdr server.
Example:
timber herdr space feature/login@timber
timber herdr space --new feature/login@timber
cd ~/worktrees/timber/feature/login/timber
timber herdr space
timber list [@repo]
List managed worktrees in a table.
- Default: list worktrees from every registered repository
@<repo>: list only the named repository
Columns:
Name: branch / worktree nameRepo: registered repository nameStatus: aligned ahead (↑, green) and behind (↓, blue) counts, followed by the upstream branchCommit: short commit hashDirty: whether the worktree has uncommitted changes (trueis highlighted in yellow)
timber migrate
Register a clone as a bare repo, or rehome existing worktrees into the managed layout.
- Inside an unregistered clone: creates
$XDG_DATA_HOME/timber/repos/<name>.git(override name with--name) - Moves every branched worktree (including the former main checkout) to
$TIMBER_WORKTREE_ROOT/<repo-name>/<branch>/<repo-name>(fallback:~/worktrees/...) - Inside a registered worktree: moves that repository’s worktrees that are not already at the managed path
--all|-a: rehomes worktrees for every registered repository- Removes empty parent directories of the old checkout, up to
$HOME - If the clone has no linked worktrees and HEAD is the default branch (
origin/HEAD, elseorigin/master/origin/main), only the bare repo is registered (no managed worktree is created) - Does not create worktrees for local branches that do not already have one
- Use
--prompt|-pto choose which worktrees to migrate
Example:
cd ~/src/github.com/nnutter/timber
timber migrate
timber migrate --name timber --prompt
cd ~/worktrees/next/timber
timber migrate
timber migrate --all
timber prune [@repo]
Remove managed worktrees that are both clean and merged into their upstream branch.
Without @<repo>, prune considers every registered repository.
Use --prompt | -p to choose which worktrees to prune interactively.
Use -n | --dry-run to list the worktrees that would be pruned without removing them.
timber remove [name[@repo]]
Remove a managed worktree and delete its branch.
When name is omitted, removes the managed worktree that contains the current directory (auto-detects the registered repo from cwd, or use <worktree>@<repo> / the repo picker).
A unique worktree name is enough from outside a managed worktree.
Refuses dirty or unmerged worktrees by default.
Use --force | -f to force (destructive) removal.
When invoked through the shell wrapper (t remove), the shell also cds to $HOME after a successful removal.
Example:
timber remove
timber remove feature/login@timber
timber remove --force feature/login@timber
timber generate zsh
Generate a zsh wrapper function, completion, and autoload helper (see Shell integration).
Typical Flow
# once: install wrapper
timber generate zsh
# register a repo
t repo add nnutter/timber
# day to day
t create feature/login@timber
t switch feature/login@timber
# ... work ...
t switch main@timber # if you created a main worktree
t prune @timber
# or:
t remove feature/login
Documentation
¶
There is no documentation for this package.