Skip to content

Commit 7d2d170

Browse files
stevenpollackclaude
andcommitted
feat(worktrees): worktree management popup (list, switch, create, remove, lock)
Adds a Worktrees popup (Shift+W, available from any tab) to manage git worktrees without leaving gitui: - list the primary + linked worktrees with branch / path / lock / valid markers (* marks the current one) - switch to the selected worktree (Enter), reusing the existing repo-switch flow - create a worktree at a path (c) with a new branch named after the final path component; missing parent dirs are created - remove a worktree (Shift+D) via a y/n confirmation; refused if it is the current tree, is locked, or has uncommitted / staged / untracked changes - lock / unlock a worktree (l) Backend is git2-native in asyncgit/src/sync/worktree.rs (get_worktrees / create_worktree / remove_worktree / toggle_worktree_lock) with tests; UI in src/popups/{worktrees,create_worktree}.rs. Also fixes a pre-existing bug where the first keystroke after switching worktree/submodule was dropped, by sharing the input reader across app restarts. Squashed from branch feat/worktrees (PR gitui-org#2995). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BMQxBdr5f3B3jmDtdvGtTu
1 parent 685cca9 commit 7d2d170

14 files changed

Lines changed: 1364 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111
* support x509 commit signing [[@kaden-l-nelson](https://github.com/kaden-l-nelson)] ([#2514](https://github.com/gitui-org/gitui/issues/2514))
12+
* worktrees popup: list worktrees and switch between them ([Shift+W])
13+
* worktrees popup: create a new worktree with a new branch ([c] from the worktrees list)
14+
* worktrees popup: remove a worktree ([Shift+D], refused if it is dirty or locked) and lock/unlock it ([l])
1215

1316
### Changed
1417
* use [tombi](https://github.com/tombi-toml/tombi) for all toml file formatting
@@ -17,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1720
* support rewording non-HEAD commits when `commit.gpgsign` is enabled (gpg format only) [[@guerinoni](https://github.com/guerinoni)] ([#2959](https://github.com/gitui-org/gitui/pull/2959))
1821

1922
### Fixes
23+
* first keystroke after switching to a submodule or worktree was dropped
2024
* crash when opening submodule ([#2895](https://github.com/gitui-org/gitui/issues/2895))
2125
* when staging the last file in a directory, the first item after the directory is no longer skipped [[@Tillerino](https://github.com/Tillerino)] ([#2748](https://github.com/gitui-org/gitui/issues/2748))
2226
* index-out-of-bounds panic when unstaging lines near the end of a diff ([#2953](https://github.com/gitui-org/gitui/issues/2953))

CLAUDE.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# gitui — working notes for Claude
2+
3+
gitui is a Rust TUI git client. Cargo **workspace**: root crate `gitui` (the `src/` TUI binary) +
4+
members `asyncgit` (git logic over `git2`/`gix`), `filetreelist`, `git2-hooks`, `git2-testing`,
5+
`scopetime`. MSRV **1.88** (`Cargo.toml` `rust-version`, `.clippy.toml` `msrv`, CI matrix — keep
6+
all three in sync if ever bumped).
7+
8+
## Feedback loop (run these — the reviewer here does not read Rust, so tooling IS the review)
9+
10+
- **Fast inner loop while editing:** `cargo check -p asyncgit` (backend) / `cargo check` (whole
11+
workspace). Never iterate with `--release` — LTO + `opt-level="z"` makes it very slow.
12+
- **Format before every check:** `cargo fmt`. Formatting is unusual (`rustfmt.toml`:
13+
`max_width = 70`, `hard_tabs = true`) — hand-written code almost never matches, and
14+
`cargo fmt -- --check` is a CI gate.
15+
- **Full gate before declaring done / committing:** `make check`
16+
= `fmt` (`cargo fmt -- --check`) + `clippy` (`cargo clippy --workspace --all-features`) +
17+
`test` (`cargo nextest run --workspace`) + `sort` (`tombi format --check`) + `deny`
18+
(`cargo deny check`). CI calls the same `make` targets, so green locally ≈ green CI.
19+
- **Subset fallback** if a tool is missing: `cargo fmt -- --check` +
20+
`cargo clippy --workspace --all-features` + `cargo test --workspace` (plain `cargo test` works;
21+
it just also runs doctests that nextest skips).
22+
- **Run a single crate's tests:** `cargo test -p asyncgit <name_filter>`.
23+
24+
Required tooling: `cargo-nextest`, `cargo-deny`, `tombi` (installed via mise), plus `python`,
25+
`gpgsm`/`gpg`, `openssl`, `perl`, a C compiler (vendored OpenSSL + bundled libgit2 build from
26+
source on first compile — the first build is slow, later ones are cached).
27+
28+
## Hard rules clippy enforces (these fail the build, not just warn)
29+
30+
Crate roots carry `#![deny(clippy::all, perf, nursery, pedantic, cargo)]` plus bans below.
31+
- **No `unwrap` / `expect` / `panic!` in production code** — use `?`, `ok_or(...)`,
32+
`unwrap_or_default()`, `map_or(...)`. (`unwrap`/`expect` are fine in `#[cfg(test)]` code.)
33+
- `asyncgit` has `#![forbid(missing_docs)]`**every `pub` item needs a `///` doc comment**.
34+
- `#![forbid(unsafe_code)]` in the binary crate.
35+
36+
## Other gotchas
37+
38+
- **UI output is snapshot-tested with `insta`** (`src/snapshots/*.snap`). If you change rendered
39+
output, tests fail with a diff — review and accept via `cargo insta review` / `cargo insta
40+
accept`, then commit the updated `.snap` files. (Install once: `cargo install cargo-insta`.)
41+
- **CHANGELOG.md** must gain an entry under `## Unreleased` (`Added`/`Changed`/`Fixes`) or the CI
42+
`log-test` job fails. Format: Keep-a-Changelog, with issue/PR links.
43+
- Only the `stable` and `1.88` CI rows gate (nightly is `continue-on-error`). `cargo +nightly
44+
udeps` runs in CI but not in `make check`, so **don't add unused dependencies**.
45+
- Production code never shells out to the `git` binary; use `git2`/`gix`. The only
46+
`Command::new("git")` calls live in test helpers (`debug_cmd_print` in `asyncgit/src/sync/mod.rs`).
47+
48+
## Where things live (patterns to copy)
49+
50+
- Sync git ops: `asyncgit/src/sync/<feature>.rs`, re-exported from `asyncgit/src/sync/mod.rs`. Each
51+
op: `pub fn f(repo_path: &RepoPath, ...) -> Result<T> { scope_time!("f"); let repo =
52+
repo(repo_path)?; ... }`. Test helpers (`repo_init`, `debug_cmd_print`) in `sync/mod.rs::tests`.
53+
- Popups (Branches/Submodules/etc.): `src/popups/<name>.rs`, registered in `src/popups/mod.rs` and
54+
the `setup_popups!` macro in `src/app.rs`; opened via an `InternalEvent` in `src/queue.rs`.
55+
- Components implement `Component` + `DrawableComponent` (`src/components/mod.rs`). Non-commit list
56+
views use `VerticalScroll` + `ui::scrolllist::draw_list` (see `src/popups/submodules.rs`).
57+
- Keybindings: `src/keys/key_list.rs` (`KeysList` struct + `Default`). Command/help strings:
58+
`src/strings.rs`.

asyncgit/src/sync/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ mod submodules;
3434
mod tags;
3535
mod tree;
3636
pub mod utils;
37+
mod worktree;
3738

3839
pub use blame::{blame_file, BlameHunk, FileBlame};
3940
pub use branch::{
@@ -108,6 +109,10 @@ pub use utils::{
108109
get_head, get_head_tuple, repo_dir, repo_open_error,
109110
stage_add_all, stage_add_file, stage_addremoved, Head,
110111
};
112+
pub use worktree::{
113+
create_worktree, get_worktrees, remove_worktree,
114+
toggle_worktree_lock, WorktreeInfo,
115+
};
111116

112117
pub use git2::ResetType;
113118

0 commit comments

Comments
 (0)