Skip to content

fix: resolve correct project path when opening existing worktrees in monorepos#67

Merged
matej21 merged 3 commits intocontember:mainfrom
JanTvrdik:fix/monorepo-discovered-worktree-path
Mar 27, 2026
Merged

fix: resolve correct project path when opening existing worktrees in monorepos#67
matej21 merged 3 commits intocontember:mainfrom
JanTvrdik:fix/monorepo-discovered-worktree-path

Conversation

@JanTvrdik
Copy link
Copy Markdown
Contributor

Summary

  • When discovering existing worktrees via git worktree list, the returned paths are always the worktree root. In monorepo setups where the project is a subdirectory (e.g. /repo/packages/app), opening such a worktree incorrectly set the project path to the worktree root instead of the matching subdirectory within it (e.g. /repo-wt/feature instead of /repo-wt/feature/packages/app).
  • Compute the subdirectory offset from the parent project's git root and append it to discovered worktree paths in add_discovered_worktree.
  • Fix the worktree list popover to correctly filter the main repo entry (compare against git root, not project path) and match tracked worktrees using the computed project path.
  • Backwards-compatible: duplicate and tracking checks match both the new full project path and bare worktree root for existing workspace.json files.

Follows up on #34 and #61 which fixed worktree creation and removal in monorepos — this PR fixes the remaining case of opening existing worktrees.

Test plan

  • Manual: in a monorepo, add a subdirectory as a project, create a worktree externally (git worktree add), then open it via the worktree list — verify terminal starts in the correct subdirectory
  • Manual: in a non-monorepo, discover and open an existing worktree — verify it still works
  • Manual: verify the main repo entry is correctly filtered from the worktree list in a monorepo
  • Manual: verify toggling tracked/untracked state works correctly for monorepo worktrees

Co-Authored-By: Claude Code

…monorepos

When discovering existing worktrees via `git worktree list`, the returned
paths are always the worktree root. In monorepo setups where the project
is a subdirectory (e.g. /repo/packages/app), opening such a worktree
would incorrectly set the project path to the worktree root instead of
the matching subdirectory within it.

Compute the subdirectory offset from the parent project's git root and
append it to discovered worktree paths. Also fix the worktree list
popover to correctly filter the main repo entry and match tracked
worktrees in monorepo setups.

Co-Authored-By: Claude Code
Copilot AI review requested due to automatic review settings March 27, 2026 15:10
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes incorrect project paths when opening existing git worktrees for monorepo subdirectory projects by ensuring discovered worktree roots are mapped to the equivalent subdirectory inside each worktree.

Changes:

  • Compute a monorepo subdirectory offset from the parent project’s git root and apply it when adding discovered worktrees.
  • Update the worktree list popover to (a) filter out the main repo by comparing against git root and (b) match tracked worktrees using the computed project path (with backwards-compat fallbacks).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
crates/okena-workspace/src/actions/project.rs Adjust discovered worktree project path to include monorepo subdir and add backwards-compat duplicate detection.
crates/okena-views-sidebar/src/worktree_list.rs Store git root + subdir in popover state; filter main repo correctly; match tracked/untracked worktrees using computed project path.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +550 to +556
let parent_path = self.project(parent_id)
.map(|p| p.path.clone())
.unwrap_or_default();
let parent_pathbuf = std::path::PathBuf::from(&parent_path);
let git_root = okena_git::get_repo_root(&parent_pathbuf)
.unwrap_or_else(|| parent_pathbuf.clone());
let subdir = parent_pathbuf.strip_prefix(&git_root)
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

add_discovered_worktree falls back to an empty parent_path when parent_id is missing, which can make get_repo_root run git -C "" ... (current process cwd) and compute an incorrect project_path. Prefer returning early if the parent project can't be found (or otherwise ensure the path used for repo detection is valid).

