Skip to content

Fix worktree detection for worktrees inside .git directory #154

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

kbwo
Copy link

@kbwo kbwo commented Jun 3, 2025

Issue Overview

Commands like :GinStatus sometimes show the status of the root repository instead of the correct worktree, even when a worktree is in use. When the worktree is created under the Git root directory (i.e., under .git/worktrees/), the plugin fails to detect it properly and ends up referencing the root repository's state.


Reproduction Patterns

  1. Correct Pattern

    • Git root:

      <any_path>/example
      
    • Worktree:

      <any_path>/workspace1
      

    If the worktree is created at the same level as (or in a directory separate from) the Git root, opening a file in workspace1 and running :GinStatus correctly shows the status of the worktree at <any_path>/workspace1.

  2. Bug Pattern

    • Git root:

      <any_path>/example
      
    • Worktree (created under the Git root):

      <any_path>/example/.git/worktrees/workspace1
      

    In this case, opening a file in workspace1 and running :GinStatus displays the status of the Git root (<any_path>/example) instead of the worktree, even though the worktree is located at <any_path>/example/.git/worktrees/workspace1.


Expected Behavior

Even when a worktree is created under the Git root directory (under .git/worktrees/), the plugin should correctly identify the root directory of the worktree to which the opened file belongs (for example, <any_path>/example/.git/worktrees/workspace1) and display that worktree's status.


Solution

This PR fixes the worktree detection logic by:

  1. Adding upward directory traversal: The plugin now searches upward from the current directory to find worktree roots by checking for .git files (which indicate a worktree).

  2. Implementing proper worktree validation: A new isWorktreeRoot function validates that a directory containing a .git file is indeed a valid git worktree.

  3. Improving git rev-parse execution: The revParse function now first attempts to execute from the current directory, which correctly handles worktrees inside .git directories. It only falls back to parent directory handling if the initial attempt fails.

These changes ensure that worktrees located within the git root directory structure are properly detected and their status is correctly displayed.

Summary by CodeRabbit

  • Bug Fixes
    • Improved detection of Git worktree roots with enhanced upward directory traversal for more reliable repository identification.
    • Added cross-platform support for identifying filesystem root directories, improving path resolution accuracy.
    • Enhanced error handling and simplified Git command execution to reduce issues during repository detection.

Note

This motivated me to want to manage the worktree in .git.

https://x.com/mizchi/status/1926974534541844532

Previously, worktrees located as children of the git root (e.g., in
.git/workspaces/) were not properly detected, causing commands like
GinStatus to show the git root's status instead of the worktree's status.

This fix adds upward directory traversal to find worktree roots and
improves git rev-parse execution to handle worktrees in .git directories.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Copy link

coderabbitai bot commented Jun 3, 2025

