git-copilot-commit 0.3.6__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/PKG-INFO +2 -2
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/pyproject.toml +1 -1
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/src/git_copilot_commit/cli.py +40 -98
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/src/git_copilot_commit/git.py +15 -3
- git_copilot_commit-0.4.0/src/git_copilot_commit/github_copilot.py +824 -0
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/src/git_copilot_commit/settings.py +1 -1
- git_copilot_commit-0.4.0/uv.lock +205 -0
- git_copilot_commit-0.3.6/uv.lock +0 -1174
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/.github/workflows/ci.yml +0 -0
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/.gitignore +0 -0
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/.justfile +0 -0
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/.python-version +0 -0
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/LICENSE +0 -0
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/README.md +0 -0
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/src/git_copilot_commit/__init__.py +0 -0
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/src/git_copilot_commit/prompts/commit-message-generator-prompt.md +0 -0
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/src/git_copilot_commit/py.typed +0 -0
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/src/git_copilot_commit/version.py +0 -0
- {git_copilot_commit-0.3.6 → git_copilot_commit-0.4.0}/vhs/demo.vhs +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-copilot-commit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Automatically generate and commit changes using copilot
|
|
5
5
|
Author-email: Dheepak Krishnamurthy <1813121+kdheepak@users.noreply.github.com>
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Requires-Python: >=3.12
|
|
8
|
-
Requires-Dist:
|
|
8
|
+
Requires-Dist: httpx>=0.28.0
|
|
9
9
|
Requires-Dist: platformdirs>=4.0.0
|
|
10
10
|
Requires-Dist: rich>=14.0.0
|
|
11
11
|
Requires-Dist: typer>=0.16.0
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
git-copilot-commit - AI-powered Git commit assistant
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import rich
|
|
6
8
|
import typer
|
|
7
9
|
from rich.console import Console
|
|
8
|
-
from rich.prompt import Confirm
|
|
9
10
|
from rich.panel import Panel
|
|
10
|
-
import
|
|
11
|
-
from pathlib import Path
|
|
11
|
+
from rich.prompt import Confirm
|
|
12
12
|
|
|
13
|
-
from litellm import completion
|
|
14
13
|
from .git import GitRepository, GitError, NotAGitRepositoryError
|
|
15
14
|
from .settings import Settings
|
|
16
15
|
from .version import __version__
|
|
16
|
+
from . import github_copilot
|
|
17
17
|
|
|
18
18
|
console = Console()
|
|
19
19
|
app = typer.Typer(help=__doc__, add_completion=False)
|
|
@@ -84,29 +84,6 @@ def load_system_prompt() -> str:
|
|
|
84
84
|
raise typer.Exit(1)
|
|
85
85
|
|
|
86
86
|
|
|
87
|
-
def ask(prompt, model) -> str:
|
|
88
|
-
response = completion(
|
|
89
|
-
model=model,
|
|
90
|
-
messages=[
|
|
91
|
-
{"role": "system", "content": load_system_prompt()},
|
|
92
|
-
{"role": "user", "content": prompt},
|
|
93
|
-
],
|
|
94
|
-
extra_headers={
|
|
95
|
-
"editor-version": "vscode/1.85.1",
|
|
96
|
-
"Copilot-Integration-Id": "vscode-chat",
|
|
97
|
-
},
|
|
98
|
-
)
|
|
99
|
-
text = response.choices[0].message.content
|
|
100
|
-
text = text.strip()
|
|
101
|
-
# Remove triple backticks if they wrap the entire text
|
|
102
|
-
if text.startswith("```") and text.endswith("```"):
|
|
103
|
-
text = text[3:-3].strip()
|
|
104
|
-
# Otherwise remove single backticks if they wrap the entire text
|
|
105
|
-
elif text.startswith("`") and text.endswith("`"):
|
|
106
|
-
text = text[1:-1].strip()
|
|
107
|
-
return text
|
|
108
|
-
|
|
109
|
-
|
|
110
87
|
def generate_commit_message(
|
|
111
88
|
repo: GitRepository, model: str | None = None, context: str = ""
|
|
112
89
|
) -> str:
|
|
@@ -134,35 +111,33 @@ def generate_commit_message(
|
|
|
134
111
|
prompt = "\n".join(prompt_parts)
|
|
135
112
|
|
|
136
113
|
if model is None:
|
|
137
|
-
model = "
|
|
114
|
+
model = "gpt-5.1-codex"
|
|
138
115
|
|
|
139
|
-
if
|
|
140
|
-
model =
|
|
116
|
+
if model.startswith("github_copilot/"):
|
|
117
|
+
model = model.replace("github_copilot/", "")
|
|
141
118
|
|
|
142
|
-
|
|
143
|
-
return ask(prompt, model=model)
|
|
144
|
-
except Exception as _:
|
|
145
|
-
console.print(
|
|
146
|
-
"Prompt failed, falling back to simpler commit message generation."
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
fallback_prompt_parts = [
|
|
150
|
-
"`git status`:\n",
|
|
151
|
-
f"```\n{status.get_porcelain_output()}\n```",
|
|
152
|
-
]
|
|
153
|
-
|
|
154
|
-
if context.strip():
|
|
155
|
-
fallback_prompt_parts.insert(
|
|
156
|
-
0, f"User-provided context:\n\n{context.strip()}\n\n"
|
|
157
|
-
)
|
|
119
|
+
return github_copilot.ask(prompt, model=model)
|
|
158
120
|
|
|
159
|
-
fallback_prompt_parts.append(
|
|
160
|
-
"\nGenerate a conventional commit message based on the git status above:"
|
|
161
|
-
)
|
|
162
121
|
|
|
163
|
-
|
|
122
|
+
def commit_with_retry_no_verify(
|
|
123
|
+
repo: GitRepository, message: str, use_editor: bool = False
|
|
124
|
+
) -> str:
|
|
125
|
+
"""Run commit and offer one retry with -n on failure."""
|
|
126
|
+
try:
|
|
127
|
+
return repo.commit(message, use_editor=use_editor)
|
|
128
|
+
except GitError as e:
|
|
129
|
+
console.print(f"[red]Commit failed: {e}[/red]")
|
|
130
|
+
if not Confirm.ask(
|
|
131
|
+
"Retry commit with [bold]`-n`[/] (skip hooks) using the same commit message?",
|
|
132
|
+
default=True,
|
|
133
|
+
):
|
|
134
|
+
raise typer.Exit(1)
|
|
164
135
|
|
|
165
|
-
|
|
136
|
+
try:
|
|
137
|
+
return repo.commit(message, use_editor=use_editor, no_verify=True)
|
|
138
|
+
except GitError as retry_error:
|
|
139
|
+
console.print(f"[red]Commit with -n failed: {retry_error}[/red]")
|
|
140
|
+
raise typer.Exit(1)
|
|
166
141
|
|
|
167
142
|
|
|
168
143
|
@app.command()
|
|
@@ -192,6 +167,14 @@ def commit(
|
|
|
192
167
|
console.print("[red]Error: Not in a git repository[/red]")
|
|
193
168
|
raise typer.Exit(1)
|
|
194
169
|
|
|
170
|
+
try:
|
|
171
|
+
existing_credentials = github_copilot.load_credentials()
|
|
172
|
+
except Exception as _:
|
|
173
|
+
existing_credentials = None
|
|
174
|
+
|
|
175
|
+
if existing_credentials is None:
|
|
176
|
+
github_copilot.login()
|
|
177
|
+
|
|
195
178
|
# Load settings and use default model if none provided
|
|
196
179
|
settings = Settings()
|
|
197
180
|
if model is None:
|
|
@@ -255,11 +238,7 @@ def commit(
|
|
|
255
238
|
# Confirm commit or edit message (skip if --yes flag is used)
|
|
256
239
|
if yes:
|
|
257
240
|
# Automatically commit with generated message
|
|
258
|
-
|
|
259
|
-
commit_sha = repo.commit(commit_message)
|
|
260
|
-
except GitError as e:
|
|
261
|
-
console.print(f"[red]Commit failed: {e}[/red]")
|
|
262
|
-
raise typer.Exit(1)
|
|
241
|
+
commit_sha = commit_with_retry_no_verify(repo, commit_message)
|
|
263
242
|
else:
|
|
264
243
|
choice = typer.prompt(
|
|
265
244
|
"Choose action: (c)ommit, (e)dit message, (q)uit",
|
|
@@ -273,18 +252,12 @@ def commit(
|
|
|
273
252
|
elif choice == "e":
|
|
274
253
|
# Use git's built-in editor with generated message as template
|
|
275
254
|
console.print("[cyan]Opening git editor...[/cyan]")
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
console.print(f"[red]Commit failed: {e}[/red]")
|
|
280
|
-
raise typer.Exit(1)
|
|
255
|
+
commit_sha = commit_with_retry_no_verify(
|
|
256
|
+
repo, commit_message, use_editor=True
|
|
257
|
+
)
|
|
281
258
|
elif choice == "c":
|
|
282
259
|
# Commit with generated message
|
|
283
|
-
|
|
284
|
-
commit_sha = repo.commit(commit_message)
|
|
285
|
-
except GitError as e:
|
|
286
|
-
console.print(f"[red]Commit failed: {e}[/red]")
|
|
287
|
-
raise typer.Exit(1)
|
|
260
|
+
commit_sha = commit_with_retry_no_verify(repo, commit_message)
|
|
288
261
|
else:
|
|
289
262
|
console.print("Invalid choice. Commit cancelled.")
|
|
290
263
|
raise typer.Exit()
|
|
@@ -293,36 +266,5 @@ def commit(
|
|
|
293
266
|
console.print(f"[green]✓ Successfully committed: {commit_sha[:8]}[/green]")
|
|
294
267
|
|
|
295
268
|
|
|
296
|
-
@app.command()
|
|
297
|
-
def config(
|
|
298
|
-
set_default_model: str | None = typer.Option(
|
|
299
|
-
None, "--set-default-model", help="Set default model for commit messages"
|
|
300
|
-
),
|
|
301
|
-
show: bool = typer.Option(False, "--show", help="Show current configuration"),
|
|
302
|
-
):
|
|
303
|
-
"""Manage application configuration."""
|
|
304
|
-
settings = Settings()
|
|
305
|
-
|
|
306
|
-
if set_default_model:
|
|
307
|
-
settings.default_model = set_default_model
|
|
308
|
-
console.print(f"[green]✓ Default model set to: {set_default_model}[/green]")
|
|
309
|
-
|
|
310
|
-
if show or (not set_default_model):
|
|
311
|
-
console.print("\n[bold]Current Configuration:[/bold]")
|
|
312
|
-
default_model = settings.default_model
|
|
313
|
-
if default_model:
|
|
314
|
-
console.print(f"Default model: [cyan]{default_model}[/cyan]")
|
|
315
|
-
else:
|
|
316
|
-
console.print("Default model: [dim]not set[/dim]")
|
|
317
|
-
|
|
318
|
-
active_prompt = get_active_prompt_path()
|
|
319
|
-
if active_prompt:
|
|
320
|
-
console.print(f"Active prompt file: [cyan]{active_prompt}[/cyan]")
|
|
321
|
-
else:
|
|
322
|
-
console.print("Active prompt file: [red]not found[/red]")
|
|
323
|
-
|
|
324
|
-
console.print(f"Config file: [dim]{settings.config_file}[/dim]")
|
|
325
|
-
|
|
326
|
-
|
|
327
269
|
if __name__ == "__main__":
|
|
328
270
|
app()
|
|
@@ -226,13 +226,19 @@ class GitRepository:
|
|
|
226
226
|
else:
|
|
227
227
|
self._run_git_command(["reset", "HEAD"] + paths)
|
|
228
228
|
|
|
229
|
-
def commit(
|
|
229
|
+
def commit(
|
|
230
|
+
self,
|
|
231
|
+
message: str | None = None,
|
|
232
|
+
use_editor: bool = False,
|
|
233
|
+
no_verify: bool = False,
|
|
234
|
+
) -> str:
|
|
230
235
|
"""
|
|
231
236
|
Create a commit with the given message or using git's editor.
|
|
232
237
|
|
|
233
238
|
Args:
|
|
234
239
|
message: Commit message. Used as template if use_editor is True.
|
|
235
240
|
use_editor: Whether to use git's configured editor.
|
|
241
|
+
no_verify: Skip pre-commit and commit-msg hooks (git commit -n).
|
|
236
242
|
|
|
237
243
|
Returns:
|
|
238
244
|
Commit SHA.
|
|
@@ -253,7 +259,10 @@ class GitRepository:
|
|
|
253
259
|
temp_file = f.name
|
|
254
260
|
|
|
255
261
|
try:
|
|
256
|
-
args = ["commit"
|
|
262
|
+
args = ["commit"]
|
|
263
|
+
if no_verify:
|
|
264
|
+
args.append("-n")
|
|
265
|
+
args.extend(["-e", "-F", temp_file])
|
|
257
266
|
|
|
258
267
|
# Run interactively without capturing output
|
|
259
268
|
cmd = ["git"] + args
|
|
@@ -273,7 +282,10 @@ class GitRepository:
|
|
|
273
282
|
else:
|
|
274
283
|
if message is None:
|
|
275
284
|
raise ValueError("message is required when use_editor is False")
|
|
276
|
-
args = ["commit"
|
|
285
|
+
args = ["commit"]
|
|
286
|
+
if no_verify:
|
|
287
|
+
args.append("-n")
|
|
288
|
+
args.extend(["-m", message])
|
|
277
289
|
|
|
278
290
|
self._run_git_command(args)
|
|
279
291
|
|