wt, Lua worktree manager
Commands
make all— format, lint, type-check, and test (the standard workflow)make test-quiet— use this! shows summary on success, full output on failuremake test— verbose test output (busted)lx test spec/foo_spec.lua— run a single test filelx run— run the application (src/main.lua)make dist— bundle all modules intodist/wtsingle-file executablemake install— install to~/.local/bin/make ci— CI mode (format check + lint + check + test)
Structure
src/
├── main.lua # Entry point, command dispatch
└── wt/
├── exit.lua # Exit codes
├── shell.lua # run_cmd, run_cmd_silent, die, quote
├── path.lua # Path manipulation (branch_to_path, etc.)
├── git.lua # Git operations, project root detection
├── config.lua # Config loading (global + project)
├── hooks.lua # Hook permissions and execution
├── help.lua # Usage text
└── cmd/ # One module per command
spec/ # Tests (busted framework, *_spec.lua)
scripts/bundle.lua # Builds single-file dist/wt
Lux Handles These (don't suggest manually)
- Dependencies: use
lx add <pkg>, notluarocks install - Tools (busted, luacheck, stylua): auto-installed on first use
- Module paths: Lux loader resolves requires; don't manipulate
package.path
Code Style
- Lua 5.4; use LuaCATS annotations (
---@param,---@return,---@class) - Tabs for indentation, 120 char line limit (see stylua.toml)
- Prefix intentionally unused variables with
_to silence luacheck
Architecture
Core Pattern: Bare Repo + Worktrees
wt manages git repositories using a bare repository structure:
.bare/- the actual git repository (moved from.git/).git- a file pointing to.bare/(makes git commands work)- Worktrees checked out as sibling directories alongside
.bare/
Module Organization
Each command lives in src/wt/cmd/<name>.lua and exports a cmd_<name>(args) function. Commands:
- Parse flags and positional args from the
argstable - Validate inputs with
shell.die(msg)for user errors - Execute via
shell.run_cmd()/shell.run_cmd_silent() - Return consistent exit codes from
wt.exit
Utility modules (shell, git, path, config, hooks) are shared across commands.
Exit Codes and Error Handling
local exit = require("wt.exit")
exit.EXIT_SUCCESS -- 0: normal completion
exit.EXIT_USER_ERROR -- 1: user mistakes (bad args, conflicts)
exit.EXIT_SYSTEM_ERROR -- 2: system failures (git errors, IO errors)
Use shell.die(msg, code) to print to stderr and exit. Default code is EXIT_USER_ERROR.
Git Command Patterns
- Bare repo operations:
GIT_DIR=<path>/.bare git <command> - Worktree operations:
git -C <path> <command> - Always check exit codes from
run_cmd()before assuming success - Use
shell.quote(str)when embedding user input in commands
Testing
Tests use busted framework in spec/. Key patterns:
-- Load wt as module (exports functions when required, not run directly)
package.path = package.path .. ";./?.lua"
local wt = dofile("src/main.lua")
-- Use spec/test_helper.lua for git operations
local helper = require("spec.test_helper")
local git = helper.git -- Preconfigured git wrapper (suppresses noise)
-- Standard setup/teardown for temp directories
local temp_dir
setup(function()
local handle = io.popen("mktemp -d")
if handle then
temp_dir = handle:read("*l")
handle:close()
end
end)
teardown(function()
if temp_dir then os.execute("rm -rf " .. temp_dir) end
end)
The dofile("src/main.lua") pattern works because main.lua detects when it's required vs run directly and exports test utilities.
Distribution
make dist bundles all modules into a single dist/wt file using scripts/bundle.lua. The bundler:
- Embeds each module as a string literal
- Provides custom
require()that loads embedded modules first - Falls back to real
require()for stdlib
Order matters: Update MODULE_ORDER in scripts/bundle.lua when adding new modules (dependencies before dependents).
Non-Obvious Behaviors
- Source Worktree Detection:
wt amust run from inside a worktree (not project root) for hooks to trigger. - Idempotent Add: Running
wt a <branch>when worktree exists prints path and succeeds (exit 0), with hints if run from root. - Branch Existence Checking: Adding without
-bchecks both local and all remotes. Fails if branch exists on multiple remotes (ambiguous). - Bare HEAD Management: During
wt init, bare repo's HEAD moves to placeholder so default branch can be checked out in worktree. - Config File Format: Both
return { ... }and bare table{ ... }are accepted (loader tries prependingreturn). - Test Module Export:
src/main.luausespcall(debug.getlocal, 4, 1)to detect if it wasrequired and exports internal functions for testing.
External Dependencies
gum- interactive prompts (choose, confirm)- Standard POSIX:
mkdir,cp,ln,rm,mv,pwd,mktemp - git