Suggested change
let parent_path = self.project(parent_id)
.map(|p| p.path.clone())
.unwrap_or_default();
let parent_pathbuf = std::path::PathBuf::from(&parent_path);
let git_root = okena_git::get_repo_root(&parent_pathbuf)
.unwrap_or_else(|| parent_pathbuf.clone());
let subdir = parent_pathbuf.strip_prefix(&git_root)
// If the parent project can't be found, don't add this worktree.
let parent_path = match self.project(parent_id) {
Some(p) => p.path.clone(),
None => return,
};
let parent_pathbuf = std::path::PathBuf::from(&parent_path);
let git_root = okena_git::get_repo_root(&parent_pathbuf)
.unwrap_or_else(|| parent_pathbuf.clone());
let subdir = parent_pathbuf
.strip_prefix(&git_root)

Copilot uses AI. Check for mistakes.
Comment on lines +554 to +556
let git_root = okena_git::get_repo_root(&parent_pathbuf)
.unwrap_or_else(|| parent_pathbuf.clone());
let subdir = parent_pathbuf.strip_prefix(&git_root)
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

strip_prefix is performed on parent_pathbuf vs git_root without normalizing; if the stored project path is relative or contains ./.., strip_prefix will fail (since get_repo_root returns an absolute path) and subdir becomes empty, reintroducing the monorepo bug. Normalize both paths (like okena_git::repository::normalize_path is used elsewhere) before computing subdir.

Suggested change
let git_root = okena_git::get_repo_root(&parent_pathbuf)
.unwrap_or_else(|| parent_pathbuf.clone());
let subdir = parent_pathbuf.strip_prefix(&git_root)
// Normalize both the parent path and the git root before computing subdir
let parent_norm = okena_git::repository::normalize_path(&parent_pathbuf);
let git_root = okena_git::get_repo_root(&parent_norm)
.unwrap_or_else(|| parent_norm.clone());
let git_root_norm = okena_git::repository::normalize_path(&git_root);
let subdir = parent_norm
.strip_prefix(&git_root_norm)

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +55
let project_path = workspace.read(cx).project(&project_id)
.map(|p| p.path.clone())
.unwrap_or_default();
let entries = okena_git::repository::list_git_worktrees(
std::path::Path::new(&project_path),
);
let project_pathbuf = std::path::PathBuf::from(&project_path);
let git_root = okena_git::get_repo_root(&project_pathbuf)
.unwrap_or_else(|| project_pathbuf.clone());
let subdir = project_pathbuf.strip_prefix(&git_root)
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

WorktreeListPopover::new uses unwrap_or_default() for project_path; if the project id is invalid, this can cause get_repo_root/git worktree list to run relative to the process cwd and show unrelated worktrees. Consider returning early (e.g., empty entries) when the project isn't found instead of defaulting to an empty path.

Copilot uses AI. Check for mistakes.
let project_pathbuf = std::path::PathBuf::from(&project_path);
let git_root = okena_git::get_repo_root(&project_pathbuf)
.unwrap_or_else(|| project_pathbuf.clone());
let subdir = project_pathbuf.strip_prefix(&git_root)
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

subdir is computed via project_pathbuf.strip_prefix(&git_root) without normalizing either path. This is inconsistent with other monorepo/worktree codepaths (e.g., WorktreeDialog) and can fail when the stored project path is relative or contains ./.., causing incorrect expected_path computation. Normalize both paths before strip_prefix.

Suggested change
let subdir = project_pathbuf.strip_prefix(&git_root)
// Normalize both paths before computing subdir to handle relative paths and `.`/`..`.
let normalized_project = project_pathbuf
.canonicalize()
.unwrap_or_else(|_| project_pathbuf.clone());
let normalized_root = git_root
.canonicalize()
.unwrap_or_else(|_| git_root.clone());
let subdir = normalized_project
.strip_prefix(&normalized_root)

Copilot uses AI. Check for mistakes.
- Add `resolve_git_root_and_subdir()` to okena-git, replacing duplicated
  inline git_root + strip_prefix logic. Uses normalize_path for
  correctness with symlinks and relative path components.
- Have `add_discovered_worktree` return the project ID instead of
  forcing caller to search by path. Remove unused `_main_repo_path`
  parameter.
- Extract `find_tracked_project_id` helper in WorktreeListPopover to
  deduplicate the backwards-compat path matching logic.
- Cache normalized git root in struct field instead of recomputing per
  render frame.

Co-Authored-By: Claude Code
@matej21 matej21 merged commit a74b12e into contember:main Mar 27, 2026
4 checks passed
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.

3 participants