git-copilot-commit 0.5.4__py3-none-any.whl → 0.5.6__py3-none-any.whl

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/cli.py CHANGED
@@ -5,6 +5,7 @@ git-copilot-commit - AI-powered Git commit assistant
5
5
  from dataclasses import dataclass
6
6
  from pathlib import Path
7
7
  import os
8
+ import re
8
9
  import sys
9
10
  from typing import Annotated, Sequence
10
11
 
@@ -94,6 +95,19 @@ class PreparedSplitCommit:
94
95
  patch_units: tuple[PatchUnit, ...]
95
96
 
96
97
 
98
+ CORE_CHANGE_COMMIT_TYPES = frozenset({"feat", "fix", "perf", "refactor", "revert"})
99
+ FOLLOW_UP_COMMIT_TYPE_PRIORITY = {
100
+ "test": 2,
101
+ "docs": 3,
102
+ "style": 4,
103
+ "chore": 4,
104
+ }
105
+ CONVENTIONAL_COMMIT_TYPE_PATTERN = re.compile(
106
+ r"^\s*([a-z]+)(?:\([^)\r\n]*\))?(?:!)?:",
107
+ re.IGNORECASE,
108
+ )
109
+
110
+
97
111
  def preprocess_cli_args(args: Sequence[str]) -> list[str]:
98
112
  """Normalize CLI arguments before Click parses them."""
99
113
  processed_args: list[str] = []
@@ -142,6 +156,37 @@ def preprocess_cli_args(args: Sequence[str]) -> list[str]:
142
156
  return processed_args
143
157
 
144
158
 
159
+ def extract_conventional_commit_type(message: str) -> str | None:
160
+ """Extract the Conventional Commit type from a generated title line."""
161
+ match = CONVENTIONAL_COMMIT_TYPE_PATTERN.match(message.strip())
162
+ if match is None:
163
+ return None
164
+
165
+ return match.group(1).lower()
166
+
167
+
168
+ def order_prepared_split_commits(
169
+ prepared_commits: Sequence[PreparedSplitCommit],
170
+ ) -> list[PreparedSplitCommit]:
171
+ """Order planned commits in a developer-friendly execution sequence."""
172
+
173
+ def sort_key(item: tuple[int, PreparedSplitCommit]) -> tuple[int, int]:
174
+ index, prepared_commit = item
175
+ commit_type = extract_conventional_commit_type(prepared_commit.message)
176
+
177
+ if commit_type in CORE_CHANGE_COMMIT_TYPES:
178
+ priority = 0
179
+ elif commit_type is None:
180
+ priority = 1
181
+ else:
182
+ priority = FOLLOW_UP_COMMIT_TYPE_PRIORITY.get(commit_type, 1)
183
+
184
+ return priority, index
185
+
186
+ ordered_items = sorted(enumerate(prepared_commits), key=sort_key)
187
+ return [prepared_commit for _, prepared_commit in ordered_items]
188
+
189
+
145
190
  def run(args: Sequence[str] | None = None) -> None:
146
191
  """Run the CLI entrypoint with argument normalization."""
147
192
  raw_args = list(args) if args is not None else sys.argv[1:]
@@ -862,6 +907,7 @@ def handle_split_commit_flow(
862
907
  context=context,
863
908
  http_client_config=http_client_config,
864
909
  )
910
+ prepared_commits = order_prepared_split_commits(prepared_commits)
865
911
 
866
912
  if len(prepared_commits) == 1:
