Skip to content

Support multiple repos directories#6

Open
brendanwhit wants to merge 12 commits into
tomplex:mainfrom
brendanwhit:multiple-repos-directories
Open

Support multiple repos directories#6
brendanwhit wants to merge 12 commits into
tomplex:mainfrom
brendanwhit:multiple-repos-directories

Conversation

@brendanwhit
Copy link
Copy Markdown

Summary

Today trellis's `repos_dir` setting is a single path. This PR lets it be a list:

  • Replaces the single-string config with a newline-separated list under the same DB key. Existing single-path configs migrate transparently — they parse as a single-entry list.
  • `Manager::repos_dirs()` returns `Vec`. `scan_existing` iterates over every configured dir to discover repos and worktrees.
  • The new-session wizard's `PickRepo` step combines repos across all dirs. When two dirs contain a same-named repo, the label disambiguates with the parent dir name (e.g. `myproj (work)` vs `myproj (personal)`).
  • Settings UI adds multi-line support for the `repos_dir` field: Up/Down moves between lines, Alt+Enter inserts a new line, plain Enter still saves. The underlying input helper is generalized to `input_handle_key_ex` with an explicit multiline flag.

Dependencies

Built on top of #4 (resurrection) — the conflict was textual (overlapping changes in `manager.rs`), not semantic. Can be rebased onto main if #4 is held.

Test plan

  • `cargo test` — green
  • Manual: configured two repo dirs in settings, new-session wizard surfaces repos from both with disambiguated labels

brendanwhit and others added 12 commits June 3, 2026 07:13
Phase B tests need real git repos in temp dirs. tempfile is the
standard Rust crate for that pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
prune_worktrees clears stale admin entries when a worktree dir has
been deleted manually. add_worktree_existing_branch attaches a worktree
to a branch that already exists locally (unlike create_worktree which
always uses -b and errors on existing branches).

Both helpers are needed by the upcoming find_or_create_worktree logic
which must recover from prunable worktrees without re-creating the
branch ref.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Free function resolve_or_create_worktree does the work; Manager method
threads in repo metadata and worktrees_dir. Consults git worktree list
as source of truth; reuses recorded paths even when outside the
trellis-convention layout; recovers from prunable worktrees by reusing
the existing branch ref. start_point is required so each caller (n, t,
R) declares its base branch explicitly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extracts schema-application from init_db into db::init_schema so tests
can build an in-memory connection. Adds Manager::find_or_create_session
which routes by sanitized session name: returns existing if the tmux
session is alive, drops stale DB rows before inserting fresh.

Tests use process-unique branch names and a Drop guard so the tmux
sessions created during testing get cleaned up even on panic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an early by-branch lookup that routes to an existing live session
for (repo, base_branch) regardless of user-typed name, matching the
spec's "route to where it lives" semantic. Dead DB rows (tmux session
gone) are dropped before any fresh insert.

Worktree creation is delegated to find_or_create_worktree with
repo.default_branch as the explicit start_point (preserving existing
behavior).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the inline git::create_worktree call with the unified helper,
so a tab for a branch that already has a worktree no longer errors —
the new tmux window attaches to the existing worktree path.

session.base_branch is passed as the explicit start_point, preserving
the original semantic where new branches in a tab are rooted at the
session's working branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reduces the PR-review flow to PR-number resolution + the two unified
helpers. Threads origin/<branch> as the explicit start_point so the
PR's actual HEAD commits are used when creating the local branch — the
load-bearing behavior the helper extraction must preserve.

70 lines down to ~12; same observable semantic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Path construction now lives inside resolve_or_create_worktree; all
former callers (create_session, add_tab, checkout_and_review) route
through find_or_create_worktree, leaving the public method without
callers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A DB session row whose tmux session no longer exists (e.g. after
reboot, or because scan_existing discovered worktrees on disk that
were never tied to live tmux state) used to silently no-op when the
user pressed Enter — switch_client errored on a nonexistent target and
the .ok() at main.rs:41 discarded the failure.

Add Manager::resurrect_session(id), which calls apply_layout using the
worktree path recorded in the DB (falling back to the repo root for
default-branch sessions). Session list's Enter handler now invokes it
when the highlighted row is managed && !live, surfacing any failure
through the inline status message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The user hit this on 2026-05-19: pressing Enter on a dead session
brought up a tmux session at the repo root rather than the worktree.
Root cause: scan_existing inserts worktree rows with session_id=NULL
and only links them to sessions via adopt_session_worktrees, which
requires a live tmux session to exist at scan time. After reboot, no
live sessions → no links → get_worktrees_for_session returned empty
→ resurrect fell back to repo.path.

Now resurrect_session resolves the working directory in four stages:
DB row linked by session_id, DB row matched by (repo_id, branch) even
if session_id is NULL, git worktree list as the authoritative
fallback, and finally repo root.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
apply_layout used to call tmux::rename_window(session, 1, "claude") to
rename the session's initial window, hardcoding index 1. tmux's default
base-index is 0, so this silently failed: the first window kept its
default name (zsh, the user's login shell), the send_keys to a target
named "claude" had no destination, claude never started in the first
window, and the session ended up with [zsh, shell] instead of the
intended [claude, shell].

Fix: pass `-n claude` to `tmux new-session` so the first window is
named at creation time. send_keys then targets the named window
directly, claude actually runs, and the focus call uses the new
select_window_by_name helper instead of a hardcoded index.

Threads an Option<&str> initial_window through tmux::new_session for
all callers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the single-string repos_dir config with a list-of-paths
config that still lives under the same DB key (newline-separated).
Existing single-path configs migrate transparently — they parse as a
single-entry list.

Manager::repos_dirs() returns Vec<PathBuf>. scan_existing iterates
over every configured dir to discover repos and worktrees. The
new-session wizard's PickRepo step combines repos across all dirs;
when two dirs contain a same-named repo, the label disambiguates with
the parent dir name (e.g. "myproj (work)" vs "myproj (personal)").

Settings UI adds multi-line support for the repos_dir field: render
across multiple rows, Up/Down to move between lines, Alt+Enter to
insert a new line, plain Enter still saves. Refactored input_handle_key
into input_handle_key_ex with an explicit multiline flag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant