From 3c5ab578e94453a4aac63e9abf0164a1f8ca9569 Mon Sep 17 00:00:00 2001 From: stef Date: Fri, 27 Mar 2026 00:26:36 +0100 Subject: [PATCH 1/2] fix: prevent cross-project name pollution in findParentRepository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When multiple projects share a common ancestor directory (e.g. /home/user), the repositoryCache could cause one project's resolved name to silently override another's. For example, a project at /home/user with basename "user" would cache "/home/user" → "user". Later, an unrelated child project (e.g. ProjectX under /home/user/Documents/ProjectX) would walk up its parents, hit the cached "/home/user" entry, and incorrectly inherit the name "user" — making the project disappear from output and its events merge into the wrong timeline. This was order-dependent: the bug only triggered when the ancestor project had events in the selected time window (e.g. -d 3 but not -d 1), making it appear that projects randomly vanish with wider date ranges. Fix: findParentRepository now only checks for actual .git/config with remotes when walking parents, never consulting the repositoryCache. The cache is still populated on cache-miss for future direct lookups, but a cached parent from another project can no longer contaminate unrelated child project names. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/core/parser/index.ts | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/core/parser/index.ts b/src/core/parser/index.ts index 1d89667..5f706f5 100644 --- a/src/core/parser/index.ts +++ b/src/core/parser/index.ts @@ -33,7 +33,10 @@ function getCachedRepositoryName(directory: string): string { return repoName; } -// Find parent repository by walking up the directory tree +// Find parent repository by walking up the directory tree. +// Only checks for actual .git/config with remotes — never uses the +// repositoryCache to avoid cross-project name pollution (e.g. a cached +// parent "/home/user" → "user" would incorrectly rename unrelated child projects). function findParentRepository(directory: string): string | null { let currentDir = directory; @@ -44,19 +47,11 @@ function findParentRepository(directory: string): string | null { break; } - if (repositoryCache.has(parentDir)) { - const repoName = repositoryCache.get(parentDir)!; - if (repoName) { - repositoryCache.set(directory, repoName); - return repoName; - } - } else { - const repoName = getRepositoryName(parentDir); - if (repoName) { - repositoryCache.set(parentDir, repoName); - repositoryCache.set(directory, repoName); - return repoName; - } + const repoName = getRepositoryName(parentDir); + if (repoName) { + repositoryCache.set(parentDir, repoName); + repositoryCache.set(directory, repoName); + return repoName; } currentDir = parentDir; From 64a780af9fd5550e019d27d12d18bebb4b08ead9 Mon Sep 17 00:00:00 2001 From: stef Date: Fri, 27 Mar 2026 00:33:09 +0100 Subject: [PATCH 2/2] fix: only extract repo name from [remote] sections in .git/config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extractRepoNameFromConfig() matched the first `url =` line in the git config regardless of which section it belonged to. When a repo had no [remote "origin"] but had [submodule "foo"] with a URL, the function would extract the submodule's repo name instead — causing the project to appear under the wrong name (the submodule's name). Fix: track section headers and only extract URLs from [remote "..."] sections, ignoring [submodule "..."] and other sections. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/core/git/index.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/core/git/index.ts b/src/core/git/index.ts index 38f398a..ad58baa 100644 --- a/src/core/git/index.ts +++ b/src/core/git/index.ts @@ -52,17 +52,26 @@ export function getRepositoryName(directory: string): string | null { function extractRepoNameFromConfig(content: string): string | null { const lines = content.split('\n'); + let inRemoteSection = false; for (const line of lines) { const trimmed = line.trim(); - // Look for URL lines - const urlMatch = trimmed.match(/url\s*=\s*(.+)/); - if (urlMatch) { - const url = urlMatch[1].trim(); - const repoName = extractRepoNameFromURL(url); - if (repoName) { - return repoName; + // Track section headers — only extract URLs from [remote "..."] sections, + // not [submodule "..."] sections whose URLs point to unrelated repos. + if (trimmed.startsWith('[')) { + inRemoteSection = /^\[remote\s+"/.test(trimmed); + continue; + } + + if (inRemoteSection) { + const urlMatch = trimmed.match(/url\s*=\s*(.+)/); + if (urlMatch) { + const url = urlMatch[1].trim(); + const repoName = extractRepoNameFromURL(url); + if (repoName) { + return repoName; + } } } }