Skip to content

feat(workspace): implement worktree.rs (T-067)#67

Merged
mpiton merged 6 commits intomainfrom
feat/t-067-worktree-rs
Mar 29, 2026
Merged

feat(workspace): implement worktree.rs (T-067)#67
mpiton merged 6 commits intomainfrom
feat/t-067-worktree-rs

Conversation

@mpiton
Copy link
Copy Markdown
Owner

@mpiton mpiton commented Mar 29, 2026

Summary

  • Implement worktree.rs module with async git worktree operations for PR workspaces
  • create_worktree: fetches remote branch then creates worktree at ~/.prism/workspaces/{repo}/worktrees/pr-{number}/
  • remove_worktree: forceful worktree removal via git worktree remove --force
  • list_worktrees: parses git worktree list --porcelain output, excludes main tree
  • worktree_exists + build_worktree_path: path utilities
  • Added process feature to tokio for tokio::process::Command

Test plan

  • test_create_worktree_success — creates worktree, verifies path and .git file
  • test_create_worktree_branch_not_found — non-existent branch returns AppError::Git
  • test_create_worktree_already_exists — duplicate creation returns AppError::Workspace
  • test_remove_worktree — creates then removes, verifies directory gone
  • test_remove_worktree_not_found — non-existent path returns AppError::Git
  • test_list_worktrees — empty list then one worktree after creation
  • test_worktree_path_construction — path format validation
  • All 252 existing tests still pass (0 regressions)
  • cargo clippy -- -D warnings clean

Summary by cubic

Implements async Git worktree management for PR workspaces with create/remove/list under ~/.prism/workspaces/{repo}/worktrees/pr-{number}/. Adds NUL-delimited parsing and stricter repo name validation to meet T-067.

  • New Features

    • Added workspace::worktree with: create_worktree, remove_worktree, list_worktrees, worktree_exists, build_worktree_path.
    • create_worktree fetches origin/{branch} (guards flag-like names with --) and creates the worktree; list_worktrees parses --porcelain -z, skips the main tree, and returns Vec<PathBuf>.
  • Bug Fixes

    • 120s timeout on git with kill_on_drop to stop hung processes.
    • Safer paths: repo name validation rejects traversal (.., .), slashes (/, \), and trailing-slash aliasing; default_base_dir now returns Result.
    • Git args now use OsString to pass non‑UTF‑8 paths verbatim.
    • Async I/O via tokio::fs; enabled tokio features process and fs.

Written for commit 85a009e. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Git worktree management: create, remove, and list isolated per‑PR worktrees with strict input validation and robust error handling; worktree APIs are now exported for use.
  • Chores

    • Expanded runtime capabilities to improve subprocess and filesystem handling.
  • Tests

    • Added comprehensive tests covering worktree creation, removal, listing, validation, and error scenarios.

…st (T-067)

Async functions using tokio::process::Command:
- create_worktree: fetch origin + worktree add with conflict detection
- remove_worktree: forceful worktree removal
- list_worktrees: parse porcelain output, exclude main tree
- worktree_exists: simple path check
- build_worktree_path: path construction helper

Worktree dir: ~/.prism/workspaces/{repo}/worktrees/pr-{number}/
7 tests covering success, error, and edge cases.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 29, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new public worktree submodule implementing async Git worktree management (create, remove, list, exists) with deterministic path helpers and tests; also enables tokio process and fs features for subprocess and async filesystem support.

Changes

Cohort / File(s) Summary
Dependency Configuration
src-tauri/Cargo.toml
Enabled process and fs features on the tokio dependency (now rt, time, process, fs).
Workspace Module Surface
src-tauri/src/workspace/mod.rs
Exported the new worktree submodule via pub mod worktree;.
Worktree Implementation & Tests
src-tauri/src/workspace/worktree.rs
New module: adds GIT_TIMEOUT, run_git() (timeout + error mapping), default_base_dir(), build_worktree_path() (strict validation), create_worktree, remove_worktree, list_worktrees, worktree_exists, plus comprehensive async tests exercising creation, removal, listing, and error cases.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(200,200,255,0.5)
    participant Client
    end
    rect rgba(200,255,200,0.5)
    participant Worktree
    end
    rect rgba(255,200,200,0.5)
    participant Git
    end
    rect rgba(255,255,200,0.5)
    participant FS
    end

    Client->>Worktree: create_worktree(repo_local_path, branch, pr_number, repo_name, base_dir)
    Worktree->>FS: build_worktree_path() & check existence
    alt path exists
        Worktree-->>Client: Err(AppError::Workspace::AlreadyExists)
    else
        Worktree->>FS: create parent directories (tokio::fs)
        Worktree->>Git: run_git("fetch", "origin", branch)
        Git-->>Worktree: stdout / Err(AppError::Git)
        Worktree->>Git: run_git("worktree", "add", path, "origin/branch")
        Git-->>Worktree: stdout / Err(AppError::Git)
        Worktree-->>Client: Ok(created PathBuf)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I fetched the branches, dug a little den,

