Skip to content

fix(sdd): detect git worktrees in findProjectRoot and resolve main repo root#1180

Open
blak0p wants to merge 1 commit into
Gentleman-Programming:mainfrom
blak0p:fix/worktree-detection
Open

fix(sdd): detect git worktrees in findProjectRoot and resolve main repo root#1180
blak0p wants to merge 1 commit into
Gentleman-Programming:mainfrom
blak0p:fix/worktree-detection

Conversation

@blak0p

@blak0p blak0p commented Jul 12, 2026

Copy link
Copy Markdown

🔗 Linked Issue

Closes #702


🏷️ PR Type

  • type:bug — Bug fix (non-breaking change that fixes an issue)

📝 Summary

  • findProjectRoot() now detects when .git is a regular file (git worktree) vs a directory (normal repo)
  • When .git is a file, it reads the gitdir: line and resolves the main repository root
  • Falls back to git rev-parse --git-common-dir if file parsing fails
  • Normal .git directories are completely unaffected

📂 Changes

File / Area What Changed
internal/components/sdd/inject.go Added resolveWorktreeRoot() and resolveGitCommonDir() helpers; modified findProjectRoot() to special-case .git file vs directory
internal/components/sdd/inject_test.go Added 3 new tests: worktree .git file resolution, malformed fallback, normal .git dir regression guard
openspec/specs/sdd-orchestrator-assets/spec.md Merged delta spec for Project Root Resolution requirement

🧪 Test Plan

Unit Tests

go test ./...
  • Unit tests pass (go test ./...) — 13/13 findProjectRoot tests pass (10 existing + 3 new)
  • E2E tests pass (cd e2e && ./docker-test.sh)
  • Manually tested locally

✅ Contributor Checklist

  • PR is linked to an issue with status:approved
  • PR stays within 400 changed lines, or I have requested/obtained maintainer-applied size:exception with rationale documented
  • I have added the appropriate type:* label to this PR
  • Unit tests pass (go test ./...)
  • E2E tests pass (cd e2e && ./docker-test.sh)
  • I have updated documentation if necessary
  • My commits follow Conventional Commits format
  • My commits do not include Co-Authored-By trailers

💬 Notes for Reviewers

The change is ~270 lines total (~120 code, ~99 tests, ~55 spec). Well under the 400-line budget. All existing tests pass unchanged.

Summary by CodeRabbit

  • Bug Fixes

    • Improved project-root detection for Git worktrees.
    • Correctly identifies the main repository when a worktree uses a .git file.
    • Added safe fallback behavior for malformed Git metadata.
  • Tests

    • Added coverage for valid and invalid worktree configurations.
    • Confirmed existing behavior remains unchanged for standard Git repositories.

…po root

In a git worktree, .git is a regular file (not a directory) containing
'gitdir: /path/to/main/.git/worktrees/name'. findProjectRoot() treated
it as a strong marker and returned the worktree root, causing Engram to
create duplicate projects per worktree and the SDD orchestrator to
re-trigger sdd-init.

Now when .git is a file, the function reads the gitdir: line, resolves
the shared .git directory, and returns the main repo root. Falls back
to 'git rev-parse --git-common-dir' if file parsing fails. Normal .git
directories are unaffected.

Closes Gentleman-Programming#702
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Project-root detection now handles Git worktrees whose .git marker is a file by resolving shared repository metadata, falling back to Git when needed, and preserving existing directory behavior. Tests cover successful resolution, malformed markers, and normal repositories.

Changes

Git worktree root resolution

Layer / File(s) Summary
Worktree root resolution helpers
internal/components/sdd/inject.go
Adds Git worktree pointer parsing, symlink resolution, directory validation, and a timed git rev-parse --git-common-dir fallback.
Project-root integration and coverage
internal/components/sdd/inject.go, internal/components/sdd/inject_test.go
Updates .git marker handling and tests successful worktree resolution, malformed-file fallback, and unchanged .git directory behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing project-root detection for Git worktrees and resolving the main repository root.
Linked Issues check ✅ Passed The changes address issue #702 by treating worktree .git files as the shared repository root and preserving normal repository behavior.
Out of Scope Changes check ✅ Passed The modified code and tests are directly tied to worktree root detection, with no obvious unrelated changes in the reviewable files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/worktree-detection

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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/components/sdd/inject_test.go`:
- Around line 5210-5211: Update the test around the findProjectRoot assertion to
resolve symlinks on mainRoot before comparing it with got, using
filepath.EvalSymlinks and handling the returned error. Compare the resolved
expected root against got while preserving the existing failure message and test
intent.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a872e091-9cca-4121-b172-3544b463b48f

📥 Commits

Reviewing files that changed from the base of the PR and between 668c581 and 1ee7037.

⛔ Files ignored due to path filters (1)
  • openspec/specs/sdd-orchestrator-assets/spec.md is excluded by !openspec/**
📒 Files selected for processing (2)
  • internal/components/sdd/inject.go
  • internal/components/sdd/inject_test.go

Comment on lines +5210 to +5211
if got != mainRoot {
t.Fatalf("findProjectRoot = %q, want main repo root %q", got, mainRoot)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Test will fail on macOS due to symlink resolution mismatch.

resolveWorktreeRoot calls filepath.EvalSymlinks on the computed commonGitDir and returns filepath.Dir(resolved). On macOS, t.TempDir() returns a path under /var/folders/... where /var is a symlink to /private/var. After EvalSymlinks, got becomes /private/var/folders/.../T/mainRoot while mainRoot remains /var/folders/.../T/mainRoot, causing the assertion to fail.

🛡️ Proposed fix: resolve symlinks on mainRoot before comparison
 func TestFindProjectRootWorktreeGitFile(t *testing.T) {
 	mainRoot := t.TempDir()
+	// Resolve symlinks so the comparison matches filepath.EvalSymlinks
+	// called inside resolveWorktreeRoot (macOS /var → /private/var).
+	resolvedMainRoot, err := filepath.EvalSymlinks(mainRoot)
+	if err != nil {
+		t.Fatalf("EvalSymlinks(mainRoot): %v", err)
+	}

 	// Main repo has a real .git directory with a worktrees subdirectory.
-	mainGitDir := filepath.Join(mainRoot, ".git")
+	mainGitDir := filepath.Join(resolvedMainRoot, ".git")

Then update the assertion:

-	if got != mainRoot {
-		t.Fatalf("findProjectRoot = %q, want main repo root %q", got, mainRoot)
+	if got != resolvedMainRoot {
+		t.Fatalf("findProjectRoot = %q, want main repo root %q", got, resolvedMainRoot)
 	}
📝 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
if got != mainRoot {
t.Fatalf("findProjectRoot = %q, want main repo root %q", got, mainRoot)
if got != resolvedMainRoot {
t.Fatalf("findProjectRoot = %q, want main repo root %q", got, resolvedMainRoot)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/components/sdd/inject_test.go` around lines 5210 - 5211, Update the
test around the findProjectRoot assertion to resolve symlinks on mainRoot before
comparing it with got, using filepath.EvalSymlinks and handling the returned
error. Compare the resolved expected root against got while preserving the
existing failure message and test intent.

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.

problema con worktrees

1 participant