mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-22 06:54:26 +00:00
fix(yield): case-insensitive canonical repo grouping via realpathSync.native
Follow-up to #719, closing the two deltas flagged in the #714 discussion: - fs.realpathSync preserves the caller's path casing on case-insensitive filesystems (macOS APFS, Windows), so the same repo reached through differently cased paths produced distinct canonical keys and split into separate groups. fs.realpathSync.native returns the on-disk casing, making the canonical key truly canonical. Regression test creates a real repo, references it through altered casing (guarded to case-insensitive filesystems), and asserts one group. - Documents the accepted residual: a main repo moved on disk after 'git worktree add' leaves git's linked-worktree metadata stale, so its worktrees may not unify; that is git-state staleness, not a grouping bug.
This commit is contained in:
parent
916119da21
commit
2c9871b2f2
2 changed files with 37 additions and 1 deletions
|
|
@ -71,7 +71,7 @@ type RepoIdentity = {
|
|||
|
||||
function canonicalPath(path: string): string {
|
||||
try {
|
||||
return realpathSync(path)
|
||||
return realpathSync.native(path)
|
||||
} catch {
|
||||
return path
|
||||
}
|
||||
|
|
@ -110,6 +110,9 @@ function resolveRepoIdentity(
|
|||
// common-dir as a realpath but the main worktree's as ".git" relative to
|
||||
// its (possibly symlinked) path, so both must be canonicalized to collapse
|
||||
// to one key.
|
||||
// Accepted residual: moving the main repo after `git worktree add` leaves
|
||||
// Git's linked-worktree metadata stale, so its worktrees may not unify;
|
||||
// that is Git-state staleness, not a grouping bug.
|
||||
identity = { key: canonicalPath(resolve(dir, commonDir)), gitDir: dir }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { execFileSync } from 'node:child_process'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
|
@ -81,6 +82,38 @@ const broadWindow = {
|
|||
}
|
||||
|
||||
describe('yield repo grouping by canonical repository identity (issue #713)', () => {
|
||||
it('groups path casing variants on case-insensitive filesystems', async () => {
|
||||
const repoDir = await mkdtemp(join(tmpdir(), 'codeburn-yield-path-case-'))
|
||||
try {
|
||||
initRepo(repoDir)
|
||||
await writeFile(join(repoDir, 'file.txt'), 'hello\n')
|
||||
commitAt(repoDir, 'feat: shipped once', '2026-01-01T10:30:00Z')
|
||||
|
||||
const caseAlteredRepoDir = join(repoDir, '..', repoDir.split('/').pop()!.toUpperCase())
|
||||
if (!existsSync(caseAlteredRepoDir)) return
|
||||
|
||||
const sessionOriginal = makeSession({ sessionId: 'case-original', project: 'app', ...tightWindow, totalCostUSD: 5 })
|
||||
const sessionAltered = makeSession({ sessionId: 'case-altered', project: 'app', ...broadWindow, totalCostUSD: 3 })
|
||||
|
||||
parseAllSessionsMock.mockResolvedValue([
|
||||
{ project: 'app', projectPath: repoDir, sessions: [sessionOriginal] } as ProjectSummary,
|
||||
{ project: 'app', projectPath: caseAlteredRepoDir, sessions: [sessionAltered] } as ProjectSummary,
|
||||
])
|
||||
|
||||
const summary = await computeYield(range, repoDir)
|
||||
|
||||
const productive = summary.details.filter(d => d.category === 'productive')
|
||||
expect(productive.map(d => d.sessionId)).toEqual(['case-original'])
|
||||
expect(productive[0]!.commitCount).toBe(1)
|
||||
|
||||
const detailAltered = summary.details.find(d => d.sessionId === 'case-altered')!
|
||||
expect(detailAltered.category).toBe('ambiguous')
|
||||
expect(detailAltered.commitCount).toBe(0)
|
||||
} finally {
|
||||
await rm(repoDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('credits one commit once across two monorepo subdirectory sessions', async () => {
|
||||
const repoDir = await mkdtemp(join(tmpdir(), 'codeburn-yield-monorepo-'))
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue