flake-update-20260201
  1package main
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"io"
  7	"os"
  8	"regexp"
  9	"strings"
 10)
 11
 12// PreToolUseData represents the input from PreToolUse hook
 13type PreToolUseData struct {
 14	ToolName       string                 `json:"tool_name"`
 15	ToolInput      map[string]interface{} `json:"tool_input"`
 16	ConversationID string                 `json:"conversation_id"`
 17}
 18
 19// Check if a git push command uses explicit refspec (branch:branch)
 20func hasExplicitRefspec(command string) bool {
 21	// Match patterns like:
 22	// git push origin branch:branch
 23	// git push origin HEAD:branch
 24	// git push -u origin branch:branch
 25	// git push --force-with-lease origin branch:branch
 26	refspecPattern := regexp.MustCompile(`git\s+push\s+.*\s+\S+:\S+`)
 27	return refspecPattern.MatchString(command)
 28}
 29
 30// Check if this is a git push command
 31func isGitPush(command string) bool {
 32	return strings.Contains(command, "git push") || strings.Contains(command, "git push")
 33}
 34
 35// Check if pushing to a protected branch without explicit refspec
 36func isPushToProtectedBranch(command string) bool {
 37	// These patterns indicate pushing to main/master without explicit refspec
 38	protectedBranches := []string{":main", ":master"}
 39	for _, branch := range protectedBranches {
 40		if strings.Contains(command, branch) {
 41			return true
 42		}
 43	}
 44	return false
 45}
 46
 47func main() {
 48	// Read input from stdin
 49	input, err := io.ReadAll(os.Stdin)
 50	if err != nil {
 51		fmt.Fprintf(os.Stderr, "[validate-git-push] Error reading stdin: %v\n", err)
 52		os.Exit(0) // Allow on error - don't block workflow
 53	}
 54
 55	if len(input) == 0 {
 56		os.Exit(0)
 57	}
 58
 59	var data PreToolUseData
 60	if err := json.Unmarshal(input, &data); err != nil {
 61		fmt.Fprintf(os.Stderr, "[validate-git-push] Error parsing JSON: %v\n", err)
 62		os.Exit(0) // Allow on error
 63	}
 64
 65	// Only check Bash tool
 66	if data.ToolName != "Bash" {
 67		os.Exit(0)
 68	}
 69
 70	// Get the command
 71	command, ok := data.ToolInput["command"].(string)
 72	if !ok || command == "" {
 73		os.Exit(0)
 74	}
 75
 76	// Check if this is a git push command
 77	if !isGitPush(command) {
 78		os.Exit(0)
 79	}
 80
 81	// Check if it has explicit refspec
 82	if !hasExplicitRefspec(command) {
 83		// Block the command - output error message to stderr and exit non-zero
 84		fmt.Fprintln(os.Stderr, "BLOCKED: git push without explicit refspec detected!")
 85		fmt.Fprintln(os.Stderr, "")
 86		fmt.Fprintln(os.Stderr, "The command uses implicit branch tracking which can push to wrong branches.")
 87		fmt.Fprintln(os.Stderr, "")
 88		fmt.Fprintln(os.Stderr, "Use explicit refspec instead:")
 89		fmt.Fprintln(os.Stderr, "  git push origin <branch>:<branch>")
 90		fmt.Fprintln(os.Stderr, "  git push origin HEAD:<branch>")
 91		fmt.Fprintln(os.Stderr, "")
 92		fmt.Fprintf(os.Stderr, "Blocked command: %s\n", command)
 93		os.Exit(2) // Non-zero exit blocks the tool
 94	}
 95
 96	// Warn about pushing to protected branches (but allow it with explicit refspec)
 97	if isPushToProtectedBranch(command) {
 98		fmt.Fprintf(os.Stderr, "[validate-git-push] Warning: Pushing to protected branch (main/master)\n")
 99	}
100
101	os.Exit(0) // Allow the command
102}