Skip to content

v0.2.0

Choose a tag to compare

@timvw-ci-bot timvw-ci-bot released this 20 Aug 11:09
· 17 commits to main since this release
47629d7

Thirteen commits since v0.1.33, including three new configuration features, a security fix that changes how repo-supplied hooks are treated, and cross-platform fixes for Git Bash, PowerShell and fish.

⚠️ Action required

Hooks from a repository's committed .wt.toml now require approval before they run (#129). Previously they were merged into the effective hook config and executed via sh -c with no prompt and no opt-in, so cloning an untrusted repo and running wt create was arbitrary code execution as the invoking user.

Approval is pinned to (repository, sha256 of .wt.toml), so any edit — a pull that adds a post_create, a branch with a different .wt.toml — asks again, and an identical file in another repository is not covered. Approvals live in ~/.config/wt/trust.toml, never in the repository. Declining skips the hooks with a warning; it never aborts the operation. With no terminal to ask on (scripts, CI, --format json) the answer is skip, unless WT_HOOKS_APPROVE_ALL=1.

New hooks_policy setting (config file or WT_HOOKS_POLICY) chooses how much is gated: prompt-untrusted (default), prompt-all, trusted-only, off. New commands: wt trust, wt trust --list, wt untrust.

Hooks from your own ~/.config/wt/config.toml are unaffected under the default policy.

Features

  • wt clone (#127) — wt clone <owner/repo|url> [dest] acquires the main repository so wt create/wt checkout have something to hang worktrees off. Placement is driven by two new top-level settings, repo_root and repo_pattern; the trailing branch segment resolves from the remote's default branch (git ls-remote --symref), so the clone lands in a valid main-worktree slot. owner/repo resolves through gh or glab, each asked for its own git_protocol — account selection stays with those tools and wt never mutates their auth state. Based on #122 by @brosu.
  • Configuration from git config (#128) — per-repo settings can now live outside the working tree, in .git/config, which is never committed:
    git config --local wt.strategy sibling-repo
    git config --global wt.root ~/projects/worktrees
    
    Supported keys are the scalars: wt.root, wt.strategy, wt.pattern, wt.separator. Precedence, highest first: env vars > local git config > .wt.toml > config file > global git config > defaults. Local git config outranks .wt.toml because it is personal local state rather than project policy arriving via a pull request. Hooks are deliberately out of scope for now. This also corrects the documented precedence, which previously omitted .wt.toml entirely.
  • Per-directory environment variables via [[context]] rules (#145) — includeIf-style rules bind variables to a directory tree instead of requiring a per-command export or direnv:
    [[context]]
    when_path = "~/dev/repos/work"
    env = { WT_CATEGORY = "work" }
    Matching is a path prefix (not a glob), evaluated after resolving symlinks, case-insensitively on macOS and Windows. Every matching rule applies, later rules winning per variable. Repo-scoped commands match the repository's main checkout so all linked worktrees resolve to the same rule; wt clone matches the working directory. Rules are rendering-only — merged underneath os.Environ(), never exported, so a real export still wins and hooks see an unchanged environment. Read from the user config file only, so a committed .wt.toml cannot redirect where worktrees land.
  • Default values for env vars in patterns (#139) — {.env.WT_CATEGORY:-personal} falls back to personal when the variable is unset. Without the :- syntax unset variables still error, preserving typo detection. Defaults respect the configured separator, so with separator = "-", {.env.X:-a/b} yields a-b. wt info now advertises the syntax.

Fixes

  • Interactive prompts are invisible under Git Bash (#124, #126) — prompts were live and accepted keystrokes, but rendered to stdout, which the shell wrapper captures to find the navigation marker. Git Bash ships no script(1), so the whole menu landed in a temp file and only appeared when the trailing cat replayed it. Prompts now render to stderr, which no integration redirects. Covers all six prompts: checkout, cd, remove and pr/mr selectors, and the cleanup confirmation. On Windows the raw VT stream is kept on stderr and console mode is enabled via SetConsoleMode for plain conhost. Reproduced and confirmed fixed on a real windows-latest runner driving Git Bash through ConPTY.
  • fish integration fails where script(1) is absent (#131, #133) — the fish wrapper called script unconditionally, so on minimal containers or any distro without util-linux every wt call died with Unknown command: script and exit 127. Now mirrors the bash/zsh fallback. $status is captured inside each branch so the replaying cat can no longer mask a failing command into an auto-cd.
  • PowerShell profile line silently does nothing (#130, #135) — the documented Invoke-Expression (& wt shellenv powershell) fails on Windows PowerShell 5.1: shellenv emits many lines, so the subexpression is an Object[] and -Command ([string]) rejects it. The wt function was never defined, so auto-cd, completion and interactive menus were silently absent while wt.exe on PATH kept plain commands working. Now piped through Out-String, with the line hoisted into a shared constant used by wt init and wt shellenv help, and a test pinning docs and the e2e harness to the documented form.
  • e2e runner tested the wrong binary (#141, #142) — it scanned for a pre-existing ./bin/wt and otherwise fell back to whatever wt was on PATH, never building anything. Since bin/ is gitignored, a stale build survives a rebase, and the PATH fallback picks up a system-wide install — producing failures that are not real and, worse, passes that hide a regression. It now builds from the module root into a temp directory; -wt remains the explicit escape hatch. Adds unit tests for the orchestrator, which had none.

Docs

  • direnv recipe for per-directory categories (#143) — complements the [[context]] rules above; the :- defaults from #139 make it degrade gracefully where no .envrc exists.
  • The examples page now notes that repo-supplied hooks need approval.
  • Dropped the paragraph claiming interactive menus are unavailable under Git Bash — that described the bug, not the design.

Chores

  • Go 1.27 (#140)
  • nixos/nix docker digest bump (#123)

Full commit list: v0.1.33...v0.2.0