-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgit-commit.sh
More file actions
executable file
·196 lines (158 loc) · 5.41 KB
/
git-commit.sh
File metadata and controls
executable file
·196 lines (158 loc) · 5.41 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
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# set -x
# Debug mode flag
DEBUG=false
ECHO=false
# Debug function
debug_log() {
if [ "$DEBUG" = true ]; then
echo "DEBUG: $1"
if [ ! -z "$2" ]; then
echo "DEBUG: Content >>>"
echo "$2"
echo "DEBUG: <<<"
fi
fi
}
# Replace all linebreaks with proper JSON escaping
function replace_linebreaks() {
local input="$1"
printf '%s' "$input" | tr '\n' '\\n' | sed 's/\n$//'
}
while [[ $# -gt 0 ]]; do
case $1 in
--debug)
DEBUG=true
shift
;;
--echo)
ECHO=true
shift
;;
*)
echo "Error: Unknown argument $1"
exit 1
;;
esac
done
# Get git changes and clean up any tabs
CHANGES=$(git diff --cached --name-status | tr '\t' ' ' | tr '\n' ' ' | sed 's/ */ /g')
# Get git diff for context
DIFF_CONTENT=$(git diff --cached --no-color)
if [ -z "$CHANGES" ]; then
echo "No staged changes found. Please stage your changes using 'git add' first."
exit 1
fi
debug_log "Git changes detected" "$CHANGES"
# Remove all linebreaks from CHANGES
CHANGES=$(replace_linebreaks "$CHANGES")
# Format changes into a single line and replace \M with newlines
FORMATTED_CHANGES=$(echo "$CHANGES" | sed 's/\\M/\n/g' | tr '\n' ' ' | sed 's/ */ /g')
# FORMATTED_DIFFS=$(escape_prompt "$DIFF_CONTENT")
FORMATTED_DIFF=$(echo "$DIFF_CONTENT" | tr '\n' '\\n' | sed 's/\\M/\\n/g' | sed 's/\\nM/\\n/g' | sed 's/"/\\"/g')
PROMPT=$(cat <<EOF
Generate a conventional commit message for these file changes:
$FORMATTED_CHANGES
and this git diff:
$DIFF_CONTENT
## Instructions:
- Start with a summary in imperitive mood.
- Explain the 'why' behind changes, when possible. Don\'t make anything up.
- Use bullet points for multiple changes, except for when the 'why' is attached to item
- Format should be:
<type>[optional scope wrapped in parens]: <description>
[optional body]
[optional footer(s)]
Rules:
The commit contains the following structural elements, to communicate intent to the consumers of your library:
1. fix: a commit of the type fix patches a bug in your codebase (this correlates with PATCH in Semantic Versioning).
2. feat: a commit of the type feat introduces a new feature to the codebase (this correlates with MINOR in Semantic Versioning).
3. BREAKING CHANGE: a commit that has a footer BREAKING CHANGE:, or appends a ! after the type/scope, introduces a breaking API change (correlating with MAJOR in Semantic Versioning). A BREAKING CHANGE can be part of commits of any type.
4. types other than fix: and feat: are allowed, for example @commitlint/config-conventional (based on the Angular convention) recommends build:, chore:, ci:, docs:, style:, refactor:, perf:, test:, and others.
5. footers other than BREAKING CHANGE: <description> may be provided and follow a convention similar to git trailer format.
6. Use 'fix' for minor changes
7. Do not wrap your response in triple backticks
8. Response should be the commit message only, no explanations as it will be passed directly to git.
Additional types are not mandated by the Conventional Commits specification, and have no implicit effect in Semantic Versioning (unless they include a BREAKING CHANGE). A scope may be provided to a commit’s type, to provide additional contextual information and is contained within parenthesis, e.g., feat(parser): add ability to parse arrays.
EOF
)
# Make the prompt safe for json
SAFE_PROMPT=$(jq -Rn --arg str "$PROMPT" '$str')
debug_log "Prompt" "$SAFE_PROMPT"
BASE_URL="https://api.githubcopilot.com/github/chat/threads"
HEADERS=( "Authorization: Bearer $(gh auth token)" )
CURL_HEADERS=()
for header in "${HEADERS[@]}"; do
CURL_HEADERS+=(-H "$header")
done
# Create Thread
REQUEST_BODY="{}"
RESPONSE=$(curl -s -X POST "$BASE_URL" \
"${CURL_HEADERS[@]}" \
-d "$REQUEST_BODY" \
)
debug_log "Response" "$RESPONSE"
THREAD_ID=$(echo $RESPONSE | jq -r .thread_id)
if [ $? -ne 0 ]; then
echo "Failed to fetch thread id"
exit 1
fi
debug_log "Thread ID" "$THREAD_ID"
# Send prompt to gh
REQUEST_BODY=$(cat <<EOF
{
"content": $SAFE_PROMPT,
"intent": "cli-suggest",
"references": [
{
"type": "cli-command",
"program": "string"
}
]
}
EOF
)
debug_log "Request Body" "$REQUEST_BODY"
RESPONSE=$(curl -s -X POST "$BASE_URL/$THREAD_ID/messages" \
"${CURL_HEADERS[@]}" \
-d "$REQUEST_BODY" \
)
debug_log "Response" "$( echo $RESPONSE | jq)"
CONTENT=$(echo "$RESPONSE" | jq -r .message.content)
if [ $? -ne 0 ]; then
echo "Failed to fetch git message"
exit 1
fi
# Clean the message:
# 1. Preserve the structure of the commit message
# 2. Clean up escape sequences
COMMIT_FULL=$(echo "$CONTENT" |
sed 's/\\n/\n/g' |
sed 's/\\r//g' |
sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' |
sed 's/\\[[:alpha:]]//g')
debug_log "Extracted commit message" "$COMMIT_FULL"
if [ -z "$COMMIT_FULL" ]; then
echo "Failed to generate commit message. API response:"
exit 1
fi
# Execute git commit
if [ "$ECHO" == true ]; then
echo "$COMMIT_FULL"
exit
fi
TMP_FILE="$(mktemp)"
echo "$COMMIT_FULL" > $TMP_FILE
debug_log "Executing git commit"
EDITOR_CMD="${GIT_EDITOR:-${EDITOR:-nvim}}"
GIT_EDITOR="$EDITOR_CMD" git commit --edit -F "$TMP_FILE"
if [ $? -ne 0 ]; then
echo "Failed to commit changes"
rm $TMP_FILE
exit 1
fi
rm $TMP_FILE
echo "Successfully committed with message:"
echo "$COMMIT_FULL"
debug_log "Script completed successfully"