"""

Walkthrough

The changes add a function to detect the filesystem root and modify the Git worktree finder to traverse directories upward to find a worktree root. The revParse function was simplified, and error handling was refined. Additional tests verify detection of Git worktrees nested inside .git directories and cache consistency.

Changes

Files Change Summary
denops/gin/git/finder.ts Added isFilesystemRoot, upward traversal in findWorktree, added isWorktreeRoot, simplified revParse, refined error handling
denops/gin/git/finder_test.ts Added tests for findWorktree and findGitdir with Git worktrees inside .git/workspaces, verifying correct detection and caching

Error: Could not generate a valid Mermaid diagram after multiple attempts.

🐇
Up the paths I hop and bound,
Roots of filesystems found,
Git worktrees now I seek,
With code both sharp and sleek.
Tests confirm my bounding leap,
In every OS, roots I keep!
🥕✨
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c96421 and d93a18d.

📒 Files selected for processing (2)
  • denops/gin/git/finder.ts (5 hunks)
  • denops/gin/git/finder_test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • denops/gin/git/finder_test.ts
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test (windows-latest, 1.x, v9.1.0448, v0.10.0)
  • GitHub Check: test (macos-latest, 1.x, v9.1.0448, v0.10.0)
🔇 Additional comments (5)
denops/gin/git/finder.ts (5)

1-1: Good cleanup of unused import.

Removing the unused SEPARATOR import improves code cleanliness.


10-19: Excellent cross-platform filesystem root detection.

The implementation correctly detects filesystem root by comparing a path with its parent directory. This approach works consistently across Windows, Linux, and Mac, addressing the Windows compatibility concern from previous reviews.


49-49: Good TypeScript practice for error handling.

Explicitly casting caught exceptions to Error type improves type safety and makes error handling more explicit.


73-73: Consistent error handling improvement.

Same good practice applied consistently across both functions.


99-99: Good simplification aligns with new architecture.

The simplified revParse function correctly removes the previous complex logic for handling .git directories, as this is now properly handled by the upward traversal and isWorktreeRoot functions.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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: 0

🧹 Nitpick comments (2)
denops/gin/git/finder.ts (2)

6-6: Fix misleading comment.

The comment says "seconds" but the value 30000 represents milliseconds (30 seconds).

-const ttl = 30000; // seconds
+const ttl = 30000; // milliseconds

76-89: Consider more specific error handling.

The function correctly identifies worktree roots, but the blanket catch block might hide real errors. Consider handling specific error types differently.

 async function isWorktreeRoot(currentPath: string): Promise<boolean> {
   try {
     const gitPath = resolve(currentPath, ".git");
     const stat = await Deno.stat(gitPath);
     if (stat.isFile) {
       // Found a .git file, verify it's a valid git worktree
       await revParse(currentPath, ["--git-dir"]);
       return true;
     }
-  } catch {
-    // Either .git doesn't exist or it's not a valid worktree
+  } catch (e) {
+    // Only ignore expected errors (file not found, not a worktree)
+    if (!(e instanceof Deno.errors.NotFound)) {
+      // Log unexpected errors for debugging
+      console.debug("Unexpected error in isWorktreeRoot:", e);
+    }
   }
   return false;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab4372b and 8165719.

📒 Files selected for processing (1)
  • denops/gin/git/finder.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
denops/gin/git/finder.ts (2)
denops/gin/git/process.ts (1)
  • execute (42-56)
denops/gin/util/text.ts (1)
  • decodeUtf8 (14-16)
🔇 Additional comments (2)
denops/gin/git/finder.ts (2)

21-34: Well-implemented upward directory traversal.

The logic correctly searches upward from the current directory to find a worktree root, with proper caching when a worktree is found. The dual condition for detecting the root directory ensures compatibility across different platforms.


92-109: Excellent implementation of retry logic for .git directories.

The function correctly handles the edge case of running git rev-parse inside .git directories by implementing a smart retry mechanism. The path manipulation using SEPARATOR ensures cross-platform compatibility.

Copy link

codecov bot commented Jun 4, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 60.61%. Comparing base (ab4372b) to head (d93a18d).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #154      +/-   ##
==========================================
+ Coverage   58.91%   60.61%   +1.70%     
==========================================
  Files          17       17              
  Lines         881      904      +23     
  Branches      108      118      +10     
==========================================
+ Hits          519      548      +29     
+ Misses        362      356       -6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

// Search upward from current directory to find worktree root
let currentPath = path;
while (
currentPath && currentPath !== "/" &&
Copy link
Owner

Choose a reason for hiding this comment

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

We must care Windows.

Copy link
Author

Choose a reason for hiding this comment

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

Sorry, I forgot about Windows.
Fixed in 0a99e14 and also added tests for worktrees inside .git.

Comment on lines 99 to 107
// If it fails and we're in a .git directory, try from parent
const terms = cwd.split(SEPARATOR);
if (terms.includes(".git")) {
const index = terms.indexOf(".git");
const parentCwd = terms.slice(0, index).join(SEPARATOR);
const stdout = await execute(["rev-parse", ...args], { cwd: parentCwd });
const output = decodeUtf8(stdout);
return resolve(output.split(/\n/, 2)[0]);
}
Copy link
Owner

Choose a reason for hiding this comment

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

Why do we need this fallback?

Copy link
Author

Choose a reason for hiding this comment

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

I kept the fallback for compatibility reasons, but on second thought, it wasn't needed since we're running the command in the intended directory from the beginning. Just my mistake for not catching that.
Removed it in d93a18d

@kbwo kbwo force-pushed the fix/wworktree branch from 3021b82 to 2c96421 Compare June 4, 2025 15:57
Copy link

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

🛑 Comments failed to post (2)
denops/gin/git/finder.ts (1)

32-42: 💡 Verification agent

🧩 Analysis chain

Excellent upward traversal logic for worktree detection.

The implementation correctly addresses the PR objective by searching upward from the current directory to find worktree roots. The early return with caching when a worktree is found and the fallback to regular repository detection is well-designed.

However, verify the test coverage for the worktree detection path:


🏁 Script executed:

#!/bin/bash
# Description: Check test coverage for the new worktree detection logic

# Search for tests that create worktrees inside .git directories
rg -A 10 -B 5 "worktree.*\.git" --type ts

# Look for tests specifically covering the upward traversal logic
ast-grep --pattern $'while (!isFilesystemRoot($$$)) {
  $$$
}'

Length of output: 595


Add test coverage for upward worktree detection
We didn’t find any existing tests that exercise the while (!isFilesystemRoot(currentPath)) { … } block or simulate a nested worktree under .git/worktrees. Please add tests covering:

  • A directory hierarchy with a .git/worktrees/<name> folder to ensure early return and caching
  • The case where no worktree is found (traverse to filesystem root and fallback)
  • Cache behavior when the same path is queried multiple times

Suggested locations:

  • Production code: denops/gin/git/finder.ts (lines 32–42)
  • New test file, e.g. tests/git/finder.test.ts
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 36-39: denops/gin/git/finder.ts#L36-L39
Added lines #L36 - L39 were not covered by tests

🤖 Prompt for AI Agents
In denops/gin/git/finder.ts around lines 32 to 42, add new test cases in a test
file like tests/git/finder.test.ts to cover the upward traversal worktree
detection logic. Create tests that simulate a directory hierarchy containing a
.git/worktrees/<name> folder to verify early return and caching behavior, tests
where no worktree is found to confirm traversal to filesystem root and fallback,
and tests that query the same path multiple times to validate cache usage. This
will ensure the while loop and caching logic are properly exercised and
verified.
denops/gin/git/finder_test.ts (1)

8-60: 🛠️ Refactor suggestion

Comprehensive and well-structured test for filesystem root detection.

The test effectively covers:

  • Cross-platform root paths (Windows and Unix)
  • Upward traversal behavior
  • Safety limits to prevent infinite loops
  • Edge cases with root directory validation

However, the main PR objective (worktree detection inside .git directories) lacks test coverage. Consider adding a test case that reproduces the specific bug scenario:

Deno.test({
  name: "findWorktree() correctly detects worktrees inside .git directory",
  fn: async () => {
    await using sbox = await prepare();
    
    // Create a worktree inside the .git directory
    const worktreePath = join(sbox.path, ".git", "worktrees", "test-branch");
    await $`git worktree add ${worktreePath} -b test-branch`;
    
    // Change to the worktree directory
    Deno.chdir(worktreePath);
    
    // Should detect the worktree root, not the main repository
    assertEquals(await findWorktree("."), worktreePath);
  },
  sanitizeResources: false,
  sanitizeOps: false,
});

This would directly test the bug fix described in the PR objectives.

🤖 Prompt for AI Agents
In denops/gin/git/finder_test.ts around lines 8 to 60, while the existing tests
thoroughly check filesystem root detection, there is no test covering the main
PR objective of detecting worktrees inside .git directories. Add a new Deno.test
that sets up a git worktree inside a .git/worktrees subdirectory, switches to
that worktree directory, and asserts that findWorktree correctly identifies the
worktree root path. This test should create the worktree using git commands,
change the current directory to the worktree, and verify the detection, ensuring
coverage of the specific bug fix.

Copy link

@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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3021b82 and 2c96421.

📒 Files selected for processing (2)
  • denops/gin/git/finder.ts (5 hunks)
  • denops/gin/git/finder_test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • denops/gin/git/finder_test.ts
🧰 Additional context used
🪛 GitHub Check: codecov/patch
denops/gin/git/finder.ts

[warning] 36-39: denops/gin/git/finder.ts#L36-L39
Added lines #L36 - L39 were not covered by tests


[warning] 89-92: denops/gin/git/finder.ts#L89-L92
Added lines #L89 - L92 were not covered by tests

🔇 Additional comments (4)
denops/gin/git/finder.ts (4)

1-1: LGTM: Unnecessary import removed.

The removal of the SEPARATOR import is appropriate since it's no longer used after the refactoring.


10-19: Well-designed cross-platform filesystem root detection.

The isFilesystemRoot function provides a clean, cross-platform solution for detecting the filesystem root. The approach of comparing a path with its parent using resolve(currentPath, "..") correctly handles Windows, Linux, and macOS paths, addressing the previous concern about Windows compatibility.


49-49: Improved error handling with explicit type casting.

The explicit casting to Error type before caching provides better type safety and clarity about the expected error handling behavior.

Also applies to: 73-73


99-103: Simplified revParse function improves maintainability.

The removal of the previous fallback logic that adjusted the working directory makes the function more straightforward and easier to understand. The worktree-specific logic is now properly handled in the upward traversal within findWorktree, which is a better separation of concerns.

@kbwo kbwo force-pushed the fix/wworktree branch from 2c96421 to d93a18d Compare June 4, 2025 16:07
@kbwo kbwo requested a review from lambdalisue June 4, 2025 16:18
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.

2 participants