Skip to content

Commit 084844b

Browse files
sinzin91claude
andauthored
fix: create .git/hooks directory if it doesn't exist (#6)
## Problem `roborev init` fails when the `.git/hooks` directory doesn't exist: ``` Error: install hook: open /path/to/repo/.git/hooks/post-commit: no such file or directory ``` This happens in repositories where the hooks directory has been removed or was never created. ## Solution Added `os.MkdirAll` to create the `.git/hooks` directory before attempting to write the post-commit hook file. ## Changes - **`cmd/roborev/main.go`**: Added hooks directory creation in both `initCmd()` and `installHookCmd()` - **`cmd/roborev/main_test.go`**: Added comprehensive test `TestInitCmdCreatesHooksDirectory` that: - Creates a git repo without a hooks directory - Verifies `roborev init` succeeds - Confirms the hooks directory and post-commit hook are created - Validates hook is executable ## Testing All existing tests pass, and the new test confirms the fix: ```bash go test ./cmd/roborev/... # ok github.com/wesm/roborev/cmd/roborev 3.617s ``` The fix follows TDD methodology: 1. ✅ Wrote failing test first 2. ✅ Implemented minimal fix 3. ✅ Verified test passes Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent a28c5d9 commit 084844b

File tree

2 files changed

+107
-0
lines changed

2 files changed

+107
-0
lines changed

cmd/roborev/main.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,12 @@ func initCmd() *cobra.Command {
260260
# RoboRev post-commit hook - auto-reviews every commit
261261
roborev enqueue --sha HEAD 2>/dev/null &
262262
`
263+
// Ensure hooks directory exists
264+
hooksDir := filepath.Join(root, ".git", "hooks")
265+
if err := os.MkdirAll(hooksDir, 0755); err != nil {
266+
return fmt.Errorf("create hooks directory: %w", err)
267+
}
268+
263269
// Check for existing hook
264270
if existing, err := os.ReadFile(hookPath); err == nil {
265271
if !strings.Contains(string(existing), "roborev") {
@@ -706,6 +712,12 @@ func installHookCmd() *cobra.Command {
706712
return fmt.Errorf("hook already exists at %s (use --force to overwrite)", hookPath)
707713
}
708714

715+
// Ensure hooks directory exists
716+
hooksDir := filepath.Join(root, ".git", "hooks")
717+
if err := os.MkdirAll(hooksDir, 0755); err != nil {
718+
return fmt.Errorf("create hooks directory: %w", err)
719+
}
720+
709721
hookContent := `#!/bin/sh
710722
# RoboRev post-commit hook - auto-reviews every commit
711723
roborev enqueue --sha HEAD 2>/dev/null &

cmd/roborev/main_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,98 @@ func TestUninstallHookCmd(t *testing.T) {
306306
}
307307
})
308308
}
309+
310+
func TestInitCmdCreatesHooksDirectory(t *testing.T) {
311+
// Override HOME to prevent reading real daemon.json
312+
tmpHome := t.TempDir()
313+
origHome := os.Getenv("HOME")
314+
os.Setenv("HOME", tmpHome)
315+
defer os.Setenv("HOME", origHome)
316+
317+
// Create a temp git repo WITHOUT a hooks directory
318+
tmpDir := t.TempDir()
319+
320+
// Initialize git repo
321+
cmd := exec.Command("git", "init")
322+
cmd.Dir = tmpDir
323+
cmd.Env = append(os.Environ(),
324+
"HOME="+tmpHome,
325+
"GIT_AUTHOR_NAME=Test",
326+
"GIT_AUTHOR_EMAIL=test@test.com",
327+
"GIT_COMMITTER_NAME=Test",
328+
"GIT_COMMITTER_EMAIL=test@test.com",
329+
)
330+
if out, err := cmd.CombinedOutput(); err != nil {
331+
t.Fatalf("git init failed: %v\n%s", err, out)
332+
}
333+
334+
// Remove .git/hooks directory to simulate the problematic scenario
335+
hooksDir := filepath.Join(tmpDir, ".git", "hooks")
336+
if err := os.RemoveAll(hooksDir); err != nil {
337+
t.Fatalf("Failed to remove hooks directory: %v", err)
338+
}
339+
340+
// Verify hooks directory doesn't exist
341+
if _, err := os.Stat(hooksDir); !os.IsNotExist(err) {
342+
t.Fatal("hooks directory should not exist before test")
343+
}
344+
345+
// Create a roborevd binary in PATH for the test (init tries to start daemon)
346+
// We'll use a fake one that does nothing
347+
binDir := filepath.Join(tmpHome, "bin")
348+
if err := os.MkdirAll(binDir, 0755); err != nil {
349+
t.Fatal(err)
350+
}
351+
fakeDaemon := filepath.Join(binDir, "roborevd")
352+
fakeScript := "#!/bin/sh\nexit 0\n"
353+
if err := os.WriteFile(fakeDaemon, []byte(fakeScript), 0755); err != nil {
354+
t.Fatal(err)
355+
}
356+
origPath := os.Getenv("PATH")
357+
os.Setenv("PATH", binDir+":"+origPath)
358+
defer os.Setenv("PATH", origPath)
359+
360+
// Run init command
361+
cmd = exec.Command("go", "run", "./cmd/roborev", "init", "--agent", "test")
362+
cmd.Dir = filepath.Join(tmpDir, "..", "..", "..") // Go back to project root
363+
cmd.Env = append(os.Environ(),
364+
"HOME="+tmpHome,
365+
"PATH="+binDir+":"+origPath,
366+
)
367+
// Change to the test repo directory before running
368+
origWd, _ := os.Getwd()
369+
if err := os.Chdir(tmpDir); err != nil {
370+
t.Fatal(err)
371+
}
372+
defer os.Chdir(origWd)
373+
374+
// Build init command
375+
initCommand := initCmd()
376+
initCommand.SetArgs([]string{"--agent", "test"})
377+
err := initCommand.Execute()
378+
379+
// Should succeed (not fail with "no such file or directory")
380+
if err != nil {
381+
t.Fatalf("init command failed: %v", err)
382+
}
383+
384+
// Verify hooks directory was created
385+
if _, err := os.Stat(hooksDir); os.IsNotExist(err) {
386+
t.Error("hooks directory was not created")
387+
}
388+
389+
// Verify hook file was created
390+
hookPath := filepath.Join(hooksDir, "post-commit")
391+
if _, err := os.Stat(hookPath); os.IsNotExist(err) {
392+
t.Error("post-commit hook was not created")
393+
}
394+
395+
// Verify hook is executable
396+
info, err := os.Stat(hookPath)
397+
if err != nil {
398+
t.Fatal(err)
399+
}
400+
if info.Mode()&0111 == 0 {
401+
t.Error("post-commit hook is not executable")
402+
}
403+
}

0 commit comments

Comments
 (0)