package cmd import ( _ "embed" "fmt" "os" "path/filepath" "strings" "time" "github.com/spf13/cobra" ) //go:embed hook_script.sh var hookScript string // hookMarker identifies a KeyHunter-managed hook. Uninstall refuses to // delete pre-commit files that don't contain this marker unless --force. const hookMarker = "KEYHUNTER-HOOK v1" var ( hookForce bool ) var hookCmd = &cobra.Command{ Use: "hook", Short: "Install or manage git pre-commit hooks", } var hookInstallCmd = &cobra.Command{ Use: "install", Short: "Install the keyhunter pre-commit hook into .git/hooks/", RunE: runHookInstall, } var hookUninstallCmd = &cobra.Command{ Use: "uninstall", Short: "Remove the keyhunter pre-commit hook", RunE: runHookUninstall, } func init() { hookInstallCmd.Flags().BoolVar(&hookForce, "force", false, "overwrite any existing pre-commit hook without prompt") hookUninstallCmd.Flags().BoolVar(&hookForce, "force", false, "delete pre-commit even if it is not KeyHunter-managed") hookCmd.AddCommand(hookInstallCmd) hookCmd.AddCommand(hookUninstallCmd) } func hookPath() (string, error) { gitDir := ".git" info, err := os.Stat(gitDir) if err != nil || !info.IsDir() { return "", fmt.Errorf("not a git repository (no .git/ in current directory)") } return filepath.Join(gitDir, "hooks", "pre-commit"), nil } func runHookInstall(cmd *cobra.Command, args []string) error { target, err := hookPath() if err != nil { return err } if _, err := os.Stat(target); err == nil { existing, _ := os.ReadFile(target) if strings.Contains(string(existing), hookMarker) { // Already ours — overwrite silently to update script. } else if !hookForce { return fmt.Errorf("pre-commit hook already exists at %s (use --force to overwrite; a .bak backup will be kept)", target) } else { backup := target + ".bak." + time.Now().Format("20060102150405") if err := os.Rename(target, backup); err != nil { return fmt.Errorf("backing up existing hook: %w", err) } fmt.Fprintf(cmd.OutOrStdout(), "Backed up existing hook to %s\n", backup) } } if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { return fmt.Errorf("creating hooks dir: %w", err) } if err := os.WriteFile(target, []byte(hookScript), 0o755); err != nil { return fmt.Errorf("writing hook: %w", err) } fmt.Fprintf(cmd.OutOrStdout(), "Installed pre-commit hook at %s\n", target) return nil } func runHookUninstall(cmd *cobra.Command, args []string) error { target, err := hookPath() if err != nil { return err } data, err := os.ReadFile(target) if err != nil { if os.IsNotExist(err) { fmt.Fprintln(cmd.OutOrStdout(), "No pre-commit hook to remove.") return nil } return fmt.Errorf("reading hook: %w", err) } if !strings.Contains(string(data), hookMarker) && !hookForce { return fmt.Errorf("pre-commit at %s is not KeyHunter-managed (use --force to remove anyway)", target) } if err := os.Remove(target); err != nil { return fmt.Errorf("removing hook: %w", err) } fmt.Fprintf(cmd.OutOrStdout(), "Removed pre-commit hook at %s\n", target) return nil }