mirror of
https://github.com/max-sixty/worktrunk.git
synced 2026-09-14 20:00:38 +08:00
946e360447
Cuts 0.70.0 — version bump plus the changelog for the 45 commits since
v0.69.2.
**Minor bump, not patch.** `cargo semver-checks` reports 6 breaking
library changes (`set_command_timeout` and `ListConfig::task_timeout_ms`
removed, `WorkingTree::stage` arity changed, three enum variants added
to exhaustive enums). Worktrunk ships breaking library changes freely,
but semver still puts a break at minor while pre-1.0.
## Release validation
- Local gate green on the release commit: 4631 tests, lints, doctests.
- `nightly` dispatched on the cut-from tip (`a27cbd42`) for the full
cross-platform suite — `full-tests` green on linux, macOS, and Windows,
plus minimal-versions, nix-flake, crate-build, and the release targets.
## Data-loss surface review
The cumulative diff was audited against the deletion surface. One
deliberate widening, signed off for this release with follow-ups to
file:
- **#3602** removes the content check from `wt config shell install`'s
legacy cleanup, so `conf.d/{cmd}.fish` and the stranded nushell
`{cmd}.nu` are now deleted by path, unread. Only that exact filename is
touched and each removal is reported, but the deletion is absent from
both `--dry-run` and the confirmation prompt, and the already-configured
path skips the prompt entirely.
Also noted, none blocking:
- The `!path.exists()` check precedes the lock guard on the
`Path`/`Current` removal arm, so a locked worktree whose directory is
absent loses its branch. This already governed the branch-targeted route
in v0.69.2; #3533 unified the other arms onto it. The FAQ's "Neither
`git worktree remove` nor `wt remove` (even with `--force`) will delete
them" is absolute where the behavior isn't.
- On the default background removal path, `ensure_clean` and
`stop_fsmonitor_daemon` swapped order, so the safety gate is now
answered by the live fsmonitor daemon rather than a full re-stat.
Bounded by trash staging with 24-hour retention, and the foreground and
picker paths were already daemon-served.
Net *improvements* to the same surface: shared-branch retention across
remove/prune/merge (#3533), outcome-accurate removal reporting (#3633,
#3637), and the removal of the thread-local command timeout that could
kill in-flight git commands on worker threads (#3615).
## Changelog accuracy
Entries were verified against the actual diffs rather than commit
messages, which corrected several drafts: the prune figures were one PR
stale (~2.9 s → the real ~0.6 s), `wt merge` takes no worktree argument
so it only gained the retention half of #3533, the Azure DevOps report
is behind `--full`, #3608 never touched `nightly.yaml`, and #3601
inverted what the FAQ change actually said. Two omissions were added —
the `install-statusline` foreign-statusline fix (#3595) and the shipped
`/wt-switch-create` skill change (#3636).
> _This was written by Claude Code on behalf of Maximilian_
374 lines
16 KiB
TOML
374 lines
16 KiB
TOML
[workspace]
|
|
members = ["tests/helpers/wt-perf", "tests/helpers/mock-stub"]
|
|
# Include the helper packages so a workspace `cargo test` builds their
|
|
# binaries: mock-stub via its dummy tests/builds.rs, wt-perf via its real
|
|
# in-package CLI tests.
|
|
default-members = [".", "tests/helpers/mock-stub", "tests/helpers/wt-perf"]
|
|
|
|
# cargo-affected input→test rules. cargo-affected selects tests by Rust-line
|
|
# coverage overlap, so a file a test reads at *runtime* (never recorded in
|
|
# coverage) maps to no test and would slip through `cargo affected run`. Each
|
|
# rule force-selects the owning tests when a matching path changes. `[*.metadata]`
|
|
# is excluded from cargo-affected's fingerprint, so editing a rule is
|
|
# cache-neutral. See https://github.com/max-sixty/cargo-affected#input-rules.
|
|
|
|
# insta `.snap` snapshots are read at test time and never appear in coverage, so
|
|
# a snapshot edit maps to no test. Route each snapshot to the binary that owns
|
|
# it — the finest granularity available without a snapshot→test map. Integration
|
|
# snapshots live under tests/; unit-test snapshots under src/, split across the
|
|
# lib (`worktrunk`) and the `wt` binary (`worktrunk::bin/wt`).
|
|
[[workspace.metadata.affected.rule]]
|
|
globs = ["tests/**/*.snap"]
|
|
filterset = "binary_id(=worktrunk::integration)"
|
|
|
|
[[workspace.metadata.affected.rule]]
|
|
globs = ["src/**/*.snap"]
|
|
filterset = "binary_id(=worktrunk) | binary_id(=worktrunk::bin/wt)"
|
|
|
|
# tests/integration_tests/readme_sync.rs reads each of these at runtime (via
|
|
# fs::read_to_string) and asserts the committed copies stay in sync — both the
|
|
# sources (README.md, docs/content markdown, docs/config.toml's site title and
|
|
# description, src/cli/mod.rs, src/llm.rs, the skill references, Taskfile.yaml)
|
|
# and the generated artifacts it regenerates and compares against (the
|
|
# dev/*.example.toml files, docs/static/llms.txt, the agent-skills index.json).
|
|
# Editing any of them flips the test, but coverage can't link a runtime-read
|
|
# file to it.
|
|
[[workspace.metadata.affected.rule]]
|
|
globs = [
|
|
"README.md",
|
|
"docs/**/*.md",
|
|
"docs/config.toml",
|
|
"docs/static/llms.txt",
|
|
"docs/static/.well-known/agent-skills/index.json",
|
|
"dev/*.toml",
|
|
"src/cli/mod.rs",
|
|
"src/llm.rs",
|
|
"Taskfile.yaml",
|
|
"skills/**/*.md",
|
|
]
|
|
filterset = "test(/readme_sync/)"
|
|
|
|
[package]
|
|
name = "worktrunk"
|
|
version = "0.70.0"
|
|
edition = "2024"
|
|
rust-version = "1.96"
|
|
build = "build.rs"
|
|
repository = "https://github.com/max-sixty/worktrunk"
|
|
homepage = "https://worktrunk.dev"
|
|
description = "A CLI for Git worktree management, designed for parallel AI agent workflows"
|
|
license = "MIT OR Apache-2.0"
|
|
default-run = "wt"
|
|
|
|
[package.metadata.release]
|
|
# Allow releases from any branch (GitHub Actions uses detached HEAD)
|
|
allow-branch = ["*"]
|
|
consolidate-commits = true
|
|
|
|
[package.metadata.cargo-udeps.ignore]
|
|
# `which` is used only on Windows (#[cfg(windows)] in src/shell_exec.rs).
|
|
# `petname-macros` is not referenced in code — it's pinned only to constrain
|
|
# the transitive resolution of `petname`'s proc-macro (see the dependency
|
|
# comment above).
|
|
normal = ["which", "petname-macros"]
|
|
|
|
[features]
|
|
# Disabled: Enable testing for shells that require extra installation steps (nushell, powershell, elvish, xonsh, oil)
|
|
# tier-2-integration-tests = []
|
|
# Enable syntax highlighting for bash commands in output (requires tree-sitter)
|
|
# This is optional to avoid C compilation issues on some platforms
|
|
default = ["cli", "syntax-highlighting"]
|
|
# Enables the `wt` binary and everything specific to the CLI: argument parsing,
|
|
# interactive picker, rich terminal rendering, and the markdown help pager.
|
|
# Library consumers (e.g. `worktrunk-sync`) should depend on worktrunk with
|
|
# `default-features = false` to avoid pulling these in.
|
|
cli = [
|
|
"dep:clap",
|
|
"dep:clap_complete",
|
|
"dep:crossterm",
|
|
"dep:humantime",
|
|
"dep:skim",
|
|
"dep:ratatui",
|
|
"dep:ansi-to-tui",
|
|
"dep:tokio",
|
|
"dep:arboard",
|
|
"dep:open",
|
|
"dep:termimad",
|
|
"dep:tracing-log",
|
|
"dep:tracing-subscriber",
|
|
]
|
|
syntax-highlighting = ["dep:tree-sitter", "dep:tree-sitter-bash", "dep:tree-sitter-highlight"]
|
|
# Enable shell/PTY integration tests (requires bash, zsh, fish installed on system)
|
|
# Includes: shell wrapper tests, PTY-based approval prompts, TUI select, progressive rendering
|
|
shell-integration-tests = []
|
|
# Enable benchmarks whose fixture is too large for hosted CI: the rust-scale
|
|
# prune fixture (~15 GiB of rust-lang/rust worktrees, cached under
|
|
# target/wt-perf/bench-repos). The daily benchmarks workflow runs plain `cargo bench`
|
|
# and must never build it; opt in locally with
|
|
# `cargo bench --bench prune --features real-repo-benches`.
|
|
real-repo-benches = []
|
|
# Install git-wt binary so `git wt` works as a git subcommand
|
|
git-wt = []
|
|
|
|
[lib]
|
|
name = "worktrunk"
|
|
path = "src/lib.rs"
|
|
|
|
[[bin]]
|
|
name = "wt"
|
|
path = "src/main.rs"
|
|
required-features = ["cli"]
|
|
|
|
# Also install as git-wt so `git wt <command>` works as a git subcommand.
|
|
# Primarily useful on Windows where `wt` conflicts with Windows Terminal.
|
|
# See: https://github.com/max-sixty/worktrunk/issues/133
|
|
# Install with: cargo install worktrunk --features git-wt
|
|
[[bin]]
|
|
name = "git-wt"
|
|
path = "src/git_wt.rs"
|
|
required-features = ["cli", "git-wt"]
|
|
# `git_wt.rs` is `include!("main.rs")`, so its unit tests are byte-identical
|
|
# duplicates of the `wt` binary's. Running them adds no coverage and breaks
|
|
# under `cargo hack test --feature-powerset`: insta derives snapshot filenames
|
|
# from the crate name, so the duplicates look for `git_wt__*.snap` files that
|
|
# don't exist (only `wt__*`). The `check` powerset still compiles this target.
|
|
test = false
|
|
|
|
# The integration suite execs the `wt` binary (needs `cli`) and snapshots its
|
|
# rendered output, which bakes in syntax highlighting (needs
|
|
# `syntax-highlighting`). Declaring both lets `cargo hack test
|
|
# --feature-powerset` skip this target on combinations that can't satisfy it
|
|
# rather than fail; lib and doc tests still run on every combination.
|
|
[[test]]
|
|
name = "integration"
|
|
path = "tests/integration.rs"
|
|
required-features = ["cli", "syntax-highlighting"]
|
|
|
|
# Deliberately a second binary rather than another module under `integration`:
|
|
# it latches process-wide cancellation state, which would take out any test
|
|
# running beside it in the same process. See the file's module docs.
|
|
[[test]]
|
|
name = "cancel_background"
|
|
path = "tests/cancel_background.rs"
|
|
|
|
[dependencies]
|
|
anyhow = "1.0.102"
|
|
anstream = "1.0"
|
|
anstyle = "1.0.13"
|
|
ansi-str = "0.9"
|
|
color-print = "0.3.7"
|
|
askama = { version = "0.16", default-features = false, features = ["derive", "std"] }
|
|
# Floor is 0.4.31, not 0.4: `DateTime::from_timestamp` (utils.rs, statusline.rs,
|
|
# backup.rs) landed in chrono 0.4.31. `-Z minimal-versions` otherwise resolves
|
|
# 0.4.0 and our own lib fails to compile.
|
|
chrono = "0.4.31"
|
|
# `unstable-markdown` renders our `///` doc-comments as styled help (bold inline
|
|
# code, literal `[experimental]`). skim 0.20 pulled it in transitively; skim 4
|
|
# (default-features = false) does not, so depend on it explicitly to keep help
|
|
# output stable rather than reverting to raw text.
|
|
clap = { version = "4.6", features = ["derive", "unstable-ext", "wrap_help", "unstable-markdown"], optional = true }
|
|
clap_complete = { version = "4.6", features = ["unstable-dynamic"], optional = true }
|
|
crossbeam-channel = "0.5"
|
|
crossterm = { version = "0.29", optional = true }
|
|
indexmap = { version = "2.14", features = ["serde"] }
|
|
etcetera = "0.11"
|
|
# `log` is still used pervasively for application logging; events flow into
|
|
# `tracing` via `tracing-log::LogTracer` (CLI binary only).
|
|
log = "0.4.31"
|
|
# 0.1.30 added the `enabled!` macro `src/trace/emit.rs::Span::drop` uses to
|
|
# elide span emission when the subscriber doesn't care. Older versions only
|
|
# expose the per-level macros and dispatcher checks.
|
|
tracing = "0.1.35"
|
|
# Compatibility shim: forward `log::*` macros into `tracing`. ~100 call sites
|
|
# across the codebase keep using `log::debug!` etc.; only the routing layer
|
|
# moved.
|
|
tracing-log = { version = "0.2.0", optional = true }
|
|
# Layered subscribers do the routing today's `Route` enum used to. `env-filter`
|
|
# replaces env_logger's RUST_LOG parsing; `fmt` provides the writer + format
|
|
# scaffolding; `registry` composes layers. Pinned to ≥ 0.3.17: that's when
|
|
# `EnvFilter::builder().with_default_directive(...).from_env_lossy()` (used in
|
|
# `src/logging.rs`) stabilized on top of the per-layer filtering rework.
|
|
tracing-subscriber = { version = "0.3.17", default-features = false, features = ["ansi", "env-filter", "fmt", "registry"], optional = true }
|
|
# Only enable needed features - saves ~200KB by excluding debug, macros, multi_template
|
|
minijinja = { version = "2.20", default-features = false, features = ["builtins", "serde", "std_collections"] }
|
|
rayon = "1.12"
|
|
serde = { version = "1.0.228", features = ["derive"] }
|
|
serde_json = "1.0.145"
|
|
shell-escape = "0.1"
|
|
shellexpand = "3.1"
|
|
strsim = "0.11.1"
|
|
strum = { version = "0.28", features = ["derive"] }
|
|
synoptic = "2"
|
|
terminal_size = "0.4.3"
|
|
toml = { version = "1.0", features = ["preserve_order"] }
|
|
toml_edit = { version = "0.25", features = ["serde"] }
|
|
# tree-sitter and tree-sitter-highlight should use matching versions.
|
|
# tree-sitter-bash may lag behind (0.25.x works with tree-sitter 0.26.x).
|
|
# These are optional dependencies controlled by the "syntax-highlighting" feature.
|
|
# To build without C compilation requirements, use: cargo install worktrunk --no-default-features --features cli
|
|
tree-sitter = { version = "0.26", optional = true }
|
|
tree-sitter-bash = { version = "0.25.1", optional = true }
|
|
tree-sitter-highlight = { version = "0.26", optional = true }
|
|
unicode-width = "0.2.1"
|
|
wrap-ansi = "0.1"
|
|
osc8 = "0.1.0"
|
|
supports-hyperlinks = "3"
|
|
home = "0.5.12"
|
|
humantime = { version = "2.2", optional = true }
|
|
once_cell = "1.21.4"
|
|
dirs = "6.0"
|
|
normalize-path = "0.2.1"
|
|
path-slash = "0.2"
|
|
pathdiff = "0.2"
|
|
which = "8.0.2"
|
|
# Cross-platform path canonicalization that avoids Windows verbatim paths (\\?\)
|
|
# which external tools like git cannot handle. On Unix, it's a no-op wrapper.
|
|
dunce = "1.0"
|
|
termimad = { version = "0.35.1", optional = true }
|
|
urlencoding = "2.1"
|
|
regex = "1.12.3"
|
|
ignore = "0.4"
|
|
reflink-copy = "0.1"
|
|
# Atomic, no-overwrite rename (renameat2 / renamex_np / MoveFileExW) for
|
|
# `--clobber` backups — closes the check-then-rename TOCTOU. Keeps the FFI
|
|
# (and its `unsafe`) out of this crate, which is `unsafe_code = "forbid"`.
|
|
renamore = "0.3"
|
|
dashmap = "6.2.1"
|
|
fs2 = "0.4.3"
|
|
sanitize-filename = "0.6.0"
|
|
schemars = { version = "1.2.1", features = ["derive"] }
|
|
# SHA-256 is used for persistent cache keys (e.g., summary diff hashes in
|
|
# `.git/wt/cache/summary/`). stdlib's `DefaultHasher` is not guaranteed
|
|
# stable across Rust versions, so we need a deterministic algorithm on disk.
|
|
sha2 = "0.11"
|
|
# Wordlists for the `codename` template filter. PINNED EXACT — bumping this
|
|
# can silently shift every existing user's codename-derived worktree path,
|
|
# orphaning their on-disk worktrees. Coordinate any version change as a
|
|
# breaking-change rollout (see `test_codename_outputs_are_stable` and
|
|
# `src/config/expansion.rs::codename`). We use only the static wordlists,
|
|
# not the random-name generator, so `default-rng` and `clap` stay off.
|
|
petname = { version = "=3.1.0", default-features = false, features = ["default-words"] }
|
|
# PINNED EXACT to match the `petname` pin above. `petname` 3.0.1 declares a
|
|
# caret range (`petname-macros = "3.0.0"` => `^3.0.0`), but petname-macros is
|
|
# SemVer-incompatible with petname across the line in BOTH directions: macros
|
|
# 3.1.0 emits `petnames!`-macro references (`::petname::lang::…`) that newer
|
|
# petname has, but 3.0.x petname doesn't (E0433); macros 3.0.0 is missing the
|
|
# `english` module that petname 3.0.1 re-exports (`petname_macros::english`),
|
|
# so it too fails to compile (E0432). Only an exact match works. The committed
|
|
# lockfile keeps 3.0.1, but the nightly `minimal-versions` job re-resolves with
|
|
# `cargo update` (pulling 3.0.0) and a plain `cargo update` would pull 3.1.0 —
|
|
# both broken. This direct `=3.0.1` requirement unifies the shared resolution
|
|
# to 3.0.1. Bump in lockstep whenever `petname` itself is bumped.
|
|
petname-macros = "=3.1.0"
|
|
|
|
tempfile = "3.27"
|
|
wait-timeout = "0.2"
|
|
|
|
# Interactive picker (`wt switch`) TUI stack, pulled in only by the `cli`
|
|
# feature's `dep:` entries. These are platform-neutral (skim 4.x is the
|
|
# ratatui/crossterm rewrite, which supports Windows), so they live in the main
|
|
# table — not under `[target.'cfg(unix)']` — or the picker fails to compile on
|
|
# Windows.
|
|
#
|
|
# skim 4.x is the ratatui-based rewrite; the 0.20 line used the now-abandoned
|
|
# tuikit backend (we previously vendored a patched skim-tuikit fork). The
|
|
# picker feeds rows as a custom `SkimItem` whose `display()` returns a ratatui
|
|
# `Line`, so `ratatui` + `ansi-to-tui` below convert the picker's pre-rendered
|
|
# ANSI rows into styled lines.
|
|
#
|
|
# `default-features = false` drops skim's own CLI (`clap` / `clap_mangen` / …):
|
|
# worktrunk embeds the picker as a library and never shells out to the `sk`
|
|
# binary. `frizbee` keeps skim's default fuzzy matcher.
|
|
# Floor is 4.8, not 4: the picker is written against 4.8's `ratatui 0.30.1`
|
|
# API, and earlier 4.x compile against an incompatible ratatui (`-Z
|
|
# minimal-versions` otherwise resolves skim 4.6 against ratatui 0.30.1 and fails
|
|
# to build).
|
|
# skim 5.3.1 (bumped in #3526) regressed the picker on the legacy Windows
|
|
# console — every picker PTY test failed with "Keyboard progressive enhancement
|
|
# not implemented for the legacy Windows API", reddening the Windows test job,
|
|
# so the pin held at the last-Windows-green 5.1.0. 5.5.0 guards that path ("Do
|
|
# not panic on push/pop keyboard enhancement flags failure") and is
|
|
# Windows-green again. Kept as an exact pin; it can relax to a range now that
|
|
# the keyboard-enhancement path no longer regresses the legacy console.
|
|
skim = { version = "=5.5.0", optional = true, default-features = false, features = ["frizbee"] }
|
|
# Pinned to skim's ratatui line so the `Line` type `display()` returns is the
|
|
# same one skim consumes. `ansi-to-tui` parses ANSI SGR codes into ratatui spans.
|
|
# Floor is 0.30.1: 0.30.0 is a broken release (its `#[instability::unstable]`
|
|
# attribute fails to compile).
|
|
ratatui = { version = "0.30.1", optional = true }
|
|
ansi-to-tui = { version = "8.0.1", optional = true }
|
|
# Already in the tree via skim (its event loop is tokio-based). The picker drives
|
|
# skim through the public `Skim::init`/`run` API rather than the all-in-one
|
|
# `Skim::run_with` so it can grab `event_sender()` and push `Event::Render` while
|
|
# collect mutates rows in place — skim 4.x renders on demand, not on a heartbeat.
|
|
# `Runtime::new()` needs `rt-multi-thread`; `event_sender()` is a `sync::mpsc`.
|
|
tokio = { version = "1.52.3", optional = true, default-features = false, features = ["rt-multi-thread", "sync"] }
|
|
# Picker row shortcuts: `alt-y` copies the selected branch to the system
|
|
# clipboard, `alt-o` opens its PR/MR URL in the browser. Both are cross-platform
|
|
# (so they sit in the main table alongside the picker stack, not under
|
|
# `[target.'cfg(unix)']`). `arboard` drops `default-features` to skip the `image`
|
|
# crate — the picker only copies text — while keeping `wayland-data-control` so
|
|
# Linux Wayland clipboards work alongside X11 and macOS.
|
|
arboard = { version = "3.4", optional = true, default-features = false, features = ["wayland-data-control"] }
|
|
open = { version = "5.3", optional = true }
|
|
|
|
[target.'cfg(unix)'.dependencies]
|
|
# Unix-only syscall crates: `nix` (process/signal) backs the fsmonitor reap and
|
|
# `signal-hook` forwards Ctrl-C to child process groups. Gating keeps both out of
|
|
# the Windows dependency graph.
|
|
nix = { version = "0.31.3", default-features = false, features = ["process", "signal"] }
|
|
signal-hook = "0.4.1"
|
|
|
|
[build-dependencies]
|
|
vergen-gitcl = { version = "10.0.0", features = ["build"] }
|
|
|
|
[dev-dependencies]
|
|
insta = { version = "1.47.2", features = ["yaml", "redactions", "filters"] }
|
|
insta-cmd = "0.7"
|
|
rstest = "0.26"
|
|
tempfile = "3.27"
|
|
toml = "1.0"
|
|
criterion = "0.8"
|
|
portable-pty = "0.9"
|
|
regex = "1.12.3"
|
|
vt100 = "0.16.2"
|
|
ansi-to-html = "0.2.3"
|
|
wt-perf = { path = "tests/helpers/wt-perf" }
|
|
similar = "3.1.1"
|
|
|
|
[[bench]]
|
|
name = "alias"
|
|
harness = false
|
|
|
|
[[bench]]
|
|
name = "completion"
|
|
harness = false
|
|
|
|
[[bench]]
|
|
name = "list"
|
|
harness = false
|
|
|
|
[[bench]]
|
|
name = "time_to_first_output"
|
|
harness = false
|
|
|
|
[[bench]]
|
|
name = "remove"
|
|
harness = false
|
|
|
|
[[bench]]
|
|
name = "prune"
|
|
harness = false
|
|
|
|
[[bench]]
|
|
name = "picker_preview"
|
|
harness = false
|
|
|
|
[lints.rust]
|
|
unsafe_code = "forbid"
|
|
|
|
# The profile that 'dist' will build with
|
|
[profile.dist]
|
|
inherits = "release"
|
|
lto = "thin"
|