867
913
  console.print(
git_copilot_commit/git.py CHANGED
@@ -139,7 +139,9 @@ class GitRepository:
139
139
  except subprocess.CalledProcessError:
140
140
  raise NotAGitRepositoryError(f"{self.cwd} is not a git repository")
141
141
  except subprocess.TimeoutExpired:
142
- raise GitCommandError("Git command timed out: git rev-parse --show-toplevel")
142
+ raise GitCommandError(
143
+ "Git command timed out: git rev-parse --show-toplevel"
144
+ )
143
145
 
144
146
  repo_root = result.stdout.strip()
145
147
  if not repo_root:
@@ -264,10 +266,18 @@ class GitRepository:
264
266
  result = self._run_git_command(["rev-parse", ref])
265
267
  return result.stdout.strip()
266
268
 
269
+ def has_commit(self, ref: str = "HEAD") -> bool:
270
+ """Return whether the provided ref resolves to a commit."""
271
+ result = self._run_git_command(
272
+ ["rev-parse", "--verify", "--quiet", f"{ref}^{{commit}}"],
273
+ check=False,
274
+ )
275
+ return result.returncode == 0
276
+
267
277
  def _parse_status_output(self, status_output: str) -> list[GitFile]:
268
278
  """Parse git status --porcelain output into GitFile objects."""
269
279
  files = []
270
- for line in status_output.strip().split("\n"):
280
+ for line in status_output.splitlines():
271
281
  if not line:
272
282
  continue
273
283
 
@@ -318,12 +328,13 @@ class GitRepository:
318
328
 
319
329
  def create_alternate_index(self, from_ref: str = "HEAD") -> AlternateGitIndex:
320
330
  """Create a temporary git index initialized from the provided ref."""
321
- fd, index_path = tempfile.mkstemp(
322
- prefix="git-copilot-commit-", suffix=".index"
323
- )
331
+ fd, index_path = tempfile.mkstemp(prefix="git-copilot-commit-", suffix=".index")
324
332
  os.close(fd)
325
333
  alternate_index = AlternateGitIndex(Path(index_path))
326
- self.read_tree(from_ref, index=alternate_index)
334
+ if from_ref == "HEAD" and not self.has_commit(from_ref):
335
+ self.read_empty_tree(index=alternate_index)
336
+ else:
337
+ self.read_tree(from_ref, index=alternate_index)
327
338
  return alternate_index
328
339
 
329
340
  @contextmanager
@@ -341,6 +352,10 @@ class GitRepository:
341
352
  """Populate an alternate index from the provided ref."""
342
353
  self._run_git_command(["read-tree", ref], env=index.env)
343
354
 
355
+ def read_empty_tree(self, *, index: AlternateGitIndex) -> None:
356
+ """Initialize an alternate index with an empty tree."""
357
+ self._run_git_command(["read-tree", "--empty"], env=index.env)
358
+
344
359
  def apply_patch(
345
360
  self,
346
361
  patch: str,
@@ -15,6 +15,11 @@ small number of coherent commits.
15
15
  - Do not split a patch unit further.
16
16
  - Preserve the natural order of work. If unit `u1` appears before `u4` in the
17
17
  diff, prefer to keep that order within a commit.
18
+ - Order commits the way experienced software developers usually land them:
19
+ foundational product code first, then tests that validate that code, then
20
+ docs/examples, then style/chore follow-ups.
21
+ - If a docs or test unit depends on a feat/fix/refactor/perf unit, place the
22
+ docs or test commit after the underlying code change.
18
23
  - If multiple units belong to the same logical change, keep them together.
19
24
  - If the staged changes are best represented as a single coherent commit, return
20
25
  one commit.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-copilot-commit
3
- Version: 0.5.4
3
+ Version: 0.5.6
4
4
  Summary: Automatically generate and commit changes using GitHub Copilot
5
5
  Author-email: Dheepak Krishnamurthy <1813121+kdheepak@users.noreply.github.com>
6
6
  License-File: LICENSE
@@ -1,15 +1,15 @@
1
1
  git_copilot_commit/__init__.py,sha256=v3x5oBkxwKJEZLv62QqSmP3iqNKLtZgrWZfH8eFzlQg,60
2
- git_copilot_commit/cli.py,sha256=k-HCs6skTLysyyHUN-lS9NjrsURvXcq7E2MgdClRnxc,32472
3
- git_copilot_commit/git.py,sha256=vNuh2j8TGmocLio4XgPpbXIktlgczNdEt2Fg1c40wuk,14962
2
+ git_copilot_commit/cli.py,sha256=yN7fq43i3TFerhEOMm4ZHUy2rbiH0n70tIcwrxdWCdc,33929
3
+ git_copilot_commit/git.py,sha256=hCTcnZC3S8B-b5NY8KbGvlXmHZ_XGwezVW9W9UdrHZE,15612
4
4
  git_copilot_commit/github_copilot.py,sha256=2SKVVFmQ2yETAySjSRYKQTujW-1vZEW4rFKSr--dtLs,50427
5
5
  git_copilot_commit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  git_copilot_commit/settings.py,sha256=WrM10_J3F7QBfOVmPDWpNZrNHhmZSeN-9FqQZxgdWvQ,3730
7
7
  git_copilot_commit/split_commits.py,sha256=rHyuVJggjmYjbva7BVqsM3aZRxUgOKkuZtxxvFRcu6Q,15060
8
8
  git_copilot_commit/version.py,sha256=AieHOUX52g6N67HL0iLWtDKrgOYyulxwHWViu26Jrd4,105
9
9
  git_copilot_commit/prompts/commit-message-generator-prompt.md,sha256=EHAS6w15vLQ-kgT1N7nPG2nWqdeTmlHje_kN9yZIoZQ,2378
10
- git_copilot_commit/prompts/split-commit-planner-prompt.md,sha256=ABKuVVyrkHsb3QV6qyS--W5yvKBoZhtq8xJEb3OZQvI,1088
11
- git_copilot_commit-0.5.4.dist-info/METADATA,sha256=gvVJMTP6Akuyws8U-z0O_ANqUAnq9fAJoEO982M25Q0,6649
12
- git_copilot_commit-0.5.4.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
13
- git_copilot_commit-0.5.4.dist-info/entry_points.txt,sha256=-D4bQqiuSPwQJG2zx--vJbZD1iqB5coUfoJ_gmC3rSg,66
14
- git_copilot_commit-0.5.4.dist-info/licenses/LICENSE,sha256=14lNZAoKJPI1U7eGpletjN_PFm1JwP1vT_0jFKY6eWg,1065
15
- git_copilot_commit-0.5.4.dist-info/RECORD,,
10
+ git_copilot_commit/prompts/split-commit-planner-prompt.md,sha256=JActWTKMbdR9eaJyCbQ6UwzyRu5jJi8Iec7H7qWAmAA,1418
11
+ git_copilot_commit-0.5.6.dist-info/METADATA,sha256=HGW7Q_WqyozLqPsmU1mefJwdTcpGD4ieQF8c9TEIIDE,6649
12
+ git_copilot_commit-0.5.6.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
13
+ git_copilot_commit-0.5.6.dist-info/entry_points.txt,sha256=-D4bQqiuSPwQJG2zx--vJbZD1iqB5coUfoJ_gmC3rSg,66
14
+ git_copilot_commit-0.5.6.dist-info/licenses/LICENSE,sha256=14lNZAoKJPI1U7eGpletjN_PFm1JwP1vT_0jFKY6eWg,1065
15
+ git_copilot_commit-0.5.6.dist-info/RECORD,,