timber

command module
v0.13.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2026 License: MIT Imports: 4 Imported by: 0

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 switch that cds into a worktree
  • t switch -c | --create creates the worktree first, then cds, unless --no-cd or a Herdr space is opened
  • t switch <name> uses that worktree if the name exists in exactly one registered repository
  • t 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 remove or t 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 listrepo ls
  • repo removerepo rm
  • repo renamerepo mv
Repository selection

Worktree commands take an optional <worktree>@<repo> qualifier on the name argument.

  • feature/login@timber selects that worktree in the timber repository
  • feature/login is enough when the name exists in exactly one registered repository
  • @timber selects the repository with no worktree name (create generates a random name; list and prune pin 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/timberhttps://github.com/nnutter/timber
  • Full URLs, git@host:path, and local paths pass through unchanged
  • --name overrides 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 from origin/master then origin/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 Agent tab that runs pi in a pane named after the branch and a Shell tab
  • Use --herdr to open the space explicitly, or --no-herdr to suppress automatic creation
  • Opening a Herdr space through t create implies --no-cd
  • Opening a Herdr space requires herdr on PATH and 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/k to 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-herdr is set
  • Use --herdr to 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: runs pi in the worktree in a pane named after the branch
  • Shell: 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 name
  • Repo: registered repository name
  • Status: aligned ahead (, green) and behind (, blue) counts, followed by the upstream branch
  • Commit: short commit hash
  • Dirty: whether the worktree has uncommitted changes (true is 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, else origin/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 | -p to 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

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL