Skip to content

fix(cli): resolve repo by filesystem identity when stored path spelling differs#282

Open
jimboswankster wants to merge 2 commits into
kunchenguid:mainfrom
jimboswankster:fix/canonical-repo-path-resolution
Open

fix(cli): resolve repo by filesystem identity when stored path spelling differs#282
jimboswankster wants to merge 2 commits into
kunchenguid:mainfrom
jimboswankster:fix/canonical-repo-path-resolution

Conversation

@jimboswankster

Copy link
Copy Markdown

What Changed

  • Reworked findRepo to collect candidate roots (git root plus the main worktree root), attempt exact path lookups against each, then fall back to a new repoByPathIdentity helper that matches a registered repo via os.SameFile instead of erroring with repo not initialized.
  • This makes commands resolve the current directory to its registered repo even when the stored working path spells the same directory differently — a case-only variant on case-insensitive filesystems (macOS/Windows) or an unresolved symlink alias.
  • Added db.ListRepos to enumerate registered repos for the identity fallback, and documented the command-time resolution behavior in the gate-model concepts doc.

Risk Assessment

✅ Low: A small, well-bounded fix that only adds a filesystem-identity fallback on the already-failing lookup path, using os.SameFile semantics that cannot false-match unrelated directories, with targeted tests covering both the symlink and case-variant cases.

Testing

Reviewed the diff and confirmed HEAD is the target commit, then drove the actual end-user surface: I built the base and target binaries and exercised the documented bug through the real no-mistakes runs command against the real product SQLite DB, seeding a registered repo whose stored path spelled the same directory differently (symlink alias and case variant). The captured CLI transcripts show the exact before→after flip — base errors with "repo not initialized" (exit 1), target resolves the repo and prints "no runs yet…" (exit 0). I also ran the new unit test (both subtests pass, including the case-variant path that confirms this filesystem is case-insensitive), the affected packages under the race detector, and the full go test -race ./... suite — all green. Transient build binaries were removed and the working tree is clean.

Evidence: CLI before/after — symlink-alias stored path (no-mistakes runs)

stored repo working_path : .../work-alias (symlink -> same dir) === BEFORE FIX (4893e32) === $ cd <repo> && no-mistakes runs repo not initialized (run 'no-mistakes init' first) [exit 1] === AFTER FIX (9990d1a) === $ cd <repo> && no-mistakes runs no runs yet. Push through the gate to start a pipeline: git push no-mistakes <branch> [exit 0]

Scenario: a registered repo whose stored working_path spells the directory
differently than 'git rev-parse' reports at command time (worktree casing /
symlink alias). Here the stored path is a symlink alias of the same directory.

  git reports at command time : /private/var/folders/tn/q2gv576569z3m_j_g29fzf5m0000gn/T/tmp.uHJew8JEEn/work
  stored repo working_path    : /var/folders/tn/q2gv576569z3m_j_g29fzf5m0000gn/T/tmp.uHJew8JEEn/work-alias  (symlink -> same dir)

=================== BEFORE FIX  (base 4893e32) ===================
stored working_path in DB: /var/folders/tn/q2gv576569z3m_j_g29fzf5m0000gn/T/tmp.uHJew8JEEn/work-alias
$ cd <repo> && no-mistakes runs
repo not initialized (run 'no-mistakes init' first)
[exit 1]

=================== AFTER FIX   (target 9990d1a) ===================
stored working_path in DB: /var/folders/tn/q2gv576569z3m_j_g29fzf5m0000gn/T/tmp.uHJew8JEEn/work-alias
$ cd <repo> && no-mistakes runs
  no runs yet. Push through the gate to start a pipeline:
  git push no-mistakes <branch>
[exit 0]
Evidence: CLI before/after — case-variant stored path (no-mistakes runs)

stored repo working_path : .../WORK (case variant of same dir) === BEFORE FIX (4893e32) === $ cd <repo> && no-mistakes runs repo not initialized (run 'no-mistakes init' first) [exit 1] === AFTER FIX (9990d1a) === $ cd <repo> && no-mistakes runs no runs yet. Push through the gate to start a pipeline: git push no-mistakes <branch> [exit 0]

Scenario: stored working_path is a CASE VARIANT of the directory git reports
(macOS/Windows case-insensitive filesystems; 'git rev-parse --git-common-dir'
preserves whatever casing the worktree was created with).

  git reports at command time : /private/var/folders/tn/q2gv576569z3m_j_g29fzf5m0000gn/T/tmp.umCp12tNl9/work
  stored repo working_path    : /private/var/folders/tn/q2gv576569z3m_j_g29fzf5m0000gn/T/tmp.umCp12tNl9/WORK  (case variant of same dir)

=================== BEFORE FIX  (base 4893e32) ===================
stored working_path in DB: /private/var/folders/tn/q2gv576569z3m_j_g29fzf5m0000gn/T/tmp.umCp12tNl9/WORK
$ cd <repo> && no-mistakes runs
repo not initialized (run 'no-mistakes init' first)
[exit 1]

=================== AFTER FIX   (target 9990d1a) ===================
stored working_path in DB: /private/var/folders/tn/q2gv576569z3m_j_g29fzf5m0000gn/T/tmp.umCp12tNl9/WORK
$ cd <repo> && no-mistakes runs
  no runs yet. Push through the gate to start a pipeline:
  git push no-mistakes <branch>
[exit 0]

Pipeline

Updates from git push no-mistakes

⏭️ **intent** - skipped

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ℹ️ internal/cli/root.go:160 - In repoByPathIdentity the loop iterates for each repo { for each candidate }, so a repo aliasing mainRoot can be returned before one aliasing gitRoot, losing the gitRoot-first preference the exact-match loop enforces. This only matters if two distinct repo records both have string-mismatched (case/symlink) paths resolving to gitRoot and mainRoot simultaneously — effectively impossible given init normalizes to the resolved main root and reattachRelocatedRepo prevents duplicates. Also note ListRepos has no ORDER BY, so iteration order is unspecified. No action needed; documenting the subtlety.
✅ **Test** - passed

✅ No issues found.

  • Built before/after binaries: go build -o ./bin/nm-target ./cmd/no-mistakes (HEAD 9990d1a) and the same against reverted base source (4893e32) → ./bin/nm-base
  • Manual product transcript (symlink alias): seeded a repos row via sqlite3 with working_path = symlink alias of the git root, then ran no-mistakes runs from inside the repo with both binaries — base printed 'repo not initialized' (exit 1), target resolved and printed 'no runs yet…' (exit 0)
  • Manual product transcript (case variant): same flow with working_path = case variant (.../WORK vs .../work) — base failed, target resolved
  • go test ./internal/cli -run &#39;^TestFindRepoResolvesAliasedWorkingPath$&#39; -v -count=1 (symlink_alias and case_variant subtests both PASS; case_variant not skipped, so os.SameFile path exercised)
  • go test -race ./internal/cli ./internal/db -count=1
  • go test -race ./... (full suite, all packages ok)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…ng differs

The repo lookup compared the stored working path to the cwd's git root by
exact string match. The same directory frequently spells differently: git
rev-parse --git-common-dir preserves whatever casing a worktree was created
with (case-insensitive filesystems on macOS/Windows), and paths can reach
the repo through symlink aliases. Any spelling mismatch surfaced as
'repo not initialized' even though the repo was registered.

Fall back to filesystem identity (os.SameFile) over the registered repos
when exact lookup misses, for both the cwd git root and the main worktree
root.
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