built neat paths for PRs with a twitch and then,
added, listed, removed with a hop and a cheer,
worktrees snug and ready — spring is near!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically identifies the main change: implementing a new worktree.rs module with async Git worktree management functionality for the workspace feature.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/t-067-worktree-rs

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 29, 2026

Greptile Summary

This PR implements the workspace::worktree module (T-067), providing async git worktree management for per-PR review workspaces under ~/.prism/workspaces/{repo}/worktrees/pr-{number}/. It introduces create_worktree, remove_worktree, list_worktrees, worktree_exists, and build_worktree_path, all wired into the existing AppError taxonomy.

All four concerns raised in the previous review pass have been addressed:

  • Blocking I/O: std::fs::create_dir_all replaced with tokio::fs::create_dir_all.
  • Panic on missing HOME: default_base_dir now returns Result<PathBuf, AppError>.
  • Path traversal via repo_name: Strict single-component validation rejects .., ., /, \\, and absolute paths.
  • Vec<String> return type: list_worktrees now returns Vec<PathBuf>.

Notable implementation details:

  • run_git wraps every git call with a 120 s tokio::time::timeout and kill_on_drop(true), preventing hung processes from blocking the executor.
  • OsString arguments are used throughout so non-UTF-8 paths are passed verbatim to git.
  • git fetch origin -- <branch> uses -- to guard against flag-like branch names.
  • git worktree list --porcelain -z uses NUL-delimited output to correctly handle paths containing newlines.
  • 7 targeted async/sync tests cover the success paths, error cases, and path validation.

Confidence Score: 5/5

Safe to merge — all previous P1 concerns are resolved and only a minor diagnostic P2 remains.

All four issues from the prior review round (blocking I/O, panic risk, path traversal, wrong return type) are fully addressed. The single remaining finding is a P2 cosmetic improvement to timeout error messages. No correctness, security, or data-integrity issues remain.

No files require special attention.

Important Files Changed

Filename Overview
src-tauri/src/workspace/worktree.rs Core new module — all four previous review concerns addressed (tokio::fs async I/O, Result-returning default_base_dir, repo_name path-traversal validation, Vec return type). Minor diagnostic gap in run_git's timeout label.
src-tauri/src/workspace/mod.rs Single-line change exporting the new worktree submodule. No issues.
src-tauri/Cargo.toml Adds process and fs tokio feature flags; both are required by the new module.
src-tauri/Cargo.lock Lock file update adds signal-hook-registry as a transitive dependency of the new tokio process feature; expected and correct.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant create_worktree
    participant build_worktree_path
    participant tokio_fs
    participant run_git

    Caller->>create_worktree: (repo_path, branch, pr_number, repo_name, base_dir)
    create_worktree->>build_worktree_path: validate repo_name, construct path
    build_worktree_path-->>create_worktree: Ok(wt_path) or Err(Workspace)
    create_worktree->>create_worktree: wt_path.exists()? → Err(Workspace) if true
    create_worktree->>tokio_fs: create_dir_all(wt_path.parent())
    tokio_fs-->>create_worktree: Ok(()) or Err(Workspace)
    create_worktree->>run_git: git fetch origin -- branch
    run_git-->>create_worktree: Ok(stdout) or Err(Git)
    create_worktree->>run_git: git worktree add wt_path origin/branch
    run_git-->>create_worktree: Ok(stdout) or Err(Git)
    create_worktree-->>Caller: Ok(wt_path)
Loading

Fix All in Claude Code

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src-tauri/src/workspace/worktree.rs
Line: 61-64

Comment:
**Timeout error message omits the git subcommand**

`cmd_label` is set from `args.first()`, which for all `git worktree *` calls is `"worktree"`. This means a timeout on `git worktree list` produces the message `"git worktree timed out after 120s"` instead of `"git worktree list timed out after 120s"`, making it harder to diagnose which operation hung when multiple worktree commands are in-flight.

The failure-path message already exposes the full args via `args_display` — making the timeout path consistent would close the gap:

