git-commit-message 0.5.1__tar.gz → 0.7.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.
Files changed (20) hide show
  1. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/PKG-INFO +51 -4
  2. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/README.md +47 -2
  3. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/pyproject.toml +4 -2
  4. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/src/git_commit_message/__init__.py +1 -3
  5. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/src/git_commit_message/_cli.py +91 -34
  6. git_commit_message-0.7.0/src/git_commit_message/_gemini.py +122 -0
  7. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/src/git_commit_message/_git.py +13 -13
  8. git_commit_message-0.7.0/src/git_commit_message/_gpt.py +90 -0
  9. git_commit_message-0.7.0/src/git_commit_message/_llm.py +594 -0
  10. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/src/git_commit_message.egg-info/PKG-INFO +51 -4
  11. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/src/git_commit_message.egg-info/SOURCES.txt +2 -0
  12. git_commit_message-0.7.0/src/git_commit_message.egg-info/requires.txt +4 -0
  13. git_commit_message-0.5.1/src/git_commit_message/_gpt.py +0 -312
  14. git_commit_message-0.5.1/src/git_commit_message.egg-info/requires.txt +0 -2
  15. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/UNLICENSE +0 -0
  16. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/setup.cfg +0 -0
  17. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/src/git_commit_message/__main__.py +0 -0
  18. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/src/git_commit_message.egg-info/dependency_links.txt +0 -0
  19. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/src/git_commit_message.egg-info/entry_points.txt +0 -0
  20. {git_commit_message-0.5.1 → git_commit_message-0.7.0}/src/git_commit_message.egg-info/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-commit-message
3
- Version: 0.5.1
4
- Summary: Generate Git commit messages from staged changes using OpenAI GPT
3
+ Version: 0.7.0
4
+ Summary: Generate Git commit messages from staged changes using LLM
5
5
  Maintainer-email: Mina Her <minacle@live.com>
6
6
  License: This is free and unencumbered software released into the public domain.
7
7
 
@@ -44,7 +44,9 @@ Classifier: Topic :: Software Development :: Version Control :: Git
44
44
  Requires-Python: >=3.13
45
45
  Description-Content-Type: text/markdown
46
46
  Requires-Dist: babel>=2.17.0
47
+ Requires-Dist: google-genai>=1.56.0
47
48
  Requires-Dist: openai>=2.6.1
49
+ Requires-Dist: tiktoken>=0.12.0
48
50
 
49
51
  # git-commit-message
50
52
 
@@ -82,6 +84,12 @@ Set your API key (POSIX sh):
82
84
  export OPENAI_API_KEY="sk-..."
83
85
  ```
84
86
 
87
+ Or for the Google provider:
88
+
89
+ ```sh
90
+ export GOOGLE_API_KEY="..."
91
+ ```
92
+
85
93
  Note (fish): In fish, set it as follows.
86
94
 
87
95
  ```fish
@@ -109,18 +117,49 @@ git-commit-message "optional extra context about the change"
109
117
  git-commit-message --one-line "optional context"
110
118
  ```
111
119
 
120
+ - Select provider (default: openai):
121
+
122
+ ```sh
123
+ git-commit-message --provider openai "optional context"
124
+ ```
125
+
126
+ - Select provider (Google Gemini via google-genai):
127
+
128
+ ```sh
129
+ git-commit-message --provider google "optional context"
130
+ ```
131
+
112
132
  - Limit subject length (default 72):
113
133
 
114
134
  ```sh
115
135
  git-commit-message --one-line --max-length 50 "optional context"
116
136
  ```
117
137
 
138
+ - Chunk long diffs by token budget (0 = single chunk + summary, -1 = disable chunking):
139
+
140
+ ```sh
141
+ # force a single summary pass over the whole diff (default)
142
+ git-commit-message --chunk-tokens 0 "optional context"
143
+
144
+ # chunk the diff into ~4000-token pieces before summarising
145
+ git-commit-message --chunk-tokens 4000 "optional context"
146
+
147
+ # disable summarisation and use the legacy one-shot prompt
148
+ git-commit-message --chunk-tokens -1 "optional context"
149
+ ```
150
+
118
151
  - Commit immediately with editor:
119
152
 
120
153
  ```sh
121
154
  git-commit-message --commit --edit "refactor parser for speed"
