Skip to content

Commit e012ece

Browse files
committed
fix: prevent shadow git from inheriting global init.templateDir
- Modified initShadowGit to use --template= flag when creating shadow repositories - This prevents unwanted hooks and templates from being copied to shadow repos - Added test to verify no executable hooks are created from templates - Fixes #8628
1 parent 3a47c55 commit e012ece

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

src/services/checkpoints/ShadowCheckpointService.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ export abstract class ShadowCheckpointService extends EventEmitter {
106106
this.baseHash = await git.revparse(["HEAD"])
107107
} else {
108108
this.log(`[${this.constructor.name}#initShadowGit] creating shadow git repo at ${this.checkpointsDir}`)
109-
await git.init()
109+
// Use --template= to prevent inheriting user's global init.templateDir configuration
110+
// This ensures no unwanted hooks or templates are copied to the shadow repository
111+
await git.init(["--template="])
110112
await git.addConfig("core.worktree", this.workspaceDir) // Sets the working tree to the current workspace.
111113
await git.addConfig("commit.gpgSign", "false") // Disable commit signing for shadow repo.
112114
await git.addConfig("user.name", "Roo Code")

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,46 @@ describe.each([[RepoPerTaskCheckpointService, "RepoPerTaskCheckpointService"]])(
483483
await fs.rm(shadowDir, { recursive: true, force: true })
484484
await fs.rm(workspaceDir, { recursive: true, force: true })
485485
})
486+
487+
it("does not inherit global init.templateDir when creating shadow git repo", async () => {
488+
// Create a new temporary workspace and service for this test
489+
const shadowDir = path.join(tmpDir, `${prefix}-no-template-${Date.now()}`)
490+
const workspaceDir = path.join(tmpDir, `workspace-no-template-${Date.now()}`)
491+
492+
// Create a workspace without any git repo
493+
await fs.mkdir(workspaceDir, { recursive: true })
494+
const testFile = path.join(workspaceDir, "test.txt")
495+
await fs.writeFile(testFile, "Test content")
496+
497+
// Create the service and initialize shadow git
498+
const service = new klass(taskId, shadowDir, workspaceDir, () => {})
499+
await service.initShadowGit()
500+
501+
// Verify the shadow git repo was created
502+
const gitDir = path.join(shadowDir, ".git")
503+
expect(await fileExistsAtPath(gitDir)).toBe(true)
504+
505+
// Verify no hooks directory was created (which would happen if templates were used)
506+
// The --template= flag should prevent any template from being used
507+
const hooksDir = path.join(gitDir, "hooks")
508+
509+
// Check if hooks directory exists
510+
const hooksExists = await fileExistsAtPath(hooksDir)
511+
512+
if (hooksExists) {
513+
// If hooks directory exists, it should only contain sample hooks (*.sample files)
514+
// and no executable hooks
515+
const hookFiles = await fs.readdir(hooksDir)
516+
const executableHooks = hookFiles.filter((file) => !file.endsWith(".sample"))
517+
518+
// There should be no executable hooks from templates
519+
expect(executableHooks).toHaveLength(0)
520+
}
521+
522+
// Clean up
523+
await fs.rm(shadowDir, { recursive: true, force: true })
524+
await fs.rm(workspaceDir, { recursive: true, force: true })
525+
})
486526
})
487527

488528
describe(`${klass.name}#events`, () => {

0 commit comments

Comments
 (0)