git-commit-message 0.5.0__tar.gz → 0.6.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 (18) hide show
  1. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/PKG-INFO +34 -10
  2. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/README.md +31 -9
  3. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/pyproject.toml +3 -1
  4. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/src/git_commit_message/__init__.py +1 -3
  5. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/src/git_commit_message/_cli.py +66 -27
  6. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/src/git_commit_message/_git.py +13 -13
  7. git_commit_message-0.6.0/src/git_commit_message/_gpt.py +604 -0
  8. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/src/git_commit_message.egg-info/PKG-INFO +34 -10
  9. git_commit_message-0.6.0/src/git_commit_message.egg-info/requires.txt +3 -0
  10. git_commit_message-0.5.0/src/git_commit_message/_gpt.py +0 -284
  11. git_commit_message-0.5.0/src/git_commit_message.egg-info/requires.txt +0 -1
  12. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/UNLICENSE +0 -0
  13. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/setup.cfg +0 -0
  14. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/src/git_commit_message/__main__.py +0 -0
  15. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/src/git_commit_message.egg-info/SOURCES.txt +0 -0
  16. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/src/git_commit_message.egg-info/dependency_links.txt +0 -0
  17. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/src/git_commit_message.egg-info/entry_points.txt +0 -0
  18. {git_commit_message-0.5.0 → git_commit_message-0.6.0}/src/git_commit_message.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-commit-message
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Generate Git commit messages from staged changes using OpenAI GPT
5
5
  Maintainer-email: Mina Her <minacle@live.com>
6
6
  License: This is free and unencumbered software released into the public domain.
@@ -43,17 +43,21 @@ Classifier: Programming Language :: Python :: 3.13
43
43
  Classifier: Topic :: Software Development :: Version Control :: Git
44
44
  Requires-Python: >=3.13
45
45
  Description-Content-Type: text/markdown
46
+ Requires-Dist: babel>=2.17.0
46
47
  Requires-Dist: openai>=2.6.1
48
+ Requires-Dist: tiktoken>=0.12.0
47
49
 
48
50
  # git-commit-message
49
51
 
50
52
  Staged changes -> GPT commit message generator.
51
53
 
