This repository was archived by the owner on Jan 23, 2026. It is now read-only.
forked from patrickstolc/commit-gpt-exercise
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
154 lines (123 loc) · 4.6 KB
/
main.py
File metadata and controls
154 lines (123 loc) · 4.6 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
import argparse
import time
from openai import OpenAI
from datamodel import CommitMessageSuggestion, DiffData
from diff import get_git_diffs
from utils import fallback_commit_message, is_git_repository
from rate_limiter import rate_limit
SYSTEM_PROMPT = """
You are a concise git commit message generator. You will be given labeled git diffs.
Rules:
- Only use information present in the diffs; do NOT invent changed behavior.
- Output JUST the commit message (no explanations).
- Primary format:
<type>(<scope>): <short summary>
[blank line]
Optional body lines, each max 72 characters.
- Short summary: imperative mood, <= 50 characters.
- Body: optional, explain why the change was made or notable details.
- Choose one type from: feat, fix, docs, chore, refactor, test, perf, ci, style.
- If scope is unclear, omit the parentheses: e.g. "fix: ..."
- If diffs are insufficient to produce a reliable message, return the single token:
NO_SUGGESTION
Examples:
Input:
[staged] file: a/src/app.py
@@
+def add(a, b):
+ return a + b
- def add_numbers(x, y):
- return x + y
Output:
refactor(src): rename add_numbers to add and simplify signature
Input:
[staged] file: a/README.md
@@
+Add instructions for setup
-Remove old setup notes
Output:
docs: update README setup instructions
Now generate a commit message for the diffs that follow. Output only the message.
"""
MODEL_NAME = "gemma3:4b"
API_BASE_URL = "http://localhost:11434/v1/"
MAX_RETRIES = 3 # Number of retries for the LLM API call
BASE_DELAY = 1 # Base delay in seconds between retries
client = OpenAI(
api_key="ollama",
base_url=API_BASE_URL,
)
def generate_commit_message(diffs: list[DiffData]) -> str | None:
diffs_str_list = []
for diff in diffs:
diff_section = f"[staged] file: a/{diff.filename}\n{diff.staged_changes}\n\n[unstaged] file: a/{diff.filename}\n{diff.unstaged_changes}\n"
diffs_str_list.append(diff_section)
diff_prompt = "\n".join(diffs_str_list)
for attempt in range(MAX_RETRIES):
commit_message = call_llm_api(diff_prompt)
if commit_message is not None:
return commit_message
print(f"Attempt {attempt + 1} failed. Retrying...")
delay = BASE_DELAY * (2**attempt)
time.sleep(delay)
return None
@rate_limit(max_calls=3, period=60)
def call_llm_api(prompt: str) -> str | None:
print(f"Calling LLM: model={MODEL_NAME} prompt_len={len(prompt)} chars")
try:
completion = client.beta.chat.completions.parse(
model=MODEL_NAME,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": prompt},
],
max_tokens=150,
temperature=0.2,
timeout=60,
response_format=CommitMessageSuggestion,
)
response = completion.choices[0].message
print(f"LLM response: {response}")
if response.parsed:
content = response.parsed.model_dump_json()
if content is not None:
suggestion = CommitMessageSuggestion.model_validate_json(content)
if suggestion.title == "NO_SUGGESTION":
print("LLM indicated no suggestion could be made.")
return None
full_message = suggestion.title
if suggestion.body:
full_message += f"\n\n{suggestion.body}"
return full_message
return None
except Exception as e:
print(f"LLM API call failed: {str(e)}")
return None
def main():
parser = argparse.ArgumentParser(
description="Generate commit message suggestions using an LLM."
)
parser.add_argument(
"repo_path", nargs="?", help="Path to the git repository", default="."
)
args = parser.parse_args()
if not is_git_repository(args.repo_path):
print("Error: The specified path is not a valid git repository.")
return
diffs = get_git_diffs(args.repo_path)
if not diffs:
print("No changes detected in the repository.")
return
try:
commit_message = generate_commit_message(diffs)
if commit_message is None:
print("No commit message generated by the LLM. Using fallback message.")
commit_message = fallback_commit_message(diffs)
print("Suggested commit message: ")
print(commit_message)
except Exception as e:
print(f"An error occurred while generating the commit message: {str(e)}")
print("Using fallback commit message.")
print(fallback_commit_message(diffs))
if __name__ == "__main__":
main()