122
155
  ```
123
156
 
157
+ - Print debug info (prompt/response + token usage):
158
+
159
+ ```sh
160
+ git-commit-message --debug "optional context"
161
+ ```
162
+
124
163
  - Select output language/locale (default: en-GB):
125
164
 
126
165
  ```sh
@@ -141,9 +180,17 @@ Notes:
141
180
 
142
181
  Environment:
143
182
 
144
- - `OPENAI_API_KEY`: required
145
- - `GIT_COMMIT_MESSAGE_MODEL` or `OPENAI_MODEL`: optional (default: `gpt-5-mini`)
183
+ - `OPENAI_API_KEY`: required when provider is `openai`
184
+ - `GOOGLE_API_KEY`: required when provider is `google`
185
+ - `GIT_COMMIT_MESSAGE_PROVIDER`: optional (default: `openai`). `--provider` overrides this value.
186
+ - `GIT_COMMIT_MESSAGE_MODEL`: optional model override (defaults: `openai` -> `gpt-5-mini`, `google` -> `gemini-2.5-flash`)
187
+ - `OPENAI_MODEL`: optional OpenAI-only model override
146
188
  - `GIT_COMMIT_MESSAGE_LANGUAGE`: optional (default: `en-GB`)
189
+ - `GIT_COMMIT_MESSAGE_CHUNK_TOKENS`: optional token budget per diff chunk (default: 0 = single chunk + summary; -1 disables summarisation)
190
+
191
+ Notes:
192
+
193
+ - If token counting fails for your provider while chunking, try `--chunk-tokens 0` (default) or `--chunk-tokens -1`.
147
194
 
148
195
  ## AI‑generated code notice
149
196
 
@@ -34,6 +34,12 @@ Set your API key (POSIX sh):
34
34
  export OPENAI_API_KEY="sk-..."
35
35
  ```
36
36
 
37
+ Or for the Google provider:
38
+
39
+ ```sh
40
+ export GOOGLE_API_KEY="..."
41
+ ```
42
+
37
43
  Note (fish): In fish, set it as follows.
38
44
 
39
45
  ```fish
@@ -61,18 +67,49 @@ git-commit-message "optional extra context about the change"
61
67
  git-commit-message --one-line "optional context"
62
68
  ```
63
69
 
70
+ - Select provider (default: openai):
71
+
72
+ ```sh
73
+ git-commit-message --provider openai "optional context"
74
+ ```
75
+
76
+ - Select provider (Google Gemini via google-genai):
77
+
78
+ ```sh
79
+ git-commit-message --provider google "optional context"
80
+ ```
81
+
64
82
  - Limit subject length (default 72):
65
83
 
66
84
  ```sh
67
85
  git-commit-message --one-line --max-length 50 "optional context"
68
86
  ```
69
87
 
88
+ - Chunk long diffs by token budget (0 = single chunk + summary, -1 = disable chunking):
89
+
90
+ ```sh
91
+ # force a single summary pass over the whole diff (default)
92
+ git-commit-message --chunk-tokens 0 "optional context"
93
+
94
+ # chunk the diff into ~4000-token pieces before summarising
95
+ git-commit-message --chunk-tokens 4000 "optional context"
96
+
97
+ # disable summarisation and use the legacy one-shot prompt
98
+ git-commit-message --chunk-tokens -1 "optional context"
99
+ ```
100
+
70
101
  - Commit immediately with editor:
71
102
 
72
103
  ```sh
73
104
  git-commit-message --commit --edit "refactor parser for speed"
74
105
  ```
75
106
 
107
+ - Print debug info (prompt/response + token usage):
108
+
109
+ ```sh
110
+ git-commit-message --debug "optional context"
111
+ ```
112
+
76
113
  - Select output language/locale (default: en-GB):
77
114
 
78
115
  ```sh
@@ -93,9 +130,17 @@ Notes:
93
130
 
94
131
  Environment:
95
132
 
96
- - `OPENAI_API_KEY`: required
97
- - `GIT_COMMIT_MESSAGE_MODEL` or `OPENAI_MODEL`: optional (default: `gpt-5-mini`)
133
+ - `OPENAI_API_KEY`: required when provider is `openai`
134
+ - `GOOGLE_API_KEY`: required when provider is `google`
135
+ - `GIT_COMMIT_MESSAGE_PROVIDER`: optional (default: `openai`). `--provider` overrides this value.
136
+ - `GIT_COMMIT_MESSAGE_MODEL`: optional model override (defaults: `openai` -> `gpt-5-mini`, `google` -> `gemini-2.5-flash`)
137
+ - `OPENAI_MODEL`: optional OpenAI-only model override
98
138
  - `GIT_COMMIT_MESSAGE_LANGUAGE`: optional (default: `en-GB`)