```suggestion
    let cmd_label = args
        .iter()
        .take(2)
        .map(|s| s.to_string_lossy().into_owned())
        .collect::<Vec<_>>()
        .join(" ");
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (6): Last reviewed commit: "fix(workspace): explicit slash check in ..." | Re-trigger Greptile

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
src-tauri/src/workspace/worktree.rs (4)

77-82: Blocking std::fs::create_dir_all in async context.

std::fs::create_dir_all is a blocking call that could briefly block the async runtime. For directory creation, the impact is typically negligible, but for consistency with the async pattern, consider using tokio::fs::create_dir_all.

♻️ Proposed async alternative
     // Ensure parent directories exist
     if let Some(parent) = wt_path.parent() {
-        std::fs::create_dir_all(parent).map_err(|e| {
+        tokio::fs::create_dir_all(parent).await.map_err(|e| {
             AppError::Workspace(format!("failed to create worktree directory: {e}"))
         })?;
     }

This requires adding "fs" to tokio features in Cargo.toml.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/workspace/worktree.rs` around lines 77 - 82, Replace the
blocking std::fs::create_dir_all call with the async tokio::fs::create_dir_all
and await it: where you currently call
std::fs::create_dir_all(parent).map_err(...)? change to
tokio::fs::create_dir_all(parent).await.map_err(|e|
AppError::Workspace(format!("failed to create worktree directory: {e}")));
ensure the containing function is async (or spawn a blocking task if async
conversion is undesirable) and add the "fs" feature to the tokio dependency in
Cargo.toml so tokio::fs is available.

28-52: Consider adding a timeout for git operations.

The run_git helper has no timeout, which could cause operations like git fetch to hang indefinitely on network issues. This could block async tasks and degrade user experience.

