feat(doctor): Add hooks-path-configured check
Verifies all clones have core.hooksPath set to .githooks. Auto-fixable with 'gt doctor --fix'. This ensures the pre-push hook is active on all clones, blocking pushes to invalid branches (no internal PRs). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -231,6 +231,126 @@ func (c *GitExcludeConfiguredCheck) Fix(ctx *CheckContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// HooksPathConfiguredCheck verifies all clones have core.hooksPath set to .githooks.
|
||||
// This ensures the pre-push hook blocks pushes to invalid branches (no internal PRs).
|
||||
type HooksPathConfiguredCheck struct {
|
||||
FixableCheck
|
||||
unconfiguredClones []string
|
||||
}
|
||||
|
||||
// NewHooksPathConfiguredCheck creates a new hooks path check.
|
||||
func NewHooksPathConfiguredCheck() *HooksPathConfiguredCheck {
|
||||
return &HooksPathConfiguredCheck{
|
||||
FixableCheck: FixableCheck{
|
||||
BaseCheck: BaseCheck{
|
||||
CheckName: "hooks-path-configured",
|
||||
CheckDescription: "Check core.hooksPath is set for all clones",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Run checks if all clones have core.hooksPath configured.
|
||||
func (c *HooksPathConfiguredCheck) Run(ctx *CheckContext) *CheckResult {
|
||||
rigPath := ctx.RigPath()
|
||||
if rigPath == "" {
|
||||
return &CheckResult{
|
||||
Name: c.Name(),
|
||||
Status: StatusError,
|
||||
Message: "No rig specified",
|
||||
}
|
||||
}
|
||||
|
||||
c.unconfiguredClones = nil
|
||||
|
||||
// Check all clone locations
|
||||
clonePaths := []string{
|
||||
filepath.Join(rigPath, "mayor", "rig"),
|
||||
filepath.Join(rigPath, "refinery", "rig"),
|
||||
}
|
||||
|
||||
// Add crew clones
|
||||
crewDir := filepath.Join(rigPath, "crew")
|
||||
if entries, err := os.ReadDir(crewDir); err == nil {
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
clonePaths = append(clonePaths, filepath.Join(crewDir, entry.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add polecat clones
|
||||
polecatDir := filepath.Join(rigPath, "polecats")
|
||||
if entries, err := os.ReadDir(polecatDir); err == nil {
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
clonePaths = append(clonePaths, filepath.Join(polecatDir, entry.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, clonePath := range clonePaths {
|
||||
// Skip if not a git repo
|
||||
if _, err := os.Stat(filepath.Join(clonePath, ".git")); os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip if no .githooks directory exists
|
||||
if _, err := os.Stat(filepath.Join(clonePath, ".githooks")); os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check core.hooksPath
|
||||
cmd := exec.Command("git", "-C", clonePath, "config", "--get", "core.hooksPath")
|
||||
output, err := cmd.Output()
|
||||
if err != nil || strings.TrimSpace(string(output)) != ".githooks" {
|
||||
// Get relative path for cleaner output
|
||||
relPath, _ := filepath.Rel(rigPath, clonePath)
|
||||
if relPath == "" {
|
||||
relPath = clonePath
|
||||
}
|
||||
c.unconfiguredClones = append(c.unconfiguredClones, clonePath)
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.unconfiguredClones) == 0 {
|
||||
return &CheckResult{
|
||||
Name: c.Name(),
|
||||
Status: StatusOK,
|
||||
Message: "All clones have hooks configured",
|
||||
}
|
||||
}
|
||||
|
||||
// Build details with relative paths
|
||||
var details []string
|
||||
for _, clonePath := range c.unconfiguredClones {
|
||||
relPath, _ := filepath.Rel(rigPath, clonePath)
|
||||
if relPath == "" {
|
||||
relPath = clonePath
|
||||
}
|
||||
details = append(details, relPath)
|
||||
}
|
||||
|
||||
return &CheckResult{
|
||||
Name: c.Name(),
|
||||
Status: StatusWarning,
|
||||
Message: fmt.Sprintf("%d clone(s) missing hooks configuration", len(c.unconfiguredClones)),
|
||||
Details: details,
|
||||
FixHint: "Run 'gt doctor --fix' to configure hooks",
|
||||
}
|
||||
}
|
||||
|
||||
// Fix configures core.hooksPath for all unconfigured clones.
|
||||
func (c *HooksPathConfiguredCheck) Fix(ctx *CheckContext) error {
|
||||
for _, clonePath := range c.unconfiguredClones {
|
||||
cmd := exec.Command("git", "-C", clonePath, "config", "core.hooksPath", ".githooks")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to configure hooks for %s: %w", clonePath, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WitnessExistsCheck verifies the witness directory structure exists.
|
||||
type WitnessExistsCheck struct {
|
||||
FixableCheck
|
||||
@@ -750,6 +870,7 @@ func RigChecks() []Check {
|
||||
return []Check{
|
||||
NewRigIsGitRepoCheck(),
|
||||
NewGitExcludeConfiguredCheck(),
|
||||
NewHooksPathConfiguredCheck(),
|
||||
NewWitnessExistsCheck(),
|
||||
NewRefineryExistsCheck(),
|
||||
NewMayorCloneExistsCheck(),
|
||||
|
||||
Reference in New Issue
Block a user