Skip to content

fix(git): correct canonical_root for worktrees and subdirectory projects#749

Merged
DeusData merged 3 commits into
mainfrom
distill/672-git-canonical-root
Jul 2, 2026
Merged

fix(git): correct canonical_root for worktrees and subdirectory projects#749
DeusData merged 3 commits into
mainfrom
distill/672-git-canonical-root

Conversation

@DeusData

@DeusData DeusData commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Distilled from #672 (thanks @anivaryam for diagnosing #659), reworked to be platform-agnostic.

Bug (#659)

detect_changes returned an empty impacted_symbols for projects indexed inside a git worktree or from a repo subdirectory: canonical_root was computed wrong. git rev-parse --git-common-dir emits a path relative to the -C directory, and the /.git suffix was stripped textually without resolving .., so the derived root never matched the project's stored root path.

Fix — let git canonicalize

Instead of manually joining the relative common-dir and normalizing it ourselves (realpath/_fullpath), capture git's own absolute canonical common-dir:

git rev-parse --path-format=absolute --git-common-dir

--path-format=absolute (git 2.31+) makes the path absolute and canonical. derive_canonical_root uses it directly (strip /.git). Why this is the right fix:

  • detect_changes: impacted_symbols always empty for projects inside git worktrees (canonical_root miscalculated) #659 fixed — git resolves the relative .. internally, so a subdirectory (or worktree) yields the real repo root.
  • Platform-agnostic & consistent — a main repo and its linked worktree both ask the same git binary, so they resolve to the identical path. This preserves the worktree == main-root invariant (test_pipeline.c git_context_linked_worktree) on all platforms, including the MSYS2 Windows CI where our earlier _fullpath approach diverged (msys /tmpD:\a\_temp\msys64\tmp vs git's native D:\tmp for the same directory).
  • No platform-specific path code — no realpath/_fullpath/#ifdef on the primary path.

Fallback: for git < 2.31 (no --path-format, capture returns empty) it falls back to the relative-join derivation (Unix realpath).

Reproduce-first (verified locally, git 2.48.1 / macOS)

tests/test_git_context.ccanonical_root_subdir is the genuine guard: a repo indexed from a subdirectory. RED without the fix ("<root>/.." != "<root>"), GREEN with it; full suite green. The git-based tests SKIP_PLATFORM on Windows (that CI shell can't git init via system() with a POSIX redirect); the cross-platform correctness is covered by the pre-existing git_context_linked_worktree invariant test, which now passes on Windows too.

Why a distillation of #672

#672 diagnosed #659 correctly; its own fix (manual input_path join + _fullpath) couldn't pass the Windows CI leg because _fullpath canonicalizes msys paths inconsistently with git. This PR keeps the diagnosis and reproduce case but derives the path from git itself, which is correct on every platform. Supersedes #696 (incomplete). Co-authored with @anivaryam.

Closes #659

Source: git rev-parse --path-format (git 2.31+).

detect_changes returned an empty impacted_symbols set for projects indexed
inside a git worktree or from a repo subdirectory (#659): canonical_root was
computed wrong. `git rev-parse --git-common-dir` emits a path relative to the
directory passed via -C (input_path), NOT to worktree_root, and the "/.git"
suffix was stripped textually without resolving ".." components. So e.g.
"../.git" joined against worktree_root, or "/ws/scripts/../.git" strip, left an
unresolved ".." and never matched the project's stored root path.

Fix (src/git/git_context.c): join the relative --git-common-dir against
input_path (the -C dir) instead of worktree_root, and realpath()/_fullpath()-
normalize the result BEFORE stripping "/.git" (the .git dir always exists for a
valid repo, so resolution succeeds). This is consistent with the existing
realpath'd root_path used for prefix matching (mcp.c).

Reproduce-first: tests/test_git_context.c. The subdirectory case is the genuine
guard — a repo indexed from a subdir yields a *relative* --git-common-dir, so the
unfixed code leaves an un-normalized "<root>/subdir/.." (verified RED; GREEN after
the realpath fix). The linked-worktree case is a supporting invariant, not the
#659 reproducer: git that emits an *absolute* worktree --git-common-dir (e.g.
2.48.x) doesn't manifest the bug, so it passes either way; it still enforces the
worktree->main-root invariant. All three git-based tests SKIP_PLATFORM on Windows
(the CI shell there cannot init a repo via system()).

Distilled from #672 (thanks @anivaryam); that PR's production fix is taken
verbatim. Its repo_root/subdir tests ran git unconditionally and failed the
Windows CI leg ("failed to init git repo"); here all three git-based tests are
Windows-skipped so ci-ok is green cross-platform. Supersedes #696, which only
realpath'd after the strip without changing the join base and so still yielded
the workspace parent for the subdirectory case.

Closes #659

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-Authored-By: anivaryam <anivaryam.dev@gmail.com>
DeusData and others added 2 commits July 2, 2026 00:37
On Windows every git_context test SKIP_PLATFORMs, leaving git_run/make_git_repo
as unused static functions -> -Wunused-function under -Werror failed the Windows
build. Guard the two helpers (only called from the non-Windows test bodies) so
the file compiles clean on Windows while the tests still run on Linux/macOS.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Replace the manual join(input_path, --git-common-dir) + realpath/_fullpath with
`git rev-parse --path-format=absolute --git-common-dir` (git 2.31+), which returns
git's own absolute, canonical common-dir. Because a main repo and its linked
worktree both query the same git binary, they resolve to the IDENTICAL path, so
canonical_root is consistent across worktrees and platforms with no
platform-specific path canonicalization.

This is the truly platform-agnostic #659 fix:
- git resolves the relative ".." internally, so a project indexed from a
  subdirectory gets the real repo root (the #659 bug).
- No realpath/_fullpath, so the msys-vs-native path-representation divergence that
  broke the worktree==main-root invariant (test_pipeline.c
  git_context_linked_worktree) on the Windows CI leg is eliminated — both sides
  get git's single canonical representation.

Falls back to the previous relative-join derivation (Unix realpath) when git is
older than 2.31 and doesn't support --path-format (abs_common_dir empty). The
input_path-join fix and the Windows test-harness SKIP_PLATFORM guards from the
earlier commits on this branch remain for that fallback path.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-Authored-By: anivaryam <anivaryam.dev@gmail.com>
@DeusData DeusData merged commit b9b7905 into main Jul 2, 2026
15 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.

detect_changes: impacted_symbols always empty for projects inside git worktrees (canonical_root miscalculated)

1 participant