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}