Conversation
Introduce a layered configuration system loaded from: 1. ./.terris.toml (project-local, highest priority) 2. ~/.terris/terris.toml (user-global, fallback) New config keys and their defaults: [worktrees] base_dir = "~/.terris-worktrees" # supports ~ expansion use_random_suffix = true suffix_length = 8 [behavior] on_missing_branch = "error" # comma-separated: error | fetch | create auto_prune = false [display] show_head = false The `on_missing_branch` field is the most significant addition: it controls what happens when the requested branch does not exist locally. Setting it to "fetch, create" will first run `git fetch origin <branch>` over the network, use the remote tracking ref if found, and fall back to creating a fresh local branch from HEAD if it is still not found. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds TOML-based configuration support to terris, allowing behavior and output to be customized via config files instead of only CLI defaults.
Changes:
- Introduces TOML config structs + loader and threads config through
cmd_list/cmd_ensure_branch. - Adds
on_missing_branchstrategy (fetch/create) and optionalauto_prune+show_headdisplay toggle. - Adds
serde+tomldependencies and expands tests for new config behavior.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/main.rs |
Adds config model/loading, new branch-missing behavior, auto-prune option, and optional HEAD column in listing + tests. |
Cargo.toml |
Adds serde (derive) and toml dependencies required for config parsing. |
Cargo.lock |
Locks transitive dependencies introduced by serde/toml. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| fn load_config() -> Result<Config> { | ||
| for path in config_file_candidates()? { | ||
| if path.exists() { | ||
| let content = std::fs::read_to_string(&path) | ||
| .with_context(|| format!("read config '{}'", path.display()))?; | ||
| let config: Config = toml::from_str(&content) | ||
| .with_context(|| format!("parse config '{}'", path.display()))?; | ||
| return Ok(config); | ||
| } |
There was a problem hiding this comment.
load_config() currently returns the first config file it finds, so if both global and project-local configs exist the global one is ignored entirely. This doesn't implement the layered/override behavior described in the PR (global fallback with local taking precedence per-key). Consider loading both (when present) and merging them (e.g., merge toml::Value tables with local overriding global) before deserializing into Config.
| for part in s.split(',') { | ||
| match part.trim() { | ||
| "error" | "" => {} // explicit "error" or empty token → no-op (default) | ||
| "fetch" => strategy.fetch = true, | ||
| "create" => strategy.create = true, | ||
| other => { | ||
| return Err(serde::de::Error::custom(format!( | ||
| "unknown on_missing_branch value '{other}'; valid values: error, fetch, create" | ||
| ))); | ||
| } | ||
| } |
There was a problem hiding this comment.
The on_missing_branch parsing treats the token error as a no-op and ignores the configured order entirely (it only sets boolean flags). This means values like "error, fetch" will still fetch, and "create, fetch" behaves the same as "fetch, create", which conflicts with the docs/PR description that actions are tried in order. Consider deserializing into an ordered list/enum of actions and executing them sequentially, and/or rejecting combinations that include error with other actions.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Issue: load_config() returned the first config file found, so global and project configs were never layered. Also, project-local config lookup depended on current working directory instead of repository root. Fix: load both files when present, merge TOML tables with project values overriding global values, and resolve project-local .terris.toml from git root (with cwd fallback outside repos). Added tests covering merge behavior and candidate path ordering.
Issue: on_missing_branch was parsed into booleans, so action order was ignored and combinations like 'error, fetch' behaved unexpectedly. In addition, any fetch failure was treated as 'branch not found', which could silently fall through to branch creation. Fix: parse on_missing_branch into an ordered action list, reject invalid combinations/duplicates, execute actions sequentially, and only treat explicit missing-remote-ref errors as 'not found' while surfacing other fetch failures. Updated parsing tests accordingly.
Issue: suffix_length accepted invalid values (e.g. 0 or excessively large), which could generate malformed or unreasonable worktree paths. Fix: added explicit suffix-length validation (1..=64), invoked validation during config loading and path construction, and added a unit test that asserts invalid values produce a clear configuration error.
Issue: multiple tests changed HOME concurrently, creating race conditions and intermittent failures when tests ran with default parallelism. Fix: added a shared test-only mutex in EnvGuard so HOME updates are serialized across tests while still restoring prior environment values on drop.
Introduce a layered configuration system loaded from:
New config keys and their defaults:
[worktrees]
base_dir = "~/.terris-worktrees" # supports ~ expansion
use_random_suffix = true
suffix_length = 8
[behavior]
on_missing_branch = "error" # comma-separated: error | fetch | create
auto_prune = false
[display]
show_head = false
The
on_missing_branchfield is the most significant addition: it controls what happens when the requested branch does not exist locally. Setting it to "fetch, create" will first rungit fetch origin <branch>over the network, use the remote tracking ref if found, and fall back to creating a fresh local branch from HEAD if it is still not found.