Repository navigation
Expand file tree
/
Copy pathprepare-commit-msg
More file actions
203 lines (169 loc) · 7.05 KB
/
Copy pathprepare-commit-msg
File metadata and controls
203 lines (169 loc) · 7.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#!/bin/bash
# Define the default commit message file
DEFAULT_COMMIT_MSG_FILE=".git/COMMIT_EDITMSG"
COMMIT_MSG_FILE="${1:-$DEFAULT_COMMIT_MSG_FILE}"
# Logging function with timestamps
log_message() {
local level="$1"
local message="$2"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message" >> /tmp/prepare-commit-msg.log
}
# Function to generate fallback commit message based on file changes
generate_fallback_message() {
local diff="$1"
# Analyze the diff to determine commit type
if echo "$diff" | grep -q "^+.*test\|spec"; then
echo "test: add or update tests"
elif echo "$diff" | grep -q "^+.*\.md\|README\|doc"; then
echo "docs: update documentation"
elif echo "$diff" | grep -qE "^\+.*fix|bug|error"; then
echo "fix: resolve issue"
elif echo "$diff" | grep -q "^+"; then
echo "feat: add new changes"
else
echo "chore: update files"
fi
}
# Read or set default configuration values
DEFAULT_CHATGPT_PROMPT="Suggest commit message based on the following diff:\n\n\`\`\`\n{{diff}}\n\`\`\`\n\ncommit messages must be following these rules:\n - follow conventional commits\n - message format must be in this format: \"<TYPE>: <DESCRIPTION>\"\n - <TYPE> must be the prefix of commit message and must be one of the following: feat, fix, docs, style, refactor, test, chore\n - <DESCRIPTION> must be the description of the commit in lowercase and without any special characters\n\nEXAMPLES COMMIT MESSAGE:\n - fix: add password regex pattern\n - feat: add new test cases\n\nNOTE: Response only commit message, no explanation anymore\n\nACTUAL COMMIT MESSAGE: \n"
DEFAULT_MODEL="gpt-4o-mini"
DEFAULT_MAX_TOKENS=128000
DEFAULT_API_ENDPOINT="https://api.openai.com/v1/chat/completions"
# Get configuration values with validation
CHATGPT_PROMPT=$(git config --get ai.prompt)
if [ -z "$CHATGPT_PROMPT" ]; then
CHATGPT_PROMPT="$DEFAULT_CHATGPT_PROMPT"
log_message "INFO" "Using default AI prompt"
fi
MODEL=$(git config --get ai.model)
if [ -z "$MODEL" ]; then
MODEL="$DEFAULT_MODEL"
log_message "INFO" "Using default model: $MODEL"
fi
MAX_TOKENS=$(git config --get ai.maxtokens)
if [ -z "$MAX_TOKENS" ]; then
MAX_TOKENS="$DEFAULT_MAX_TOKENS"
log_message "INFO" "Using default max tokens: $MAX_TOKENS"
elif ! [[ "$MAX_TOKENS" =~ ^[0-9]+$ ]] || [ "$MAX_TOKENS" -lt 1 ] || [ "$MAX_TOKENS" -gt 200000 ]; then
log_message "WARN" "Invalid max tokens value: $MAX_TOKENS. Using default: $DEFAULT_MAX_TOKENS"
MAX_TOKENS="$DEFAULT_MAX_TOKENS"
fi
API_ENDPOINT=$(git config --get ai.endpoint)
if [ -z "$API_ENDPOINT" ]; then
API_ENDPOINT="$DEFAULT_API_ENDPOINT"
log_message "INFO" "Using default API endpoint"
elif ! [[ "$API_ENDPOINT" =~ ^https?:// ]]; then
log_message "ERROR" "Invalid API endpoint format: $API_ENDPOINT. Must start with http:// or https://"
echo "chore: update files (API endpoint configuration error)"
exit 0
fi
# Initialize logging
log_message "INFO" "Starting prepare-commit-msg hook with arguments: $@"
# Check if a commit message file is provided or exists
if [ ! -f "$COMMIT_MSG_FILE" ]; then
log_message "ERROR" "Commit message file does not exist or was not provided: $COMMIT_MSG_FILE"
echo "chore: update files (commit message file error)"
exit 0
fi
# Collect the git diff
DIFF=$(git diff --cached)
# Validate that there are staged changes
if [ -z "$DIFF" ]; then
log_message "WARN" "No staged changes found in git diff --cached"
echo "chore: update files (no staged changes)"
exit 0
fi
log_message "INFO" "Found staged changes, diff length: ${#DIFF} characters"
# Replace placeholder with actual diff in the prompt
COMMIT_PROMPT=${CHATGPT_PROMPT/\{\{diff\}\}/"$DIFF"}
log_message "DEBUG" "Generated prompt: $COMMIT_PROMPT"
# Use jq to construct the JSON payload
DATA=$(jq -n --arg prompt "$COMMIT_PROMPT" --arg model "$MODEL" --argjson max_tokens "$MAX_TOKENS" \
'{
"model": $model,
"messages": [
{
"role": "user",
"content": $prompt
}
],
"temperature": 0.5,
"max_tokens": $max_tokens
}')
# Make sure jq successfully created the payload
if [ -z "$DATA" ]; then
log_message "ERROR" "Failed to construct JSON payload with jq"
FALLBACK_MSG=$(generate_fallback_message "$DIFF")
echo "$FALLBACK_MSG"
exit 0
fi
log_message "INFO" "JSON payload constructed successfully"
# Get the OpenAI API key from the git configuration
API_KEY=$(git config --get openai.apikey)
if [ -z "$API_KEY" ]; then
log_message "ERROR" "OpenAI API key is not set in git configuration"
FALLBACK_MSG=$(generate_fallback_message "$DIFF")
echo "$FALLBACK_MSG"
exit 0
elif [ ${#API_KEY} -lt 20 ]; then
log_message "ERROR" "OpenAI API key appears to be invalid (too short)"
FALLBACK_MSG=$(generate_fallback_message "$DIFF")
echo "$FALLBACK_MSG"
exit 0
fi
log_message "INFO" "API key validated successfully"
# Make the API request
API_URL="$API_ENDPOINT"
log_message "INFO" "Making API request to: $API_URL"
RESPONSE=$(curl -s -X POST "$API_URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
--data "$DATA" \
--max-time 30 \
--write-out "HTTPSTATUS:%{http_code}")
# Extract HTTP status code and response body
HTTP_STATUS=$(echo "$RESPONSE" | grep -o "HTTPSTATUS:[0-9]*" | cut -d: -f2)
RESPONSE_BODY=$(echo "$RESPONSE" | sed 's/HTTPSTATUS:[0-9]*$//')
log_message "INFO" "API response HTTP status: $HTTP_STATUS"
# Check if the response was successful
if [ -z "$RESPONSE_BODY" ]; then
log_message "ERROR" "No response from the API"
FALLBACK_MSG=$(generate_fallback_message "$DIFF")
echo "$FALLBACK_MSG"
exit 0
fi
# Check HTTP status
if [ "$HTTP_STATUS" != "200" ]; then
log_message "ERROR" "API request failed with HTTP status $HTTP_STATUS"
# Try to extract error message from response
API_ERROR=$(echo "$RESPONSE_BODY" | jq -r '.error.message // .error // "Unknown API error"' 2>/dev/null || echo "Unknown API error")
log_message "ERROR" "API error: $API_ERROR"
FALLBACK_MSG=$(generate_fallback_message "$DIFF")
echo "$FALLBACK_MSG"
exit 0
fi
# Validate JSON response structure
if ! echo "$RESPONSE_BODY" | jq -e '.choices[0].message.content' > /dev/null 2>&1; then
log_message "ERROR" "Invalid API response structure"
log_message "DEBUG" "Response body: $RESPONSE_BODY"
FALLBACK_MSG=$(generate_fallback_message "$DIFF")
echo "$FALLBACK_MSG"
exit 0
fi
# Parse the response to get the commit message
COMMIT_MSG=$(echo "$RESPONSE_BODY" | jq -r '.choices[0].message.content')
# Check if the commit message is valid
if [ -z "$COMMIT_MSG" ] || [ "$COMMIT_MSG" = "null" ]; then
log_message "ERROR" "AI generated null or empty commit message"
FALLBACK_MSG=$(generate_fallback_message "$DIFF")
echo "$FALLBACK_MSG"
exit 0
fi
# Validate commit message format (basic check)
if ! echo "$COMMIT_MSG" | grep -qE "^(feat|fix|docs|style|refactor|test|chore):\s*.+"; then
log_message "WARN" "AI generated message doesn't follow conventional commits format: $COMMIT_MSG"
# Still use it but log the warning
fi
log_message "INFO" "Successfully generated commit message: $COMMIT_MSG"
# Output the commit message
echo "$COMMIT_MSG"