54
+ [![asciicast](https://asciinema.org/a/jk0phFqNnc5vaCiIZEYBwZOyN.svg)](https://asciinema.org/a/jk0phFqNnc5vaCiIZEYBwZOyN)
55
+
52
56
  ## Install (PyPI)
53
57
 
54
58
  Install the latest released version from PyPI:
55
59
 
56
- ```fish
60
+ ```sh
57
61
  # User environment (recommended)
58
62
  python -m pip install --user git-commit-message
59
63
 
@@ -69,11 +73,17 @@ python -m pip install --upgrade git-commit-message
69
73
 
70
74
  Quick check:
71
75
 
72
- ```fish
76
+ ```sh
73
77
  git-commit-message --help
74
78
  ```
75
79
 
76
- Set your API key (fish):
80
+ Set your API key (POSIX sh):
81
+
82
+ ```sh
83
+ export OPENAI_API_KEY="sk-..."
84
+ ```
85
+
86
+ Note (fish): In fish, set it as follows.
77
87
 
78
88
  ```fish
79
89
  set -x OPENAI_API_KEY "sk-..."
@@ -81,7 +91,7 @@ set -x OPENAI_API_KEY "sk-..."
81
91
 
82
92
  ## Install (editable)
83
93
 
84
- ```fish
94
+ ```sh
85
95
  python -m pip install -e .
86
96
  ```
87
97
 
@@ -89,32 +99,45 @@ python -m pip install -e .
89
99
 
90
100
  - Print commit message only:
91
101
 
92
- ```fish
102
+ ```sh
93
103
  git add -A
94
104
  git-commit-message "optional extra context about the change"
95
105
  ```
96
106
 
97
107
  - Force single-line subject only:
98
108
 
99
- ```fish
109
+ ```sh
100
110
  git-commit-message --one-line "optional context"
101
111
  ```
102
112
 
103
113
  - Limit subject length (default 72):
104
114
 
105
- ```fish
115
+ ```sh
106
116
  git-commit-message --one-line --max-length 50 "optional context"
107
117
  ```
108
118
 
119
+ - Chunk long diffs by token budget (0 = single chunk + summary, -1 = disable chunking):
120
+
121
+ ```sh
122
+ # force a single summary pass over the whole diff (default)
123
+ git-commit-message --chunk-tokens 0 "optional context"
124
+
125
+ # chunk the diff into ~4000-token pieces before summarising
126
+ git-commit-message --chunk-tokens 4000 "optional context"
127
+
128
+ # disable summarisation and use the legacy one-shot prompt
129
+ git-commit-message --chunk-tokens -1 "optional context"
130
+ ```
131
+
109
132
  - Commit immediately with editor:
110
133
 
111
- ```fish
134
+ ```sh
112
135
  git-commit-message --commit --edit "refactor parser for speed"
113
136
  ```
114
137
 
115
138
  - Select output language/locale (default: en-GB):
116
139
 
117
- ```fish
140
+ ```sh
118
141
  # American English
119
142
  git-commit-message --language en-US "optional context"
120
143
 
@@ -135,6 +158,7 @@ Environment:
135
158
  - `OPENAI_API_KEY`: required
136
159
  - `GIT_COMMIT_MESSAGE_MODEL` or `OPENAI_MODEL`: optional (default: `gpt-5-mini`)
137
160
  - `GIT_COMMIT_MESSAGE_LANGUAGE`: optional (default: `en-GB`)
161
+ - `GIT_COMMIT_MESSAGE_CHUNK_TOKENS`: optional token budget per diff chunk (default: 0 = single chunk + summary; -1 disables summarisation)
138
162
 
139
163
  ## AI‑generated code notice
140
164
 
@@ -2,11 +2,13 @@
2
2
 
3
3
  Staged changes -> GPT commit message generator.
4
4
 
5
+ [![asciicast](https://asciinema.org/a/jk0phFqNnc5vaCiIZEYBwZOyN.svg)](https://asciinema.org/a/jk0phFqNnc5vaCiIZEYBwZOyN)
6
+
5
7
  ## Install (PyPI)
6
8
 
7
9
  Install the latest released version from PyPI:
8
10
 
9
- ```fish
11
+ ```sh
10
12
  # User environment (recommended)
11
13
  python -m pip install --user git-commit-message
12
14
 
@@ -22,11 +24,17 @@ python -m pip install --upgrade git-commit-message
22
24
 
23
25
  Quick check:
24
26
 
25
- ```fish
27
+ ```sh
26
28
  git-commit-message --help
27
29
  ```
28
30
 
29
- Set your API key (fish):
31
+ Set your API key (POSIX sh):
32
+
33
+ ```sh
34
+ export OPENAI_API_KEY="sk-..."
35
+ ```
36
+
37
+ Note (fish): In fish, set it as follows.
30
38
 
31
39
  ```fish
32
40
  set -x OPENAI_API_KEY "sk-..."
@@ -34,7 +42,7 @@ set -x OPENAI_API_KEY "sk-..."
34
42
 
35
43
  ## Install (editable)
36
44
 
37
- ```fish
45
+ ```sh
38
46
  python -m pip install -e .
39
47
  ```
40
48
 
@@ -42,32 +50,45 @@ python -m pip install -e .
42
50
 
43
51
  - Print commit message only:
44
52
 
45
- ```fish
53
+ ```sh
46
54
  git add -A
47
55
  git-commit-message "optional extra context about the change"
48
56
  ```
49
57
 
50
58
  - Force single-line subject only:
51
59
 
52
- ```fish
60
+ ```sh
53
61
  git-commit-message --one-line "optional context"
54
62
  ```
55
63
 
56
64
  - Limit subject length (default 72):
57
65
 
58
- ```fish
66
+ ```sh
59
67
  git-commit-message --one-line --max-length 50 "optional context"
60
68
  ```
61
69
 
70
+ - Chunk long diffs by token budget (0 = single chunk + summary, -1 = disable chunking):
71
+
72
+ ```sh
73
+ # force a single summary pass over the whole diff (default)
74
+ git-commit-message --chunk-tokens 0 "optional context"
75
+
76
+ # chunk the diff into ~4000-token pieces before summarising
77
+ git-commit-message --chunk-tokens 4000 "optional context"
78
+
79
+ # disable summarisation and use the legacy one-shot prompt
80
+ git-commit-message --chunk-tokens -1 "optional context"
81
+ ```
82
+
62
83
  - Commit immediately with editor:
63
84
 
64
- ```fish
85
+ ```sh
65
86
  git-commit-message --commit --edit "refactor parser for speed"
66
87
  ```
67
88
 
68
89
  - Select output language/locale (default: en-GB):
69
90
 
70
- ```fish
91
+ ```sh
71
92
  # American English
72
93
  git-commit-message --language en-US "optional context"
73
94
 
@@ -88,6 +109,7 @@ Environment:
88
109
  - `OPENAI_API_KEY`: required
89
110
  - `GIT_COMMIT_MESSAGE_MODEL` or `OPENAI_MODEL`: optional (default: `gpt-5-mini`)
90
111
  - `GIT_COMMIT_MESSAGE_LANGUAGE`: optional (default: `en-GB`)
112
+ - `GIT_COMMIT_MESSAGE_CHUNK_TOKENS`: optional token budget per diff chunk (default: 0 = single chunk + summary; -1 disables summarisation)
91
113
 
92
114
  ## AI‑generated code notice
93
115
 
@@ -1,11 +1,13 @@
1
1
  [project]
2
2
  name = "git-commit-message"
3
- version = "0.5.0"
3
+ version = "0.6.0"
4
4
  description = "Generate Git commit messages from staged changes using OpenAI GPT"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
7
7
  dependencies = [
8
+ "babel>=2.17.0",
8
9
  "openai>=2.6.1",
10
+ "tiktoken>=0.12.0",
9
11
  ]
10
12
  maintainers = [{ name = "Mina Her", email = "minacle@live.com" }]
11
13
  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,17 +1,24 @@
1
- from __future__ import annotations
2
-
3
1
  """Command-line interface entry point.
4
2
 
5
3
  Collect staged changes from the repository and call an OpenAI GPT model
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
16
+ from ._git import (
17
+ commit_with_message,
18
+ get_repo_root,
19
+ get_staged_diff,
20
+ has_staged_changes,
21
+ )
15
22
  from ._gpt import (
16
23
  generate_commit_message,
17
24
  generate_commit_message_with_info,
@@ -19,6 +26,18 @@ from ._gpt import (
19
26
  )
20
27
 
21
28
 
29
+ def _env_chunk_tokens_default() -> int | None:
30
+ """Return chunk token default from env if valid, else None."""
31
+
32
+ raw: str | None = environ.get("GIT_COMMIT_MESSAGE_CHUNK_TOKENS")
33
+ if raw is None:
34
+ return None
35
+ try:
36
+ return int(raw)
37
+ except ValueError:
38
+ return None
39
+
40
+
22
41
  def _build_parser() -> ArgumentParser:
23
42
  """Create the CLI argument parser.
24
43
 
@@ -92,12 +111,24 @@ def _build_parser() -> ArgumentParser:
92
111
  help="Maximum subject (first line) length (default: 72).",
93
112
  )
94
113
 
114
+ parser.add_argument(
115
+ "--chunk-tokens",
116
+ dest="chunk_tokens",
117
+ type=int,
118
+ default=None,
119
+ help=(
120
+ "Target token budget per diff chunk. "
121
+ "0 forces a single chunk with summarisation; -1 disables summarisation (legacy one-shot). "
122
+ "If omitted, uses GIT_COMMIT_MESSAGE_CHUNK_TOKENS when set (default: 0)."
123
+ ),
124
+ )
125
+
95
126
  return parser
96
127
 
97
128
 
98
129
  def _run(
99
- *,
100
130
  args: Namespace,
131
+ /,
101
132
  ) -> int:
102
133
  """Main execution logic.
103
134
 
@@ -114,37 +145,45 @@ def _run(
114
145
 
115
146
  repo_root: Path = get_repo_root()
116
147
 
117
- if not has_staged_changes(cwd=repo_root):
118
- print("No staged changes. Run 'git add' and try again.", file=sys.stderr)
148
+ if not has_staged_changes(repo_root):
149
+ print("No staged changes. Run 'git add' and try again.", file=stderr)
119
150
  return 2
120
151
 
121
- diff_text: str = get_staged_diff(cwd=repo_root)
152
+ diff_text: str = get_staged_diff(repo_root)
122
153
 
123
154
  hint: str | None = args.description if isinstance(args.description, str) else None
124
155
 
156
+ chunk_tokens: int | None = args.chunk_tokens
157
+ if chunk_tokens is None:
158
+ chunk_tokens = _env_chunk_tokens_default()
159
+ if chunk_tokens is None:
160
+ chunk_tokens = 0
161
+
125
162
  result: CommitMessageResult | None = None
126
163
  try:
127
164
  if args.debug:
128
165
  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),
166
+ diff_text,
167
+ hint,
168
+ args.model,
169
+ getattr(args, "one_line", False),
170
+ getattr(args, "max_length", None),
171
+ getattr(args, "language", None),
172
+ chunk_tokens,
135
173
  )
136
174
  message = result.message
137
175
  else:
138
176
  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),
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,
145
184
  )
146
185
  except Exception as exc: # noqa: BLE001 - to preserve standard output messaging
147
- print(f"Failed to generate commit message: {exc}", file=sys.stderr)
186
+ print(f"Failed to generate commit message: {exc}", file=stderr)
148
187
  return 3
149
188
 
150
189
  # Option: force single-line message
@@ -198,9 +237,9 @@ def _run(
198
237
  print(message)
199
238
 
200
239
  if args.edit:
201
- rc: int = commit_with_message(message=message, edit=True, cwd=repo_root)
240
+ rc: int = commit_with_message(message, True, repo_root)
202
241
  else:
203
- rc = commit_with_message(message=message, edit=False, cwd=repo_root)
242
+ rc = commit_with_message(message, False, repo_root)
204
243
 
205
244
  return rc
206
245
 
@@ -215,8 +254,8 @@ def main() -> None:
215
254
  args: Namespace = parser.parse_args()
216
255
 
217
256
  if args.edit and not args.commit:
218
- print("'--edit' must be used together with '--commit'.", file=sys.stderr)
219
- sys.exit(2)
257
+ print("'--edit' must be used together with '--commit'.", file=stderr)
258
+ sys_exit(2)
220
259
 
221
- code: int = _run(args=args)
222
- sys.exit(code)
260
+ code: int = _run(args)
261
+ sys_exit(code)
@@ -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