Skip to content

Commit 014858f

Browse files
committed
fix: improve nested git repository detection logic
- Fix path filtering to correctly expect 'file' type for HEAD files instead of 'folder' - Use Node's path module for robust cross-platform path manipulation - Update tests to match the corrected logic
1 parent a7f54c4 commit 014858f

File tree

2 files changed

+30
-12
lines changed

2 files changed

+30
-12
lines changed

src/services/checkpoints/ShadowCheckpointService.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -162,24 +162,40 @@ export abstract class ShadowCheckpointService extends EventEmitter {
162162

163163
private async getNestedGitRepository(): Promise<string | null> {
164164
try {
165-
// Find all .git directories that are not at the root level.
165+
// Find all .git/HEAD files that are not at the root level.
166166
const args = ["--files", "--hidden", "--follow", "-g", "**/.git/HEAD", this.workspaceDir]
167167

168168
const gitPaths = await executeRipgrep({ args, workspacePath: this.workspaceDir })
169169

170170
// Filter to only include nested git directories (not the root .git).
171-
const nestedGitPaths = gitPaths.filter(
172-
({ type, path }) =>
173-
type === "folder" && path.includes(".git") && !path.startsWith(".git") && path !== ".git",
174-
)
171+
// Since we're searching for HEAD files, we expect type to be "file"
172+
const nestedGitPaths = gitPaths.filter(({ type, path: filePath }) => {
173+
// Check if it's a file and is a nested .git/HEAD (not at root)
174+
if (type !== "file") return false
175+
176+
// Ensure it's a .git/HEAD file and not the root one
177+
const normalizedPath = filePath.replace(/\\/g, "/")
178+
return (
179+
normalizedPath.includes(".git/HEAD") &&
180+
!normalizedPath.startsWith(".git/") &&
181+
normalizedPath !== ".git/HEAD"
182+
)
183+
})
175184

176185
if (nestedGitPaths.length > 0) {
177-
// Get the first nested git repository path (remove .git/HEAD from the path)
178-
const firstNestedPath = nestedGitPaths[0].path.replace(/[\/\\]\.git[\/\\]HEAD$/, "")
179-
const absolutePath = path.join(this.workspaceDir, firstNestedPath)
186+
// Get the first nested git repository path
187+
// Remove .git/HEAD from the path to get the repository directory
188+
const headPath = nestedGitPaths[0].path
189+
190+
// Use path module to properly extract the repository directory
191+
// The HEAD file is at .git/HEAD, so we need to go up two directories
192+
const gitDir = path.dirname(headPath) // removes HEAD, gives us .git
193+
const repoDir = path.dirname(gitDir) // removes .git, gives us the repo directory
194+
195+
const absolutePath = path.join(this.workspaceDir, repoDir)
180196

181197
this.log(
182-
`[${this.constructor.name}#getNestedGitRepository] found ${nestedGitPaths.length} nested git repositories, first at: ${firstNestedPath}`,
198+
`[${this.constructor.name}#getNestedGitRepository] found ${nestedGitPaths.length} nested git repositories, first at: ${repoDir}`,
183199
)
184200
return absolutePath
185201
}

src/services/checkpoints/__tests__/ShadowCheckpointService.spec.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,11 +421,13 @@ describe.each([[RepoPerTaskCheckpointService, "RepoPerTaskCheckpointService"]])(
421421
const searchPattern = args[4]
422422

423423
if (searchPattern.includes(".git/HEAD")) {
424+
// Return the HEAD file path, not the .git directory
425+
const headFilePath = path.join(path.relative(workspaceDir, nestedGitDir), "HEAD")
424426
return Promise.resolve([
425427
{
426-
path: path.relative(workspaceDir, nestedGitDir),
427-
type: "folder",
428-
label: ".git",
428+
path: headFilePath,
429+
type: "file", // HEAD is a file, not a folder
430+
label: "HEAD",
429431
},
430432
])
431433
} else {

0 commit comments

Comments
 (0)