git-copilot-commit 0.5.4__tar.gz → 0.5.6__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.5.4 → git_copilot_commit-0.5.6}/PKG-INFO +1 -1
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/src/git_copilot_commit/cli.py +46 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/src/git_copilot_commit/git.py +21 -6
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/src/git_copilot_commit/prompts/split-commit-planner-prompt.md +5 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/tests/conftest.py +3 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/tests/test_cli.py +159 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/tests/test_git.py +37 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/.github/dependabot.yml +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/.github/workflows/ci.yml +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/.gitignore +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/.justfile +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/.python-version +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/LICENSE +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/README.md +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/pyproject.toml +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/src/git_copilot_commit/__init__.py +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/src/git_copilot_commit/github_copilot.py +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/src/git_copilot_commit/prompts/commit-message-generator-prompt.md +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/src/git_copilot_commit/py.typed +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/src/git_copilot_commit/settings.py +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/src/git_copilot_commit/split_commits.py +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/src/git_copilot_commit/version.py +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/tests/test_github_copilot_utils.py +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/tests/test_settings.py +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/tests/test_split_commits.py +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/uv.lock +0 -0
- {git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/vhs/demo.vhs +0 -0
|
@@ -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(
|
|
@@ -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(
|
|
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.
|
|
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.
|
|
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.
|
|
@@ -23,6 +23,9 @@ def git_repo_path(tmp_path: Path) -> Path:
|
|
|
23
23
|
run_git(tmp_path, "init", "-q")
|
|
24
24
|
run_git(tmp_path, "config", "user.name", "Test User")
|
|
25
25
|
run_git(tmp_path, "config", "user.email", "test@example.com")
|
|
26
|
+
hooks_dir = tmp_path / ".githooks"
|
|
27
|
+
hooks_dir.mkdir()
|
|
28
|
+
run_git(tmp_path, "config", "core.hooksPath", str(hooks_dir))
|
|
26
29
|
return tmp_path
|
|
27
30
|
|
|
28
31
|
|
|
@@ -15,12 +15,14 @@ from git_copilot_commit.cli import (
|
|
|
15
15
|
commit_with_retry_no_verify,
|
|
16
16
|
display_selected_model,
|
|
17
17
|
display_split_commit_plan,
|
|
18
|
+
extract_conventional_commit_type,
|
|
18
19
|
execute_split_commit_plan,
|
|
19
20
|
generate_commit_message_for_status,
|
|
20
21
|
handle_split_commit_flow,
|
|
21
22
|
load_named_prompt,
|
|
22
23
|
load_system_prompt,
|
|
23
24
|
normalize_model_name,
|
|
25
|
+
order_prepared_split_commits,
|
|
24
26
|
print_copilot_error,
|
|
25
27
|
preprocess_cli_args,
|
|
26
28
|
resolve_prompt_file,
|
|
@@ -369,6 +371,36 @@ def test_build_http_client_config_and_normalize_model_name(
|
|
|
369
371
|
assert normalize_model_name(None) is None
|
|
370
372
|
|
|
371
373
|
|
|
374
|
+
def test_extract_conventional_commit_type_supports_scope_and_breaking_change() -> None:
|
|
375
|
+
assert extract_conventional_commit_type("feat(api): add endpoint") == "feat"
|
|
376
|
+
assert extract_conventional_commit_type("refactor!: simplify planner") == (
|
|
377
|
+
"refactor"
|
|
378
|
+
)
|
|
379
|
+
assert extract_conventional_commit_type("not a conventional commit") is None
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def test_order_prepared_split_commits_moves_follow_up_commits_after_core_changes() -> (
|
|
383
|
+
None
|
|
384
|
+
):
|
|
385
|
+
prepared_commits = [
|
|
386
|
+
PreparedSplitCommit(message="docs: update README", patch_units=()),
|
|
387
|
+
PreparedSplitCommit(message="feat: add split planning", patch_units=()),
|
|
388
|
+
PreparedSplitCommit(message="test: cover split planning", patch_units=()),
|
|
389
|
+
PreparedSplitCommit(message="chore: update fixtures", patch_units=()),
|
|
390
|
+
PreparedSplitCommit(message="fix(parser): preserve ordering", patch_units=()),
|
|
391
|
+
]
|
|
392
|
+
|
|
393
|
+
ordered = order_prepared_split_commits(prepared_commits)
|
|
394
|
+
|
|
395
|
+
assert [prepared_commit.message for prepared_commit in ordered] == [
|
|
396
|
+
"feat: add split planning",
|
|
397
|
+
"fix(parser): preserve ordering",
|
|
398
|
+
"test: cover split planning",
|
|
399
|
+
"docs: update README",
|
|
400
|
+
"chore: update fixtures",
|
|
401
|
+
]
|
|
402
|
+
|
|
403
|
+
|
|
372
404
|
def test_preprocess_cli_args_rewrites_split_syntax() -> None:
|
|
373
405
|
assert preprocess_cli_args(["commit", "--split=auto", "--yes"]) == [
|
|
374
406
|
"commit",
|
|
@@ -640,6 +672,58 @@ def test_execute_split_commit_plan_creates_multiple_commits(
|
|
|
640
672
|
assert recent_messages == ["chore: update last line", "chore: update first line"]
|
|
641
673
|
|
|
642
674
|
|
|
675
|
+
def test_execute_split_commit_plan_supports_initial_commit(
|
|
676
|
+
git_repo,
|
|
677
|
+
git_repo_path,
|
|
678
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
679
|
+
) -> None:
|
|
680
|
+
monkeypatch.setattr(cli.Confirm, "ask", Mock(return_value=True))
|
|
681
|
+
src_dir = git_repo_path / "src"
|
|
682
|
+
src_dir.mkdir()
|
|
683
|
+
app_file = src_dir / "app.lua"
|
|
684
|
+
readme_file = git_repo_path / "README.md"
|
|
685
|
+
app_file.write_text("print('hello')\n", encoding="utf-8")
|
|
686
|
+
readme_file.write_text("# Project\n", encoding="utf-8")
|
|
687
|
+
git_repo.stage_files(["src/app.lua", "README.md"])
|
|
688
|
+
|
|
689
|
+
patch_units = tuple(
|
|
690
|
+
extract_patch_units(git_repo.get_staged_diff(extra_args=SPLIT_DIFF_ARGS))
|
|
691
|
+
)
|
|
692
|
+
assert len(patch_units) == 2
|
|
693
|
+
|
|
694
|
+
prepared_commits = [
|
|
695
|
+
PreparedSplitCommit(
|
|
696
|
+
message=(
|
|
697
|
+
f"docs: add {patch_units[0].path}"
|
|
698
|
+
if patch_units[0].path.endswith(".md")
|
|
699
|
+
else f"feat: add {patch_units[0].path}"
|
|
700
|
+
),
|
|
701
|
+
patch_units=(patch_units[0],),
|
|
702
|
+
),
|
|
703
|
+
PreparedSplitCommit(
|
|
704
|
+
message=(
|
|
705
|
+
f"docs: add {patch_units[1].path}"
|
|
706
|
+
if patch_units[1].path.endswith(".md")
|
|
707
|
+
else f"feat: add {patch_units[1].path}"
|
|
708
|
+
),
|
|
709
|
+
patch_units=(patch_units[1],),
|
|
710
|
+
),
|
|
711
|
+
]
|
|
712
|
+
|
|
713
|
+
commit_shas = execute_split_commit_plan(git_repo, prepared_commits, yes=True)
|
|
714
|
+
|
|
715
|
+
assert len(commit_shas) == 2
|
|
716
|
+
final_status = git_repo.get_status()
|
|
717
|
+
assert not final_status.has_staged_changes
|
|
718
|
+
assert not final_status.has_unstaged_changes
|
|
719
|
+
|
|
720
|
+
recent_messages = [message for _, message in git_repo.get_recent_commits(limit=2)]
|
|
721
|
+
assert recent_messages == [
|
|
722
|
+
prepared_commits[1].message,
|
|
723
|
+
prepared_commits[0].message,
|
|
724
|
+
]
|
|
725
|
+
|
|
726
|
+
|
|
643
727
|
def test_handle_split_commit_flow_falls_back_to_single_commit(
|
|
644
728
|
monkeypatch: pytest.MonkeyPatch,
|
|
645
729
|
) -> None:
|
|
@@ -930,6 +1014,81 @@ def test_handle_split_commit_flow_split_limit_can_trigger_split_planning(
|
|
|
930
1014
|
execute_plan.assert_called_once_with(repo, prepared_commits, yes=False)
|
|
931
1015
|
|
|
932
1016
|
|
|
1017
|
+
def test_handle_split_commit_flow_reorders_prepared_commits_before_execution(
|
|
1018
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1019
|
+
) -> None:
|
|
1020
|
+
status = make_status(
|
|
1021
|
+
staged_diff="diff --git a/src/example.py b/src/example.py\n+print('hi')\n"
|
|
1022
|
+
)
|
|
1023
|
+
repo = Mock()
|
|
1024
|
+
repo.get_staged_diff.return_value = "diff"
|
|
1025
|
+
patch_units = (
|
|
1026
|
+
PatchUnit(
|
|
1027
|
+
id="u1",
|
|
1028
|
+
order=0,
|
|
1029
|
+
path="README.md",
|
|
1030
|
+
staged_status="A",
|
|
1031
|
+
kind="new_file",
|
|
1032
|
+
patch="patch 1",
|
|
1033
|
+
summary="summary 1",
|
|
1034
|
+
),
|
|
1035
|
+
PatchUnit(
|
|
1036
|
+
id="u2",
|
|
1037
|
+
order=1,
|
|
1038
|
+
path="src/app.py",
|
|
1039
|
+
staged_status="M",
|
|
1040
|
+
kind="hunk",
|
|
1041
|
+
patch="patch 2",
|
|
1042
|
+
summary="summary 2",
|
|
1043
|
+
),
|
|
1044
|
+
PatchUnit(
|
|
1045
|
+
id="u3",
|
|
1046
|
+
order=2,
|
|
1047
|
+
path="tests/test_app.py",
|
|
1048
|
+
staged_status="M",
|
|
1049
|
+
kind="hunk",
|
|
1050
|
+
patch="patch 3",
|
|
1051
|
+
summary="summary 3",
|
|
1052
|
+
),
|
|
1053
|
+
)
|
|
1054
|
+
split_plan = SplitCommitPlan(
|
|
1055
|
+
commits=(
|
|
1056
|
+
SplitPlanCommit(("u1",)),
|
|
1057
|
+
SplitPlanCommit(("u2",)),
|
|
1058
|
+
SplitPlanCommit(("u3",)),
|
|
1059
|
+
)
|
|
1060
|
+
)
|
|
1061
|
+
unordered_commits = [
|
|
1062
|
+
PreparedSplitCommit(message="docs: readme", patch_units=(patch_units[0],)),
|
|
1063
|
+
PreparedSplitCommit(message="feat: app", patch_units=(patch_units[1],)),
|
|
1064
|
+
PreparedSplitCommit(message="test: app", patch_units=(patch_units[2],)),
|
|
1065
|
+
]
|
|
1066
|
+
expected_order = [
|
|
1067
|
+
unordered_commits[1],
|
|
1068
|
+
unordered_commits[2],
|
|
1069
|
+
unordered_commits[0],
|
|
1070
|
+
]
|
|
1071
|
+
monkeypatch.setattr(cli, "extract_patch_units", lambda _diff: patch_units)
|
|
1072
|
+
monkeypatch.setattr(cli, "request_split_commit_plan", Mock(return_value=split_plan))
|
|
1073
|
+
monkeypatch.setattr(
|
|
1074
|
+
cli, "request_split_commit_messages", Mock(return_value=unordered_commits)
|
|
1075
|
+
)
|
|
1076
|
+
display_plan = Mock()
|
|
1077
|
+
execute_plan = Mock(return_value=["aaaabbbb", "ccccdddd", "eeeeffff"])
|
|
1078
|
+
monkeypatch.setattr(cli, "display_split_commit_plan", display_plan)
|
|
1079
|
+
monkeypatch.setattr(cli, "execute_split_commit_plan", execute_plan)
|
|
1080
|
+
monkeypatch.setattr(cli, "handle_single_commit_flow", Mock())
|
|
1081
|
+
|
|
1082
|
+
handle_split_commit_flow(
|
|
1083
|
+
repo,
|
|
1084
|
+
status,
|
|
1085
|
+
model="gpt-5.4",
|
|
1086
|
+
)
|
|
1087
|
+
|
|
1088
|
+
display_plan.assert_called_once_with(expected_order)
|
|
1089
|
+
execute_plan.assert_called_once_with(repo, expected_order, yes=False)
|
|
1090
|
+
|
|
1091
|
+
|
|
933
1092
|
def test_handle_split_commit_flow_prompts_when_plan_exceeds_preference(
|
|
934
1093
|
monkeypatch: pytest.MonkeyPatch,
|
|
935
1094
|
) -> None:
|
|
@@ -99,6 +99,34 @@ def test_alternate_index_commit_preserves_real_index_and_unstaged_changes(
|
|
|
99
99
|
assert recent_messages == ["part 2", "part 1"]
|
|
100
100
|
|
|
101
101
|
|
|
102
|
+
def test_alternate_index_supports_unborn_head(git_repo, git_repo_path) -> None:
|
|
103
|
+
file_path = git_repo_path / "README.md"
|
|
104
|
+
file_path.write_text("# Title\n", encoding="utf-8")
|
|
105
|
+
git_repo.stage_files(["README.md"])
|
|
106
|
+
|
|
107
|
+
staged_diff = git_repo.get_staged_diff(
|
|
108
|
+
extra_args=["--src-prefix=a/", "--dst-prefix=b/"]
|
|
109
|
+
)
|
|
110
|
+
assert "new file mode" in staged_diff
|
|
111
|
+
|
|
112
|
+
with git_repo.temporary_alternate_index() as alternate_index:
|
|
113
|
+
git_repo.check_patch_for_alternate_index(staged_diff, index=alternate_index)
|
|
114
|
+
git_repo.apply_patch_to_alternate_index(staged_diff, index=alternate_index)
|
|
115
|
+
commit_sha = git_repo.commit(
|
|
116
|
+
"docs: add readme",
|
|
117
|
+
env=alternate_index.env,
|
|
118
|
+
no_verify=True,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
assert len(commit_sha) == 40
|
|
122
|
+
assert not alternate_index.path.exists()
|
|
123
|
+
|
|
124
|
+
final_status = git_repo.get_status()
|
|
125
|
+
assert not final_status.has_staged_changes
|
|
126
|
+
assert not final_status.has_unstaged_changes
|
|
127
|
+
assert git_repo.get_recent_commits(limit=1)[0][1] == "docs: add readme"
|
|
128
|
+
|
|
129
|
+
|
|
102
130
|
def test_git_file_and_status_helper_properties() -> None:
|
|
103
131
|
staged = GitFile(path="staged.py", status=" ", staged_status="M")
|
|
104
132
|
unstaged = GitFile(path="unstaged.py", status="M", staged_status=" ")
|
|
@@ -201,3 +229,12 @@ def test_parse_status_output_build_env_and_commit_validation(git_repo) -> None:
|
|
|
201
229
|
|
|
202
230
|
with pytest.raises(ValueError):
|
|
203
231
|
git_repo.commit(None)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def test_parse_status_output_preserves_leading_space_on_first_line(git_repo) -> None:
|
|
235
|
+
parsed = git_repo._parse_status_output(" M backend/service.py\nM frontend.py\n")
|
|
236
|
+
|
|
237
|
+
assert [(file.staged_status, file.status, file.path) for file in parsed] == [
|
|
238
|
+
(" ", "M", "backend/service.py"),
|
|
239
|
+
("M", " ", "frontend.py"),
|
|
240
|
+
]
|
|
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
|
{git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/src/git_copilot_commit/github_copilot.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{git_copilot_commit-0.5.4 → git_copilot_commit-0.5.6}/src/git_copilot_commit/split_commits.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|