⏱️ Proposed timeout implementation
+use std::time::Duration;
+use tokio::time::timeout;
+
+const GIT_TIMEOUT: Duration = Duration::from_secs(120);
+
 async fn run_git(args: &[&str], cwd: &Path) -> Result<String, AppError> {
-    let output = Command::new("git")
+    let output = timeout(GIT_TIMEOUT, Command::new("git")
         .args(args)
         .current_dir(cwd)
-        .output()
-        .await
+        .output())
+        .await
+        .map_err(|_| AppError::Git(format!(
+            "git {} timed out after {}s",
+            args.first().unwrap_or(&""),
+            GIT_TIMEOUT.as_secs()
+        )))?
         .map_err(|e| {
             AppError::Git(format!(
                 "failed to run git {}: {e}",
                 args.first().unwrap_or(&"")
             ))
         })?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/workspace/worktree.rs` around lines 28 - 52, The run_git helper
needs a timeout to avoid hanging; replace the direct Command::output().await
with spawning the child (Command::spawn), then await child.wait_with_output()
wrapped in tokio::time::timeout(Duration::from_secs(<N>), ...); if timeout
occurs, call child.kill().await (handle errors) and return an AppError::Git
indicating a git operation timeout; preserve the existing error mapping for
spawn failures and preserve the current handling of non-success exit status and
stdout/stderr logic in run_git.

17-26: Consider sanitizing repo_name to prevent path traversal.

If repo_name contains .. or /, it could cause the worktree to be created outside the intended directory structure. While the input likely comes from GitHub API data, defensive validation would prevent potential misuse.

🛡️ Proposed sanitization
 pub fn build_worktree_path(base_dir: &Path, repo_name: &str, pr_number: u32) -> PathBuf {
+    // Basic sanitization: reject path separators and traversal
+    debug_assert!(
+        !repo_name.contains('/') && !repo_name.contains('\\') && !repo_name.contains(".."),
+        "repo_name must not contain path separators or traversal sequences"
+    );
     base_dir
         .join(repo_name)
         .join("worktrees")
         .join(format!("pr-{pr_number}"))
 }

Alternatively, strip or replace problematic characters at runtime if the input source is not fully trusted.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/workspace/worktree.rs` around lines 17 - 26,
build_worktree_path currently joins repo_name directly which allows path
traversal (e.g., repo_name with ".." or "/" )—sanitize repo_name first by
normalizing or rejecting unsafe input: remove or replace path-separators and
dot-only segments (e.g., replace '/' and '\' with '_' and strip leading/trailing
'.' or collapse sequences of ".."), or validate against a whitelist
(alphanumeric, '-', '_', '.') and return an error for invalid names; then use
the sanitized_repo_name when building the PathBuf in build_worktree_path so
worktrees cannot be created outside the intended directory.

7-15: Wrap PRism in backticks for clippy's doc_markdown lint.

The doc comment should use backticks around PRism per project conventions.

📝 Proposed fix
-/// Returns the default base directory for `PRism` workspaces: `~/.prism/workspaces`.
+/// Returns the default base directory for [`PRism`] workspaces: `~/.prism/workspaces`.

Based on learnings: "For Rust doc comments, the name should be backtick-wrapped as PRism. This is required for clippy's doc_markdown lint."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/workspace/worktree.rs` around lines 7 - 15, Update the doc
comment for the function default_base_dir to wrap PRism in backticks (i.e.,
change "PRism" to "`PRism`") so it satisfies clippy's doc_markdown lint; the
comment immediately above the pub fn default_base_dir() is the location to edit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src-tauri/src/workspace/worktree.rs`:
- Around line 77-82: Replace the blocking std::fs::create_dir_all call with the
async tokio::fs::create_dir_all and await it: where you currently call
std::fs::create_dir_all(parent).map_err(...)? change to
tokio::fs::create_dir_all(parent).await.map_err(|e|
AppError::Workspace(format!("failed to create worktree directory: {e}")));
ensure the containing function is async (or spawn a blocking task if async
conversion is undesirable) and add the "fs" feature to the tokio dependency in
Cargo.toml so tokio::fs is available.
- Around line 28-52: The run_git helper needs a timeout to avoid hanging;
replace the direct Command::output().await with spawning the child
(Command::spawn), then await child.wait_with_output() wrapped in
tokio::time::timeout(Duration::from_secs(<N>), ...); if timeout occurs, call
child.kill().await (handle errors) and return an AppError::Git indicating a git
operation timeout; preserve the existing error mapping for spawn failures and
preserve the current handling of non-success exit status and stdout/stderr logic
in run_git.
- Around line 17-26: build_worktree_path currently joins repo_name directly
which allows path traversal (e.g., repo_name with ".." or "/" )—sanitize
repo_name first by normalizing or rejecting unsafe input: remove or replace
path-separators and dot-only segments (e.g., replace '/' and '\' with '_' and
strip leading/trailing '.' or collapse sequences of ".."), or validate against a
whitelist (alphanumeric, '-', '_', '.') and return an error for invalid names;
then use the sanitized_repo_name when building the PathBuf in
build_worktree_path so worktrees cannot be created outside the intended
directory.
- Around line 7-15: Update the doc comment for the function default_base_dir to
wrap PRism in backticks (i.e., change "PRism" to "`PRism`") so it satisfies
clippy's doc_markdown lint; the comment immediately above the pub fn
default_base_dir() is the location to edit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b677243b-2b7f-41bb-9faf-425655173720

📥 Commits

Reviewing files that changed from the base of the PR and between 1c22d65 and d279fdd.

⛔ Files ignored due to path filters (1)
  • src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • src-tauri/Cargo.toml
  • src-tauri/src/workspace/mod.rs
  • src-tauri/src/workspace/worktree.rs

Comment thread src-tauri/src/workspace/worktree.rs
Comment thread src-tauri/src/workspace/worktree.rs Outdated
Comment thread src-tauri/src/workspace/worktree.rs Outdated
Comment thread src-tauri/src/workspace/worktree.rs
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

…, Result types

- run_git: add 120s timeout via tokio::time::timeout
- build_worktree_path: validate repo_name against path traversal (/, \, ..)
- default_base_dir: return Result instead of panicking on missing HOME
- create_dir_all: use tokio::fs for async consistency
- list_worktrees: return Vec<PathBuf> instead of Vec<String>
- Add tokio "fs" feature
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src-tauri/src/workspace/worktree.rs`:
- Around line 116-119: The call to run_git currently passes a UTF-8 lossy string
from wt_path.to_string_lossy(), corrupting non‑UTF8 paths; change run_git to
accept OS-string arguments (e.g., &[impl AsRef<OsStr>] or &[OsString]) instead
of &str and update its use of Command::args to forward those OsStrs directly,
then replace the callers (the worktree add invocation using wt_path and the
other call at the second location) to pass the Path/OsStr (e.g.,
wt_path.as_os_str() or the PathBuf itself) rather than wt_str so git receives
the exact OS bytes.
- Around line 35-42: The current repo_name validation uses string checks and
still allows values like "." or "C:" which are not a single normal path
component; change the validation to parse Path::new(&repo_name).components() and
require that it yields exactly one component and that component matches
Component::Normal(_), returning the same AppError::Workspace message on failure;
update the logic that currently checks repo_name.is_empty() ||
repo_name.contains('/')... to use this components-based check before calling
base_dir.join(repo_name) so only a single normal component is accepted.
- Around line 57-60: The timeout currently wraps Command::output(), which only
cancels waiting while leaving the spawned git child running; instead, spawn the
process, call child.kill_on_drop(true) on the returned Child, then wrap
child.wait_with_output() in the timeout so a timed-out operation will also
terminate the git process. Concretely, replace Command::new(...).output() with
Command::new(...).spawn(), call child.kill_on_drop(true) on the spawned Child,
and then use timeout(GIT_TIMEOUT, child.wait_with_output()) to obtain the
output.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bd36a206-27cd-4b33-bcdc-67173aca1941

📥 Commits

Reviewing files that changed from the base of the PR and between d279fdd and 8148c5d.

📒 Files selected for processing (2)
  • src-tauri/Cargo.toml
  • src-tauri/src/workspace/worktree.rs
✅ Files skipped from review due to trivial changes (1)
  • src-tauri/Cargo.toml

Comment thread src-tauri/src/workspace/worktree.rs Outdated
Comment thread src-tauri/src/workspace/worktree.rs Outdated
Comment on lines +57 to +60
let output = timeout(
GIT_TIMEOUT,
Command::new("git").args(args).current_dir(cwd).output(),
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate the file and check if it exists
find . -name "worktree.rs" -type f

Repository: mpiton/prism

Length of output: 93


🏁 Script executed:

# Read the specific lines mentioned in the review
cat -n src-tauri/src/workspace/worktree.rs | sed -n '50,70p'

Repository: mpiton/prism

Length of output: 895


🏁 Script executed:

# Check the entire file to understand context and search for timeout/Command patterns
wc -l src-tauri/src/workspace/worktree.rs

Repository: mpiton/prism

Length of output: 95


🏁 Script executed:

# Search for kill_on_drop in the file
grep -n "kill_on_drop" src-tauri/src/workspace/worktree.rs

Repository: mpiton/prism

Length of output: 38


🏁 Script executed:

# Check the entire run_git function to see full context
cat -n src-tauri/src/workspace/worktree.rs | sed -n '51,80p'

Repository: mpiton/prism

Length of output: 1231


🏁 Script executed:

# Look for other similar git command patterns in the file
grep -n "Command::new" src-tauri/src/workspace/worktree.rs

Repository: mpiton/prism

Length of output: 171


🏁 Script executed:

# Check Cargo.toml for tokio version
find . -name "Cargo.toml" -type f -exec grep -l "tokio" {} \; | head -5

Repository: mpiton/prism

Length of output: 78


🏁 Script executed:

# Check tokio version in the Cargo.toml
grep -A 2 "^tokio" ./src-tauri/Cargo.toml

Repository: mpiton/prism

Length of output: 293


🏁 Script executed:

# Also check the line 181 context to see if there's a similar pattern
cat -n src-tauri/src/workspace/worktree.rs | sed -n '175,190p'

Repository: mpiton/prism

Length of output: 638


🏁 Script executed:

# Search for timeout usage in the entire file to see the pattern
grep -n "timeout" src-tauri/src/workspace/worktree.rs

Repository: mpiton/prism

Length of output: 179


🏁 Script executed:

# Check imports to understand what timeout function is being used
head -n 20 src-tauri/src/workspace/worktree.rs

Repository: mpiton/prism

Length of output: 737


🏁 Script executed:

# Look for any issue tracking or comments about git processes
grep -r "kill_on_drop\|zombie\|process.*leak\|git.*timeout" src-tauri/src/workspace/ 2>/dev/null

Repository: mpiton/prism

Length of output: 38


🏁 Script executed:

# Check if there are any tests that might show the impact
grep -A 10 -B 5 "fn.*test" src-tauri/src/workspace/worktree.rs | head -40

Repository: mpiton/prism

Length of output: 1365


🏁 Script executed:

# Verify the default behavior by checking tokio docs reference in comments
grep -n "kill_on_drop\|drop.*child\|spawn.*child" src-tauri/src/workspace/worktree.rs

Repository: mpiton/prism

Length of output: 38


🏁 Script executed:

# Check if there are any related issues or TODOs in the codebase
rg -i "todo|fixme|bug|issue" src-tauri/src/workspace/worktree.rs

Repository: mpiton/prism

Length of output: 38


🏁 Script executed:

# Look at what happens with the Command in the test function
cat -n src-tauri/src/workspace/worktree.rs | sed -n '179,195p'

Repository: mpiton/prism

Length of output: 663


🏁 Script executed:

# Check the entire file for any cleanup or abort patterns
rg "abort|kill|terminate" src-tauri/src/workspace/worktree.rs

Repository: mpiton/prism

Length of output: 38


🌐 Web query:

tokio::process::Child kill_on_drop default behavior timeout

💡 Result:

The default behavior of tokio::process::Child when dropped (with kill_on_drop at its default of false) is that the child process continues running independently, matching std::process::Child behavior. It does not get killed automatically. When kill_on_drop(true) is explicitly set on the Command before spawning, dropping the Child sends a kill signal (SIGKILL on Unix) synchronously in the Drop implementation. However, reaping the resulting zombie process cannot be awaited in Drop (to avoid blocking). The Tokio runtime makes a best-effort attempt to reap it later when another process Child is waited on, but there are no guarantees—zombies may persist if no further processes are spawned. There is no built-in timeout for the kill operation itself, as killing is synchronous. For graceful shutdown with timeout, users must implement it manually using Child::id with signals like SIGTERM followed by tokio::time::timeout on wait, or select! with kill.

Citations:


Set kill_on_drop(true) to terminate timed-out git commands.

Wrapping Command::output() in timeout() only cancels the wait—the spawned child process continues running after drop by default. A timed-out fetch or worktree add can mutate repository state even after this function returns an error.

Suggested fix
-    let output = timeout(
-        GIT_TIMEOUT,
-        Command::new("git").args(args).current_dir(cwd).output(),
-    )
+    let mut cmd = Command::new("git");
+    cmd.kill_on_drop(true).args(args).current_dir(cwd);
+
+    let output = timeout(GIT_TIMEOUT, cmd.output())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let output = timeout(
GIT_TIMEOUT,
Command::new("git").args(args).current_dir(cwd).output(),
)
let mut cmd = Command::new("git");
cmd.kill_on_drop(true).args(args).current_dir(cwd);
let output = timeout(GIT_TIMEOUT, cmd.output())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/workspace/worktree.rs` around lines 57 - 60, The timeout
currently wraps Command::output(), which only cancels waiting while leaving the
spawned git child running; instead, spawn the process, call
child.kill_on_drop(true) on the returned Child, then wrap
child.wait_with_output() in the timeout so a timed-out operation will also
terminate the git process. Concretely, replace Command::new(...).output() with
Command::new(...).spawn(), call child.kill_on_drop(true) on the spawned Child,
and then use timeout(GIT_TIMEOUT, child.wait_with_output()) to obtain the
output.

Comment thread src-tauri/src/workspace/worktree.rs Outdated
@mpiton
Copy link
Copy Markdown
Owner Author

mpiton commented Mar 29, 2026

@claude

@claude
Copy link
Copy Markdown

claude Bot commented Mar 29, 2026

Claude finished @mpiton's task in 4m 40s —— View job


Fixed remaining review issues in worktree.rs

All open issues from the coderabbitai and greptile review rounds have been addressed. Commit: 4f24bb0

Changes made:

  • build_worktree_path — stronger path validation (src-tauri/src/workspace/worktree.rs:35-49): Replaced string-check validation with Path::components() analysis. The old check missed "." (a CurDir component, not Normal), which would create a path relative to the base dir. The new check requires exactly one Component::Normal(_) — rejects ., .., /-separated, absolute paths, and Windows drive prefixes. Added a test for ".".

  • run_gitOsString args + kill_on_drop (src-tauri/src/workspace/worktree.rs:60-93): Changed signature from &[&str] to &[OsString] so PathBuf values are forwarded to git as raw OS bytes (no to_string_lossy() corruption on non-UTF-8 paths). Also added cmd.kill_on_drop(true) so a timed-out git process is actually killed rather than left running in the background.

  • create_worktree-- flag separator (src-tauri/src/workspace/worktree.rs:131-138): Added "--" before the branch name in git fetch origin -- <branch> to prevent branch names starting with - from being interpreted as git flags.

  • Parent-dir cleanup note (src-tauri/src/workspace/worktree.rs:104-108): Added doc comment on create_worktree documenting the known behavior that intermediate parent directories created before a failed fetch/worktree-add are left on disk (P2, not blocking correctness).
    | Branch

- Use Path::components() validation in build_worktree_path to also reject
  single-dot names ("." resolves to CurDir, not Normal)
- Change run_git to accept &[OsString] so paths are passed verbatim to git
  without lossy UTF-8 conversion (removes to_string_lossy() on wt_path)
- Add kill_on_drop(true) on Command so timed-out git processes are killed
- Add -- separator in git fetch to prevent branch names starting with -
  being interpreted as flags
- Document parent-dir cleanup behavior in create_worktree doc comment
- Add test case for "." rejection in test_worktree_path_construction

Co-authored-by: Mathieu <mpiton@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src-tauri/src/workspace/worktree.rs (3)

231-259: Consider adding test case for "." repo_name.

The validation tests at lines 254-258 verify rejection of "..", "owner/repo", "a\\b", and empty strings, but don't test the "." case which (per the earlier validation issue) currently passes but shouldn't.

// After fixing validation, add:
assert!(build_worktree_path(&base, ".", 1).is_err());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/workspace/worktree.rs` around lines 231 - 259, Add a test
asserting that a repo_name of "." is rejected by build_worktree_path: call
build_worktree_path(&base, ".", 1) and assert it returns an error; if validation
logic lives in a helper (e.g., sanitize_repo_name or validate_repo_name) update
that function to treat "." as invalid (same as "" and ".." and paths containing
separators) so the new test fails until validation is fixed, then commit the
added assertion to test_worktree_path_construction.

98-98: Consider async existence check for consistency.

wt_path.exists() is synchronous I/O in an async function. While stat() is typically fast, using tokio::fs::try_exists() would maintain consistency with the async filesystem operations used elsewhere in this function (line 107).

Suggested fix
-    if wt_path.exists() {
+    if tokio::fs::try_exists(&wt_path).await.unwrap_or(false) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/workspace/worktree.rs` at line 98, Replace the synchronous
wt_path.exists() call with the async tokio helper: call
tokio::fs::try_exists(&wt_path).await and handle the Result like other async FS
ops in this function (e.g., propagate the error with ? or map to a bool
consistent with surrounding error handling); update the conditional that
currently uses wt_path.exists() to use the awaited boolean from try_exists
instead.

160-164: Consider making this async for consistency.

This is a synchronous function that may be called from async contexts. While path.exists() is a quick syscall, an async version using tokio::fs::try_exists() would align with the module's async patterns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/workspace/worktree.rs` around lines 160 - 164, The synchronous
worktree_exists function should be converted to an async function: change pub fn
worktree_exists(path: &Path) -> bool to pub async fn worktree_exists(path:
&Path) -> std::io::Result<bool> (or -> bool if you prefer to map errors) and
implement it using tokio::fs::try_exists(path).await; update any call sites to
.await and import tokio::fs::try_exists; if you prefer to keep a bool API, map
the Result to a bool (e.g., Ok(b) => b, Err(_) => false) but prefer surfacing
the io::Result for callers that need error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src-tauri/src/workspace/worktree.rs`:
- Around line 231-259: Add a test asserting that a repo_name of "." is rejected
by build_worktree_path: call build_worktree_path(&base, ".", 1) and assert it
returns an error; if validation logic lives in a helper (e.g.,
sanitize_repo_name or validate_repo_name) update that function to treat "." as
invalid (same as "" and ".." and paths containing separators) so the new test
fails until validation is fixed, then commit the added assertion to
test_worktree_path_construction.
- Line 98: Replace the synchronous wt_path.exists() call with the async tokio
helper: call tokio::fs::try_exists(&wt_path).await and handle the Result like
other async FS ops in this function (e.g., propagate the error with ? or map to
a bool consistent with surrounding error handling); update the conditional that
currently uses wt_path.exists() to use the awaited boolean from try_exists
instead.
- Around line 160-164: The synchronous worktree_exists function should be
converted to an async function: change pub fn worktree_exists(path: &Path) ->
bool to pub async fn worktree_exists(path: &Path) -> std::io::Result<bool> (or
-> bool if you prefer to map errors) and implement it using
tokio::fs::try_exists(path).await; update any call sites to .await and import
tokio::fs::try_exists; if you prefer to keep a bool API, map the Result to a
bool (e.g., Ok(b) => b, Err(_) => false) but prefer surfacing the io::Result for
callers that need error handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f9335577-a0cf-4925-98be-83ae51471c53

📥 Commits

Reviewing files that changed from the base of the PR and between 8148c5d and 37c0b14.

📒 Files selected for processing (1)
  • src-tauri/src/workspace/worktree.rs

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src-tauri/src/workspace/worktree.rs (1)

118-123: Consider using async filesystem check for consistency.

The function uses tokio::fs::create_dir_all (line 127) but wt_path.exists() is a blocking call. For consistency with the async filesystem operations used elsewhere, consider using tokio::fs::try_exists().

♻️ Suggested change
-    if wt_path.exists() {
+    if tokio::fs::try_exists(&wt_path).await.unwrap_or(false) {
         return Err(AppError::Workspace(format!(
             "worktree already exists at {}",
             wt_path.display()
         )));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/workspace/worktree.rs` around lines 118 - 123, Replace the
blocking wt_path.exists() check with the async
tokio::fs::try_exists(wt_path).await call: call tokio::fs::try_exists with the
same wt_path, handle the Result<bool, std::io::Error> (map the io::Error into
your AppError::Workspace or propagate using ?), and use the returned bool to
decide the existing-worktree error (same message) before calling
tokio::fs::create_dir_all; ensure any necessary tokio::fs import is added and
adjust control flow to await the async existence check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src-tauri/src/workspace/worktree.rs`:
- Around line 37-40: The binding is failing rustfmt: reformat the matches!
expression that computes is_single_normal (the variables components and
is_single_normal in worktree.rs) so it conforms to rustfmt style; update the
multiline formatting around matches!(components.next(),
Some(std::path::Component::Normal(_))) && components.next().is_none() and then
run cargo fmt to apply the canonical formatting.
- Around line 134-143: The argument array passed to run_git (the slice created
with "fetch".into(), "origin".into(), "--".into(), branch.into()) is formatted
in a way that fails rustfmt; reformat the array to match rustfmt's preferred
layout (or simply run cargo fmt) so the call in worktree.rs that invokes
run_git(...) conforms to CI formatting rules; ensure the run_git call and its
argument slice remain unchanged functionally but are reformatted to satisfy
rustfmt.

---

Nitpick comments:
In `@src-tauri/src/workspace/worktree.rs`:
- Around line 118-123: Replace the blocking wt_path.exists() check with the
async tokio::fs::try_exists(wt_path).await call: call tokio::fs::try_exists with
the same wt_path, handle the Result<bool, std::io::Error> (map the io::Error
into your AppError::Workspace or propagate using ?), and use the returned bool
to decide the existing-worktree error (same message) before calling
tokio::fs::create_dir_all; ensure any necessary tokio::fs import is added and
adjust control flow to await the async existence check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 98b5cb12-995d-4ae2-8fbf-10ba1f389922

📥 Commits

Reviewing files that changed from the base of the PR and between 37c0b14 and 4f24bb0.

📒 Files selected for processing (1)
  • src-tauri/src/workspace/worktree.rs

Comment thread src-tauri/src/workspace/worktree.rs Outdated
Comment thread src-tauri/src/workspace/worktree.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src-tauri/src/workspace/worktree.rs`:
- Around line 37-43: The validation currently uses
Path::new(repo_name).components() and is_single_normal but misses explicit
forward-slash checks so inputs like "repo/" or "repo/." slip through; update the
check that currently tests repo_name.contains('\\') to also explicitly reject
repo_name.contains('/') (and any other path-separator-like characters you deem
necessary) so that the branch that returns an error when !is_single_normal ||
repo_name.contains('\\') becomes !is_single_normal || repo_name.contains('\\')
|| repo_name.contains('/'), keeping the rest of the logic (variables: repo_name,
is_single_normal, components) intact.
- Around line 177-180: The call to run_git inside list_worktrees currently uses
"--porcelain" and parses output with .lines(), which breaks on worktree paths
containing newlines; change the args to include "-z" (i.e., "--porcelain", "-z")
when invoking run_git and update the parsing logic to split on NUL bytes instead
of newline boundaries (use output.split('\0') and handle any trailing empty
element) when interpreting entries like the "worktree " and related records;
update any downstream matching that assumes line endings accordingly in the
list_worktrees function and the code that consumes its results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 662f6919-05b9-48ca-bdac-edaff476618f

📥 Commits

Reviewing files that changed from the base of the PR and between 4f24bb0 and 2c2a521.

📒 Files selected for processing (1)
  • src-tauri/src/workspace/worktree.rs

Comment thread src-tauri/src/workspace/worktree.rs Outdated
Comment thread src-tauri/src/workspace/worktree.rs
…tree list

- build_worktree_path: add contains('/') check to reject "repo/" aliasing
- list_worktrees: use --porcelain -z with split('\0') for path-safe parsing
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