139
+ - `GIT_COMMIT_MESSAGE_CHUNK_TOKENS`: optional token budget per diff chunk (default: 0 = single chunk + summary; -1 disables summarisation)
140
+
141
+ Notes:
142
+
143
+ - If token counting fails for your provider while chunking, try `--chunk-tokens 0` (default) or `--chunk-tokens -1`.
99
144
 
100
145
  ## AI‑generated code notice
101
146
 
@@ -1,12 +1,14 @@
1
1
  [project]
2
2
  name = "git-commit-message"
3
- version = "0.5.1"
4
- description = "Generate Git commit messages from staged changes using OpenAI GPT"
3
+ version = "0.7.0"
4
+ description = "Generate Git commit messages from staged changes using LLM"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
7
7
  dependencies = [
8
8
  "babel>=2.17.0",
9
+ "google-genai>=1.56.0",
9
10
  "openai>=2.6.1",
11
+ "tiktoken>=0.12.0",
10
12
  ]
11
13
  maintainers = [{ name = "Mina Her", email = "minacle@live.com" }]
12
14
  license = { file = "UNLICENSE" }
@@ -5,6 +5,4 @@ This module exposes only public symbols in accordance with the codestyle guide.
5
5
 
6
6
  from ._cli import main
7
7
 
8
- __all__ = (
9
- "main",
10
- )
8
+ __all__ = ("main",)
@@ -1,24 +1,44 @@
1
- from __future__ import annotations
2
-
3
1
  """Command-line interface entry point.
4
2
 
5
- Collect staged changes from the repository and call an OpenAI GPT model
3
+ Collect staged changes from the repository and call an LLM provider
6
4
  to generate a commit message, or create a commit straight away.
7
5
  """
8
6
 
7
+ from __future__ import annotations
8
+
9
9
  from argparse import ArgumentParser, Namespace
10
+ from os import environ
10
11
  from pathlib import Path
11
- import sys
12
+ from sys import exit as sys_exit
13
+ from sys import stderr
12
14
  from typing import Final
13
15
 
