git-copilot-commit 0.1.12__tar.gz → 0.1.14__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.1.12 → git_copilot_commit-0.1.14}/PKG-INFO +1 -1
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/src/git_copilot_commit/cli.py +1 -1
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/src/git_copilot_commit/git.py +20 -84
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/.github/workflows/ci.yml +0 -0
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/.gitignore +0 -0
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/.python-version +0 -0
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/LICENSE +0 -0
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/README.md +0 -0
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/pyproject.toml +0 -0
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/src/git_copilot_commit/__init__.py +0 -0
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/src/git_copilot_commit/py.typed +0 -0
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/src/git_copilot_commit/settings.py +0 -0
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/src/git_copilot_commit/version.py +0 -0
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/uv.lock +0 -0
- {git_copilot_commit-0.1.12 → git_copilot_commit-0.1.14}/vhs/demo.vhs +0 -0
|
@@ -9,7 +9,7 @@ from rich.panel import Panel
|
|
|
9
9
|
from rich.table import Table
|
|
10
10
|
import rich
|
|
11
11
|
|
|
12
|
-
from pycopilot.copilot import Copilot
|
|
12
|
+
from pycopilot.copilot import Copilot # type: ignore
|
|
13
13
|
from pycopilot.auth import Authentication
|
|
14
14
|
from .git import GitRepository, GitError, NotAGitRepositoryError, GitStatus
|
|
15
15
|
from .settings import Settings
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import
|
|
5
|
-
from enum import Enum
|
|
4
|
+
from typing import Tuple
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class GitError(Exception):
|
|
@@ -23,19 +22,6 @@ class GitCommandError(GitError):
|
|
|
23
22
|
pass
|
|
24
23
|
|
|
25
24
|
|
|
26
|
-
class FileStatus(Enum):
|
|
27
|
-
"""Git file status codes."""
|
|
28
|
-
|
|
29
|
-
MODIFIED = "M"
|
|
30
|
-
ADDED = "A"
|
|
31
|
-
DELETED = "D"
|
|
32
|
-
RENAMED = "R"
|
|
33
|
-
COPIED = "C"
|
|
34
|
-
UNMERGED = "U"
|
|
35
|
-
UNTRACKED = "?"
|
|
36
|
-
IGNORED = "!"
|
|
37
|
-
|
|
38
|
-
|
|
39
25
|
@dataclass
|
|
40
26
|
class GitFile:
|
|
41
27
|
"""Represents a file in git status."""
|
|
@@ -64,7 +50,7 @@ class GitFile:
|
|
|
64
50
|
class GitStatus:
|
|
65
51
|
"""Structured representation of git status."""
|
|
66
52
|
|
|
67
|
-
files:
|
|
53
|
+
files: list[GitFile]
|
|
68
54
|
staged_diff: str
|
|
69
55
|
unstaged_diff: str
|
|
70
56
|
|
|
@@ -84,17 +70,17 @@ class GitStatus:
|
|
|
84
70
|
return any(f.is_untracked for f in self.files)
|
|
85
71
|
|
|
86
72
|
@property
|
|
87
|
-
def staged_files(self) ->
|
|
73
|
+
def staged_files(self) -> list[GitFile]:
|
|
88
74
|
"""Get list of files with staged changes."""
|
|
89
75
|
return [f for f in self.files if f.is_staged]
|
|
90
76
|
|
|
91
77
|
@property
|
|
92
|
-
def unstaged_files(self) ->
|
|
78
|
+
def unstaged_files(self) -> list[GitFile]:
|
|
93
79
|
"""Get list of files with unstaged changes."""
|
|
94
80
|
return [f for f in self.files if not f.is_staged and not f.is_untracked]
|
|
95
81
|
|
|
96
82
|
@property
|
|
97
|
-
def untracked_files(self) ->
|
|
83
|
+
def untracked_files(self) -> list[GitFile]:
|
|
98
84
|
"""Get list of untracked files."""
|
|
99
85
|
return [f for f in self.files if f.is_untracked]
|
|
100
86
|
|
|
@@ -109,7 +95,7 @@ class GitStatus:
|
|
|
109
95
|
class GitRepository:
|
|
110
96
|
"""Encapsulates git repository operations."""
|
|
111
97
|
|
|
112
|
-
def __init__(self, repo_path:
|
|
98
|
+
def __init__(self, repo_path: Path | None = None, timeout: int = 30):
|
|
113
99
|
"""
|
|
114
100
|
Initialize GitRepository.
|
|
115
101
|
|
|
@@ -132,7 +118,7 @@ class GitRepository:
|
|
|
132
118
|
raise NotAGitRepositoryError(f"{self.repo_path} is not a git repository")
|
|
133
119
|
|
|
134
120
|
def _run_git_command(
|
|
135
|
-
self, args:
|
|
121
|
+
self, args: list[str], check: bool = True
|
|
136
122
|
) -> subprocess.CompletedProcess:
|
|
137
123
|
"""
|
|
138
124
|
Run a git command and return the result.
|
|
@@ -188,7 +174,7 @@ class GitRepository:
|
|
|
188
174
|
unstaged_diff=unstaged_diff_result.stdout,
|
|
189
175
|
)
|
|
190
176
|
|
|
191
|
-
def _parse_status_output(self, status_output: str) ->
|
|
177
|
+
def _parse_status_output(self, status_output: str) -> list[GitFile]:
|
|
192
178
|
"""Parse git status --porcelain output into GitFile objects."""
|
|
193
179
|
files = []
|
|
194
180
|
for line in status_output.strip().split("\n"):
|
|
@@ -212,27 +198,23 @@ class GitRepository:
|
|
|
212
198
|
|
|
213
199
|
return files
|
|
214
200
|
|
|
215
|
-
def stage_files(self, paths:
|
|
201
|
+
def stage_files(self, paths: list[str] | None = None) -> None:
|
|
216
202
|
"""
|
|
217
203
|
Stage files for commit.
|
|
218
204
|
|
|
219
205
|
Args:
|
|
220
|
-
|
|
206
|
+
paths: List of file paths to stage. If None, stages all files (git add .).
|
|
221
207
|
"""
|
|
222
208
|
if paths is None:
|
|
223
209
|
self._run_git_command(["add", "."])
|
|
224
210
|
else:
|
|
225
|
-
|
|
226
|
-
batch_size = 100
|
|
227
|
-
for i in range(0, len(paths), batch_size):
|
|
228
|
-
batch = paths[i : i + batch_size]
|
|
229
|
-
self._run_git_command(["add"] + batch)
|
|
211
|
+
self._run_git_command(["add"] + paths)
|
|
230
212
|
|
|
231
213
|
def stage_modified(self) -> None:
|
|
232
214
|
"""Stage all modified files (git add -u)."""
|
|
233
215
|
self._run_git_command(["add", "-u"])
|
|
234
216
|
|
|
235
|
-
def unstage_files(self, paths:
|
|
217
|
+
def unstage_files(self, paths: list[str] | None = None) -> None:
|
|
236
218
|
"""
|
|
237
219
|
Unstage files.
|
|
238
220
|
|
|
@@ -244,7 +226,7 @@ class GitRepository:
|
|
|
244
226
|
else:
|
|
245
227
|
self._run_git_command(["reset", "HEAD"] + paths)
|
|
246
228
|
|
|
247
|
-
def commit(self, message:
|
|
229
|
+
def commit(self, message: str | None = None, use_editor: bool = False) -> str:
|
|
248
230
|
"""
|
|
249
231
|
Create a commit with the given message or using git's editor.
|
|
250
232
|
|
|
@@ -261,16 +243,18 @@ class GitRepository:
|
|
|
261
243
|
if use_editor:
|
|
262
244
|
import tempfile
|
|
263
245
|
import os
|
|
264
|
-
|
|
246
|
+
|
|
265
247
|
# Create temp file with message as starting point
|
|
266
|
-
with tempfile.NamedTemporaryFile(
|
|
248
|
+
with tempfile.NamedTemporaryFile(
|
|
249
|
+
mode="w", suffix=".txt", delete=False
|
|
250
|
+
) as f:
|
|
267
251
|
if message:
|
|
268
252
|
f.write(message)
|
|
269
253
|
temp_file = f.name
|
|
270
|
-
|
|
254
|
+
|
|
271
255
|
try:
|
|
272
256
|
args = ["commit", "-e", "-F", temp_file]
|
|
273
|
-
|
|
257
|
+
|
|
274
258
|
# Run interactively without capturing output
|
|
275
259
|
cmd = ["git"] + args
|
|
276
260
|
subprocess.run(
|
|
@@ -297,7 +281,7 @@ class GitRepository:
|
|
|
297
281
|
sha_result = self._run_git_command(["rev-parse", "HEAD"])
|
|
298
282
|
return sha_result.stdout.strip()
|
|
299
283
|
|
|
300
|
-
def get_recent_commits(self, limit: int = 10) ->
|
|
284
|
+
def get_recent_commits(self, limit: int = 10) -> list[Tuple[str, str]]:
|
|
301
285
|
"""
|
|
302
286
|
Get recent commit history.
|
|
303
287
|
|
|
@@ -318,51 +302,3 @@ class GitRepository:
|
|
|
318
302
|
commits.append((sha, message))
|
|
319
303
|
|
|
320
304
|
return commits
|
|
321
|
-
|
|
322
|
-
def get_current_branch(self) -> str:
|
|
323
|
-
"""Get the name of the current branch."""
|
|
324
|
-
result = self._run_git_command(["rev-parse", "--abbrev-ref", "HEAD"])
|
|
325
|
-
return result.stdout.strip()
|
|
326
|
-
|
|
327
|
-
def has_changes(self) -> bool:
|
|
328
|
-
"""Check if there are any changes (staged, unstaged, or untracked)."""
|
|
329
|
-
status = self.get_status()
|
|
330
|
-
return (
|
|
331
|
-
status.has_staged_changes
|
|
332
|
-
or status.has_unstaged_changes
|
|
333
|
-
or status.has_untracked_files
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
def get_diff(self, staged: bool = False, paths: Optional[List[str]] = None) -> str:
|
|
337
|
-
"""
|
|
338
|
-
Get diff output.
|
|
339
|
-
|
|
340
|
-
Args:
|
|
341
|
-
staged: Whether to get staged diff (--staged flag).
|
|
342
|
-
paths: Specific paths to diff.
|
|
343
|
-
|
|
344
|
-
Returns:
|
|
345
|
-
Diff output as string.
|
|
346
|
-
"""
|
|
347
|
-
args = ["diff"]
|
|
348
|
-
if staged:
|
|
349
|
-
args.append("--staged")
|
|
350
|
-
if paths:
|
|
351
|
-
args.extend(["--"] + paths)
|
|
352
|
-
|
|
353
|
-
result = self._run_git_command(args)
|
|
354
|
-
return result.stdout
|
|
355
|
-
|
|
356
|
-
def get_file_content(self, path: str, revision: str = "HEAD") -> str:
|
|
357
|
-
"""
|
|
358
|
-
Get content of a file at a specific revision.
|
|
359
|
-
|
|
360
|
-
Args:
|
|
361
|
-
path: File path.
|
|
362
|
-
revision: Git revision (default: HEAD).
|
|
363
|
-
|
|
364
|
-
Returns:
|
|
365
|
-
File content as string.
|
|
366
|
-
"""
|
|
367
|
-
result = self._run_git_command(["show", f"{revision}:{path}"])
|
|
368
|
-
return result.stdout
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|