14
- from ._git import commit_with_message, get_repo_root, get_staged_diff, has_staged_changes
15
- from ._gpt import (
16
+ from ._git import (
17
+ commit_with_message,
18
+ get_repo_root,
19
+ get_staged_diff,
20
+ has_staged_changes,
21
+ )
22
+ from ._llm import (
23
+ CommitMessageResult,
24
+ UnsupportedProviderError,
16
25
  generate_commit_message,
17
26
  generate_commit_message_with_info,
18
- CommitMessageResult,
19
27
  )
20
28
 
21
29
 
30
+ def _env_chunk_tokens_default() -> int | None:
31
+ """Return chunk token default from env if valid, else None."""
32
+
33
+ raw: str | None = environ.get("GIT_COMMIT_MESSAGE_CHUNK_TOKENS")
34
+ if raw is None:
35
+ return None
36
+ try:
37
+ return int(raw)
38
+ except ValueError:
39
+ return None
40
+
41
+
22
42
  def _build_parser() -> ArgumentParser:
23
43
  """Create the CLI argument parser.
24
44
 
@@ -31,7 +51,7 @@ def _build_parser() -> ArgumentParser:
31
51
  parser: ArgumentParser = ArgumentParser(
32
52
  prog="git-commit-message",
33
53
  description=(
34
- "Generate a commit message with OpenAI GPT based on the staged changes."
54
+ "Generate a commit message based on the staged changes."
35
55
  ),
36
56
  )
37
57
 
@@ -53,11 +73,21 @@ def _build_parser() -> ArgumentParser:
53
73
  help="Open an editor to amend the message before committing. Use with '--commit'.",
54
74
  )
55
75
 
76
+ parser.add_argument(
77
+ "--provider",
78
+ default=None,
79
+ help=(
80
+ "LLM provider to use (default: openai). "
81
+ "You may also set GIT_COMMIT_MESSAGE_PROVIDER. "
82
+ "The CLI flag overrides the environment variable."
83
+ ),
84
+ )
85
+
56
86
  parser.add_argument(
57
87
  "--model",
58
88
  default=None,
59
89
  help=(
60
- "OpenAI model name to use. If unspecified, uses the environment variables (GIT_COMMIT_MESSAGE_MODEL, OPENAI_MODEL) or 'gpt-5-mini'."
90
+ "Model name to use. If unspecified, uses GIT_COMMIT_MESSAGE_MODEL or a provider-specific default (openai: gpt-5-mini; google: gemini-2.5-flash)."
61
91
  ),
62
92
  )
63
93
 
@@ -92,12 +122,24 @@ def _build_parser() -> ArgumentParser:
92
122
  help="Maximum subject (first line) length (default: 72).",
93
123
  )
94
124
 
125
+ parser.add_argument(
126
+ "--chunk-tokens",
127
+ dest="chunk_tokens",
128
+ type=int,
129
+ default=None,
130
+ help=(
131
+ "Target token budget per diff chunk. "
132
+ "0 forces a single chunk with summarisation; -1 disables summarisation (legacy one-shot). "
133
+ "If omitted, uses GIT_COMMIT_MESSAGE_CHUNK_TOKENS when set (default: 0)."
134
+ ),
135
+ )
136
+
95
137
  return parser
96
138
 
97
139
 
98
140
  def _run(
99
- *,
100
141
  args: Namespace,
142
+ /,
101
143
  ) -> int:
102
144
  """Main execution logic.
103
145
 
@@ -114,37 +156,50 @@ def _run(
114
156
 
115
157
  repo_root: Path = get_repo_root()
116
158
 
117
- if not has_staged_changes(cwd=repo_root):
118
- print("No staged changes. Run 'git add' and try again.", file=sys.stderr)
159
+ if not has_staged_changes(repo_root):
160
+ print("No staged changes. Run 'git add' and try again.", file=stderr)
119
161
  return 2
120
162
 
121
- diff_text: str = get_staged_diff(cwd=repo_root)
163
+ diff_text: str = get_staged_diff(repo_root)
122
164
 
123
165
  hint: str | None = args.description if isinstance(args.description, str) else None
124
166
 
167
+ chunk_tokens: int | None = args.chunk_tokens
168
+ if chunk_tokens is None:
169
+ chunk_tokens = _env_chunk_tokens_default()
170
+ if chunk_tokens is None:
171
+ chunk_tokens = 0
172
+
125
173
  result: CommitMessageResult | None = None
126
174
  try:
127
175
  if args.debug:
128
176
  result = generate_commit_message_with_info(
129
- diff=diff_text,
130
- hint=hint,
131
- model=args.model,
132
- single_line=getattr(args, "one_line", False),
133
- subject_max=getattr(args, "max_length", None),
134
- language=getattr(args, "language", None),
177
+ diff_text,
178
+ hint,
179
+ args.model,
180
+ getattr(args, "one_line", False),
181
+ getattr(args, "max_length", None),
182
+ getattr(args, "language", None),
183
+ chunk_tokens,
184
+ getattr(args, "provider", None),
135
185
  )
136
186
  message = result.message
137
187
  else:
138
188
  message = generate_commit_message(
139
- diff=diff_text,
140
- hint=hint,
141
- model=args.model,
142
- single_line=getattr(args, "one_line", False),
143
- subject_max=getattr(args, "max_length", None),
144
- language=getattr(args, "language", None),
189
+ diff_text,
190
+ hint,
191
+ args.model,
192
+ getattr(args, "one_line", False),
193
+ getattr(args, "max_length", None),
194
+ getattr(args, "language", None),
195
+ chunk_tokens,
196
+ getattr(args, "provider", None),
145
197
  )
198
+ except UnsupportedProviderError as exc:
199
+ print(str(exc), file=stderr)
200
+ return 3
146
201
  except Exception as exc: # noqa: BLE001 - to preserve standard output messaging
147
- print(f"Failed to generate commit message: {exc}", file=sys.stderr)
202
+ print(f"Failed to generate commit message: {exc}", file=stderr)
148
203
  return 3
149
204
 
150
205
  # Option: force single-line message
@@ -160,7 +215,8 @@ def _run(
160
215
  if not args.commit:
161
216
  if args.debug and result is not None:
162
217
  # Print debug information
163
- print("==== OpenAI Usage ====")
218
+ print(f"==== {result.provider} Usage ====")
219
+ print(f"provider: {result.provider}")
164
220
  print(f"model: {result.model}")
165
221
  print(f"response_id: {getattr(result, 'response_id', '(n/a)')}")
166
222
  if result.total_tokens is not None:
@@ -181,7 +237,8 @@ def _run(
181
237
 
182
238
  if args.debug and result is not None:
183
239
  # Also print debug info before commit
184
- print("==== OpenAI Usage ====")
240
+ print(f"==== {result.provider} Usage ====")
241
+ print(f"provider: {result.provider}")
185
242
  print(f"model: {result.model}")
186
243
  print(f"response_id: {getattr(result, 'response_id', '(n/a)')}")
187
244
  if result.total_tokens is not None:
@@ -198,9 +255,9 @@ def _run(
198
255
  print(message)
199
256
 
200
257
  if args.edit:
201
- rc: int = commit_with_message(message=message, edit=True, cwd=repo_root)
258
+ rc: int = commit_with_message(message, True, repo_root)
202
259
  else:
203
- rc = commit_with_message(message=message, edit=False, cwd=repo_root)
260
+ rc = commit_with_message(message, False, repo_root)
204
261
 
205
262
  return rc
206
263
 
@@ -215,8 +272,8 @@ def main() -> None:
215
272
  args: Namespace = parser.parse_args()
216
273
 
217
274
  if args.edit and not args.commit:
218
- print("'--edit' must be used together with '--commit'.", file=sys.stderr)
219
- sys.exit(2)
275
+ print("'--edit' must be used together with '--commit'.", file=stderr)
276
+ sys_exit(2)
220
277
 
221
- code: int = _run(args=args)
222
- sys.exit(code)
278
+ code: int = _run(args)
279
+ sys_exit(code)
@@ -0,0 +1,122 @@
1
+ """Google (Gemini) provider implementation.
2
+
3
+ This module contains only Google GenAI-specific API calls and token counting.
4
+ Provider-agnostic orchestration/prompt logic lives in `_llm.py`.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from os import environ
10
+
11
+ from google import genai
12
+ from google.genai import types
13
+
14
+ from ._llm import LLMTextResult, LLMUsage
15
+
16
+
17
+ class GoogleGenAIProvider:
18
+ name = "google"
19
+
20
+ def __init__(
21
+ self,
22
+ /,
23
+ *,
24
+ api_key: str | None = None,
25
+ ) -> None:
26
+ key = api_key or environ.get("GOOGLE_API_KEY")
27
+ if not key:
28
+ raise RuntimeError("The GOOGLE_API_KEY environment variable is required.")
29
+ self._client = genai.Client(api_key=key)
30
+
31
+ def count_tokens(
32
+ self,
33
+ /,
34
+ *,
35
+ model: str,
36
+ text: str,
37
+ ) -> int:
38
+ try:
39
+ resp = self._client.models.count_tokens(
40
+ model=model,
41
+ contents=text,
42
+ )
43
+ except Exception as exc:
44
+ raise RuntimeError(
45
+ "Token counting failed for the Google provider. "
46
+ "Try `--chunk-tokens 0` (default) or `--chunk-tokens -1` to disable summarisation."
47
+ ) from exc
48
+
49
+ total = getattr(resp, "total_tokens", None)
50
+ if not isinstance(total, int):
51
+ raise RuntimeError(
52
+ "Token counting returned an unexpected response from the Google provider. "
53
+ "Try `--chunk-tokens 0` (default) or `--chunk-tokens -1` to disable summarisation."
54
+ )
55
+
56
+ return total
57
+
58
+ def generate_text(
59
+ self,
60
+ /,
61
+ *,
62
+ model: str,
63
+ instructions: str,
64
+ user_text: str,
65
+ ) -> LLMTextResult:
66
+ config = types.GenerateContentConfig(
67
+ system_instruction=instructions,
68
+ )
69
+
70
+ resp = self._client.models.generate_content(
71
+ model=model,
72
+ contents=user_text,
73
+ config=config,
74
+ )
75
+
76
+ text = self._extract_text(resp)
77
+ if not text:
78
+ raise RuntimeError("An empty response text was generated by the provider.")
79
+
80
+ usage = self._extract_usage(resp)
81
+
82
+ return LLMTextResult(
83
+ text=text,
84
+ response_id=getattr(resp, "response_id", None),
85
+ usage=usage,
86
+ )
87
+
88
+ @staticmethod
89
+ def _extract_text(
90
+ resp: types.GenerateContentResponse,
91
+ /,
92
+ ) -> str:
93
+ candidates = getattr(resp, "candidates", None)
94
+ if not candidates:
95
+ return ""
96
+
97
+ parts = getattr(candidates[0].content, "parts", None) if candidates[0].content else None
98
+ if not parts:
99
+ return ""
100
+
101
+ texts: list[str] = []
102
+ for part in parts:
103
+ t = getattr(part, "text", None)
104
+ if isinstance(t, str) and t.strip():
105
+ texts.append(t)
106
+
107
+ return "\n".join(texts).strip()
108
+
109
+ @staticmethod
110
+ def _extract_usage(
111
+ resp: types.GenerateContentResponse,
112
+ /,
113
+ ) -> LLMUsage | None:
114
+ metadata = getattr(resp, "usage_metadata", None)
115
+ if metadata is None:
116
+ return None
117
+
118
+ return LLMUsage(
119
+ prompt_tokens=getattr(metadata, "prompt_token_count", None),
120
+ completion_tokens=getattr(metadata, "candidates_token_count", None),
121
+ total_tokens=getattr(metadata, "total_token_count", None),
122
+ )
@@ -1,18 +1,18 @@
1
- from __future__ import annotations
2
-
3
1
  """Git-related helper functions.
4
2
 
5
3
  Provides repository root discovery, extraction of staged changes, and
6
4
  creating commits from a message.
7
5
  """
8
6
 
7
+ from __future__ import annotations
8
+
9
9
  from pathlib import Path
10
- import subprocess
10
+ from subprocess import CalledProcessError, check_call, check_output, run
11
11
 
12
12
 
13
13
  def get_repo_root(
14
- *,
15
14
  cwd: Path | None = None,
15
+ /,
16
16
  ) -> Path:
17
17
  """Find the repository root from the current working directory.
18
18
 
@@ -29,7 +29,7 @@ def get_repo_root(
29
29
 
30
30
  start: Path = cwd or Path.cwd()
31
31
  try:
32
- out: bytes = subprocess.check_output(
32
+ out: bytes = check_output(
33
33
  [
34
34
  "git",
35
35
  "rev-parse",
@@ -37,7 +37,7 @@ def get_repo_root(
37
37
  ],
38
38
  cwd=str(start),
39
39
  )
40
- except subprocess.CalledProcessError as exc: # noqa: TRY003
40
+ except CalledProcessError as exc: # noqa: TRY003
41
41
  raise RuntimeError("Not a Git repository.") from exc
42
42
 
43
43
  root = Path(out.decode().strip())
@@ -45,28 +45,28 @@ def get_repo_root(
45
45
 
46
46
 
47
47
  def has_staged_changes(
48
- *,
49
48
  cwd: Path,
49
+ /,
50
50
  ) -> bool:
51
51
  """Check whether there are staged changes."""
52
52
 
53
53
  try:
54
- subprocess.check_call(
54
+ check_call(
55
55
  ["git", "diff", "--cached", "--quiet", "--exit-code"],
56
56
  cwd=str(cwd),
57
57
  )
58
58
  return False
59
- except subprocess.CalledProcessError:
59
+ except CalledProcessError:
60
60
  return True
61
61
 
62
62
 
63
63
  def get_staged_diff(
64
- *,
65
64
  cwd: Path,
65
+ /,
66
66
  ) -> str:
67
67
  """Return the staged changes as diff text."""
68
68
 
69
- out: bytes = subprocess.check_output(
69
+ out: bytes = check_output(
70
70
  [
71
71
  "git",
72
72
  "diff",
@@ -81,10 +81,10 @@ def get_staged_diff(
81
81
 
82
82
 
83
83
  def commit_with_message(
84
- *,
85
84
  message: str,
86
85
  edit: bool,
87
86
  cwd: Path,
87
+ /,
88
88
  ) -> int:
89
89
  """Create a commit with the given message.
90
90
 
@@ -108,7 +108,7 @@ def commit_with_message(
108
108
  cmd.append("--edit")
109
109
 
110
110
  try:
111
- completed = subprocess.run(cmd, cwd=str(cwd), check=False)
111
+ completed = run(cmd, cwd=str(cwd), check=False)
112
112
  return int(completed.returncode)
113
113
  except OSError as exc: # e.g., editor launch failure, etc.
114
114
  raise RuntimeError(f"Failed to run 'git commit': {exc}") from exc