tgit 0.10.0__tar.gz → 0.10.2__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.
- {tgit-0.10.0 → tgit-0.10.2}/PKG-INFO +3 -2
- {tgit-0.10.0 → tgit-0.10.2}/pyproject.toml +18 -5
- {tgit-0.10.0 → tgit-0.10.2}/tgit/add.py +4 -2
- {tgit-0.10.0 → tgit-0.10.2}/tgit/changelog.py +51 -48
- {tgit-0.10.0 → tgit-0.10.2}/tgit/cli.py +2 -2
- {tgit-0.10.0 → tgit-0.10.2}/tgit/commit.py +30 -26
- tgit-0.10.2/tgit/prompts/commit.txt +26 -0
- {tgit-0.10.0 → tgit-0.10.2}/tgit/utils.py +5 -6
- {tgit-0.10.0 → tgit-0.10.2}/tgit/version.py +175 -96
- tgit-0.10.2/uv.lock +565 -0
- tgit-0.10.0/.vscode/settings.json +0 -13
- tgit-0.10.0/requirements-dev.lock +0 -101
- tgit-0.10.0/requirements.lock +0 -88
- {tgit-0.10.0 → tgit-0.10.2}/.gitignore +0 -0
- {tgit-0.10.0 → tgit-0.10.2}/.python-version +0 -0
- {tgit-0.10.0 → tgit-0.10.2}/.tgit.yml +0 -0
- {tgit-0.10.0 → tgit-0.10.2}/CHANGELOG.md +0 -0
- {tgit-0.10.0 → tgit-0.10.2}/README.md +0 -0
- {tgit-0.10.0 → tgit-0.10.2}/scripts/publish.sh +0 -0
- {tgit-0.10.0 → tgit-0.10.2}/tgit/__init__.py +0 -0
- {tgit-0.10.0 → tgit-0.10.2}/tgit/settings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: tgit
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.2
|
|
4
4
|
Summary: Tool for Git Interaction Temptation (tgit): An elegant CLI tool that simplifies and streamlines your Git workflow, making version control a breeze.
|
|
5
5
|
Author-email: Jannchie <jannchie@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,6 +15,7 @@ Classifier: Typing :: Typed
|
|
|
15
15
|
Requires-Python: >=3.11
|
|
16
16
|
Requires-Dist: gitpython>=3.1.43
|
|
17
17
|
Requires-Dist: inquirer>=3.4.0
|
|
18
|
+
Requires-Dist: jinja2>=3.1.4
|
|
18
19
|
Requires-Dist: openai>=1.52.0
|
|
19
20
|
Requires-Dist: pyyaml>=6.0.2
|
|
20
21
|
Requires-Dist: rich>=13.9.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tgit"
|
|
3
|
-
version = "0.10.
|
|
3
|
+
version = "0.10.2"
|
|
4
4
|
description = "Tool for Git Interaction Temptation (tgit): An elegant CLI tool that simplifies and streamlines your Git workflow, making version control a breeze."
|
|
5
5
|
authors = [{ name = "Jannchie", email = "jannchie@gmail.com" }]
|
|
6
6
|
dependencies = [
|
|
@@ -9,6 +9,7 @@ dependencies = [
|
|
|
9
9
|
"inquirer>=3.4.0",
|
|
10
10
|
"gitpython>=3.1.43",
|
|
11
11
|
"openai>=1.52.0",
|
|
12
|
+
"jinja2>=3.1.4",
|
|
12
13
|
]
|
|
13
14
|
readme = { content-type = "text/markdown", file = "README.md" }
|
|
14
15
|
requires-python = ">= 3.11"
|
|
@@ -28,10 +29,6 @@ license = "MIT"
|
|
|
28
29
|
requires = ["hatchling"]
|
|
29
30
|
build-backend = "hatchling.build"
|
|
30
31
|
|
|
31
|
-
[tool.rye]
|
|
32
|
-
managed = true
|
|
33
|
-
dev-dependencies = ["black>=24.10.0", "isort>=5.13.2"]
|
|
34
|
-
|
|
35
32
|
[tool.black]
|
|
36
33
|
line-length = 160
|
|
37
34
|
|
|
@@ -43,3 +40,19 @@ packages = ["tgit"]
|
|
|
43
40
|
|
|
44
41
|
[project.scripts]
|
|
45
42
|
tgit = "tgit:cli.main"
|
|
43
|
+
|
|
44
|
+
[tool.ruff]
|
|
45
|
+
line-length = 140
|
|
46
|
+
select = ["ALL"]
|
|
47
|
+
|
|
48
|
+
ignore = [
|
|
49
|
+
"PGH",
|
|
50
|
+
"RUF003",
|
|
51
|
+
"BLE001",
|
|
52
|
+
"ERA001",
|
|
53
|
+
"FIX002",
|
|
54
|
+
"TD002",
|
|
55
|
+
"TD003",
|
|
56
|
+
"D",
|
|
57
|
+
"TRY300",
|
|
58
|
+
]
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
1
3
|
from tgit.utils import simple_run_command
|
|
2
4
|
|
|
3
5
|
|
|
4
|
-
def define_add_parser(subparsers):
|
|
6
|
+
def define_add_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
5
7
|
parser_add = subparsers.add_parser("add", help="same as git add")
|
|
6
8
|
parser_add.add_argument("files", help="files to add", nargs="*")
|
|
7
9
|
parser_add.set_defaults(func=handle_add)
|
|
8
10
|
|
|
9
11
|
|
|
10
|
-
def handle_add(args):
|
|
12
|
+
def handle_add(args: argparse.Namespace) -> None:
|
|
11
13
|
files = " ".join(args.files)
|
|
12
14
|
command = f"git add {files}"
|
|
13
15
|
simple_run_command(command)
|
|
@@ -1,23 +1,27 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import re
|
|
2
3
|
import warnings
|
|
4
|
+
from argparse import _SubParsersAction
|
|
3
5
|
from collections import defaultdict
|
|
4
6
|
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
5
8
|
|
|
6
9
|
import git
|
|
7
10
|
|
|
11
|
+
logger = logging.getLogger("tgit")
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
|
|
14
|
+
def get_latest_git_tag(repo: git.Repo) -> str:
|
|
10
15
|
return get_tag_by_idx(repo, -1)
|
|
11
16
|
|
|
12
17
|
|
|
13
|
-
def get_tag_by_idx(repo: git.Repo, idx: int):
|
|
18
|
+
def get_tag_by_idx(repo: git.Repo, idx: int) -> str:
|
|
14
19
|
try:
|
|
15
20
|
if tags := sorted(repo.tags, key=lambda t: t.commit.committed_datetime):
|
|
16
21
|
return tags[idx].name
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
print(f"Error: {e}")
|
|
22
|
+
return None
|
|
23
|
+
except Exception:
|
|
24
|
+
logger.exception("Can't find tag by index %s", idx)
|
|
21
25
|
return None
|
|
22
26
|
|
|
23
27
|
|
|
@@ -28,15 +32,15 @@ def get_first_commit_hash(repo: git.Repo) -> str:
|
|
|
28
32
|
)
|
|
29
33
|
|
|
30
34
|
|
|
31
|
-
def get_commit_hash_from_tag(repo: git.Repo, tag):
|
|
35
|
+
def get_commit_hash_from_tag(repo: git.Repo, tag: str) -> str:
|
|
32
36
|
try:
|
|
33
37
|
return repo.tags[tag].commit.hexsha
|
|
34
|
-
except Exception
|
|
35
|
-
|
|
38
|
+
except Exception:
|
|
39
|
+
logger.exception("Can't find tag %s", tag)
|
|
36
40
|
return None
|
|
37
41
|
|
|
38
42
|
|
|
39
|
-
def define_changelog_parser(subparsers):
|
|
43
|
+
def define_changelog_parser(subparsers: _SubParsersAction) -> None:
|
|
40
44
|
parser_changelog = subparsers.add_parser("changelog", help="generate changelogs")
|
|
41
45
|
parser_changelog.add_argument("-f", "--from", help="From hash/tag", type=str, dest="from_raw")
|
|
42
46
|
parser_changelog.add_argument("-t", "--to", help="To hash/tag", type=str, dest="to_raw")
|
|
@@ -64,29 +68,29 @@ class ChangelogArgs:
|
|
|
64
68
|
output: str
|
|
65
69
|
|
|
66
70
|
|
|
67
|
-
def get_simple_hash(repo: git.Repo,
|
|
71
|
+
def get_simple_hash(repo: git.Repo, git_hash: str, length: int = 7) -> str:
|
|
68
72
|
try:
|
|
69
|
-
return repo.git.rev_parse(
|
|
70
|
-
except Exception
|
|
71
|
-
|
|
73
|
+
return repo.git.rev_parse(git_hash, short=length)
|
|
74
|
+
except Exception:
|
|
75
|
+
logger.exception("Can't find hash %s", git_hash)
|
|
72
76
|
return None
|
|
73
77
|
|
|
74
78
|
|
|
75
|
-
def ref_to_hash(repo: git.Repo, ref: str, length=7):
|
|
79
|
+
def ref_to_hash(repo: git.Repo, ref: str, length: int = 7) -> str:
|
|
76
80
|
try:
|
|
77
81
|
return repo.git.rev_parse(ref, short=length)
|
|
78
|
-
except Exception
|
|
79
|
-
|
|
82
|
+
except Exception:
|
|
83
|
+
logger.exception("Can't find ref %s", ref)
|
|
80
84
|
return None
|
|
81
85
|
|
|
82
86
|
|
|
83
87
|
commit_pattern = re.compile(
|
|
84
|
-
r"(?P<emoji>:.+:|(\uD83C[\uDF00-\uDFFF])|(\uD83D[\uDC00-\uDE4F\uDE80-\uDEFF])|[\u2600-\u2B55])?( *)?(?P<type>[a-z]+)(\((?P<scope>.+?)\))?(?P<breaking>!)?: (?P<description>.+)",
|
|
88
|
+
r"(?P<emoji>:.+:|(\uD83C[\uDF00-\uDFFF])|(\uD83D[\uDC00-\uDE4F\uDE80-\uDEFF])|[\u2600-\u2B55])?( *)?(?P<type>[a-z]+)(\((?P<scope>.+?)\))?(?P<breaking>!)?: (?P<description>.+)", # noqa: E501
|
|
85
89
|
re.IGNORECASE,
|
|
86
90
|
)
|
|
87
91
|
|
|
88
92
|
|
|
89
|
-
def resolve_from_ref(repo, from_raw):
|
|
93
|
+
def resolve_from_ref(repo: git.Repo, from_raw: str) -> str:
|
|
90
94
|
if from_raw is not None:
|
|
91
95
|
return from_raw
|
|
92
96
|
last_tag = get_latest_git_tag(repo)
|
|
@@ -98,18 +102,18 @@ class Author:
|
|
|
98
102
|
name: str
|
|
99
103
|
email: str
|
|
100
104
|
|
|
101
|
-
def __str__(self):
|
|
105
|
+
def __str__(self) -> str:
|
|
102
106
|
return f"{self.name} <{self.email}>"
|
|
103
107
|
|
|
104
108
|
|
|
105
109
|
class TGITCommit:
|
|
106
|
-
def __init__(self, repo: git.Repo, commit: git.Commit, message_dict: dict):
|
|
110
|
+
def __init__(self, repo: git.Repo, commit: git.Commit, message_dict: dict) -> None:
|
|
107
111
|
commit_date = commit.committed_datetime
|
|
108
112
|
|
|
109
113
|
co_author_raws = [line for line in commit.message.split("\n") if line.lower().startswith("co-authored-by:")]
|
|
110
114
|
co_author_pattern = re.compile(r"Co-authored-by: (?P<name>.+?) <(?P<email>.+?)>", re.IGNORECASE)
|
|
111
115
|
co_authors = [co_author_pattern.match(co_author).groupdict() for co_author in co_author_raws]
|
|
112
|
-
authors = [{"name": commit.author.name, "email": commit.author.email}
|
|
116
|
+
authors = [{"name": commit.author.name, "email": commit.author.email}, *co_authors]
|
|
113
117
|
self.authors: list[Author] = [Author(**kwargs) for kwargs in authors]
|
|
114
118
|
self.date = commit_date
|
|
115
119
|
self.emoji = message_dict.get("emoji")
|
|
@@ -131,14 +135,14 @@ class TGITCommit:
|
|
|
131
135
|
)
|
|
132
136
|
|
|
133
137
|
|
|
134
|
-
def format_names(names):
|
|
138
|
+
def format_names(names: list[str]) -> str:
|
|
135
139
|
if not names:
|
|
136
140
|
return ""
|
|
137
141
|
|
|
138
142
|
if len(names) == 1:
|
|
139
143
|
return f"By {names[0]}"
|
|
140
144
|
|
|
141
|
-
if len(names) == 2:
|
|
145
|
+
if len(names) == 2: # noqa: PLR2004
|
|
142
146
|
return f"By {names[0]} and {names[1]}"
|
|
143
147
|
|
|
144
148
|
formatted_names = ", ".join(names[:-1])
|
|
@@ -147,7 +151,7 @@ def format_names(names):
|
|
|
147
151
|
return f"By {formatted_names}"
|
|
148
152
|
|
|
149
153
|
|
|
150
|
-
def get_remote_uri(url: str):
|
|
154
|
+
def get_remote_uri(url: str) -> str:
|
|
151
155
|
# SSH URL regex, with groups for domain, namespace and repo name
|
|
152
156
|
ssh_pattern = re.compile(r"git@([\w\.]+):(.+)/(.+)\.git")
|
|
153
157
|
# HTTPS URL regex, with groups for domain, namespace and repo name
|
|
@@ -184,7 +188,7 @@ def group_commits_by_type(commits: list[TGITCommit]) -> dict[str, list[TGITCommi
|
|
|
184
188
|
return commits_by_type
|
|
185
189
|
|
|
186
190
|
|
|
187
|
-
def generate_changelog(commits_by_type: dict[str, list[TGITCommit]], from_ref: str, to_ref: str, remote_uri: str = None) -> str:
|
|
191
|
+
def generate_changelog(commits_by_type: dict[str, list[TGITCommit]], from_ref: str, to_ref: str, remote_uri: str | None = None) -> str:
|
|
188
192
|
order = ["breaking", "feat", "fix", "refactor", "perf", "style", "docs", "chore"]
|
|
189
193
|
names = [
|
|
190
194
|
":rocket: Breaking Changes",
|
|
@@ -203,7 +207,7 @@ def generate_changelog(commits_by_type: dict[str, list[TGITCommit]], from_ref: s
|
|
|
203
207
|
else:
|
|
204
208
|
out_str += f"{from_ref}...{to_ref}\n\n"
|
|
205
209
|
|
|
206
|
-
def get_hash_link(commit: TGITCommit):
|
|
210
|
+
def get_hash_link(commit: TGITCommit) -> str:
|
|
207
211
|
if remote_uri:
|
|
208
212
|
return f"[{commit.hash}](https://{remote_uri}/commit/{commit.hash})"
|
|
209
213
|
return commit.hash
|
|
@@ -215,7 +219,6 @@ def generate_changelog(commits_by_type: dict[str, list[TGITCommit]], from_ref: s
|
|
|
215
219
|
# Sort commits by scope, if scope is None, put it to last
|
|
216
220
|
commits.sort(key=lambda c: c.scope or "zzzzz")
|
|
217
221
|
for commit in commits:
|
|
218
|
-
|
|
219
222
|
authors_str = format_names([f"[{a.name}](mailto:{a.email})" for a in commit.authors])
|
|
220
223
|
if commit.scope:
|
|
221
224
|
line = f"- **{commit.scope}**: {commit.description} - {authors_str} in {get_hash_link(commit)}\n"
|
|
@@ -226,39 +229,39 @@ def generate_changelog(commits_by_type: dict[str, list[TGITCommit]], from_ref: s
|
|
|
226
229
|
return out_str
|
|
227
230
|
|
|
228
231
|
|
|
229
|
-
def handle_changelog(args: ChangelogArgs):
|
|
232
|
+
def handle_changelog(args: ChangelogArgs) -> None:
|
|
230
233
|
repo = git.Repo(args.path)
|
|
231
234
|
|
|
232
235
|
if args.output:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
236
|
+
with Path(args.output).open("w") as output_file:
|
|
237
|
+
# 获取所有 tags
|
|
238
|
+
tags = repo.tags
|
|
239
|
+
# 获取第一个 commit
|
|
240
|
+
first_commit = get_first_commit_hash(repo)
|
|
241
|
+
points = [first_commit] + [tag.name for tag in tags]
|
|
242
|
+
points.reverse()
|
|
243
|
+
changelogs = ""
|
|
244
|
+
for i in range(len(points) - 1):
|
|
245
|
+
to_ref = points[i]
|
|
246
|
+
from_ref = points[i + 1]
|
|
247
|
+
changelog = get_changelog_by_range(repo, from_ref, to_ref)
|
|
248
|
+
changelogs += changelog
|
|
249
|
+
output_file.write(changelogs.strip("\n") + "\n")
|
|
247
250
|
from_raw = args.from_raw
|
|
248
251
|
to_raw = args.to_raw
|
|
249
252
|
|
|
250
253
|
from_ref, to_ref = get_git_commits_range(repo, from_raw, to_raw)
|
|
251
254
|
changelog = get_changelog_by_range(repo, from_ref, to_ref)
|
|
252
|
-
print()
|
|
253
|
-
print(changelog)
|
|
255
|
+
print() # noqa: T201
|
|
256
|
+
print(changelog) # noqa: T201
|
|
254
257
|
|
|
255
258
|
|
|
256
|
-
def get_changelog_by_range(repo: git.Repo, from_ref: str, to_ref: str):
|
|
259
|
+
def get_changelog_by_range(repo: git.Repo, from_ref: str, to_ref: str) -> str:
|
|
257
260
|
try:
|
|
258
261
|
origin_url = repo.remote().url
|
|
259
262
|
remote_uri = get_remote_uri(origin_url)
|
|
260
263
|
except ValueError:
|
|
261
|
-
warnings.warn("Origin not found, some of the link generation functions could not be enabled.")
|
|
264
|
+
warnings.warn("Origin not found, some of the link generation functions could not be enabled.", stacklevel=2)
|
|
262
265
|
remote_uri = None
|
|
263
266
|
|
|
264
267
|
tgit_commits = get_commits(repo, from_ref, to_ref)
|
|
@@ -266,7 +269,7 @@ def get_changelog_by_range(repo: git.Repo, from_ref: str, to_ref: str):
|
|
|
266
269
|
return generate_changelog(commits_by_type, from_ref, to_ref, remote_uri)
|
|
267
270
|
|
|
268
271
|
|
|
269
|
-
def get_git_commits_range(repo: git.Repo, from_raw: str, to_raw: str):
|
|
272
|
+
def get_git_commits_range(repo: git.Repo, from_raw: str, to_raw: str) -> tuple[str, str]:
|
|
270
273
|
from_ref = resolve_from_ref(repo, from_raw)
|
|
271
274
|
to_ref = "HEAD" if to_raw is None else to_raw
|
|
272
275
|
if to_ref == "HEAD":
|
|
@@ -13,7 +13,7 @@ from tgit.version import define_version_parser
|
|
|
13
13
|
rich.traceback.install()
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def main():
|
|
16
|
+
def main() -> None:
|
|
17
17
|
parser = argparse.ArgumentParser(
|
|
18
18
|
description="TGIT cli",
|
|
19
19
|
prog="tgit",
|
|
@@ -31,7 +31,7 @@ def main():
|
|
|
31
31
|
handle(parser, args)
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def handle(parser, args):
|
|
34
|
+
def handle(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None:
|
|
35
35
|
if hasattr(args, "func"):
|
|
36
36
|
args.func(args)
|
|
37
37
|
elif args.version:
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
import importlib.resources
|
|
2
3
|
import itertools
|
|
3
|
-
import os
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from
|
|
5
|
+
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
import git
|
|
8
|
+
from jinja2 import Environment, FileSystemLoader
|
|
8
9
|
from openai import AuthenticationError, OpenAI
|
|
9
10
|
from pydantic import BaseModel
|
|
10
|
-
from rich import print
|
|
11
|
+
from rich import print # noqa: A004
|
|
11
12
|
|
|
12
13
|
from tgit.settings import settings
|
|
13
14
|
from tgit.utils import get_commit_command, run_command, type_emojis
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
with importlib.resources.path("tgit", "prompts") as prompt_path:
|
|
17
|
+
env = Environment(loader=FileSystemLoader(prompt_path), autoescape=True)
|
|
16
18
|
|
|
19
|
+
commit_types = ["feat", "fix", "chore", "docs", "style", "refactor", "perf", "wip"]
|
|
20
|
+
commit_file = "commit.txt"
|
|
21
|
+
commit_prompt_template = env.get_template("commit.txt")
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
|
|
24
|
+
def define_commit_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
19
25
|
commit_type = ["feat", "fix", "chore", "docs", "style", "refactor", "perf"]
|
|
20
26
|
commit_settings = settings.get("commit", {})
|
|
21
27
|
types_settings = commit_settings.get("types", [])
|
|
@@ -26,7 +32,7 @@ def define_commit_parser(subparsers: argparse._SubParsersAction):
|
|
|
26
32
|
parser_commit = subparsers.add_parser("commit", help="commit changes following the conventional commit format")
|
|
27
33
|
parser_commit.add_argument(
|
|
28
34
|
"message",
|
|
29
|
-
help="
|
|
35
|
+
help="the first word should be the type, if the message is more than two parts, the second part should be the scope",
|
|
30
36
|
nargs="*",
|
|
31
37
|
)
|
|
32
38
|
parser_commit.add_argument("-v", "--verbose", action="count", default=0, help="increase output verbosity")
|
|
@@ -46,35 +52,29 @@ class CommitArgs:
|
|
|
46
52
|
|
|
47
53
|
class CommitData(BaseModel):
|
|
48
54
|
type: str
|
|
49
|
-
scope:
|
|
55
|
+
scope: str | None
|
|
50
56
|
msg: str
|
|
51
57
|
is_breaking: bool
|
|
52
58
|
|
|
53
59
|
|
|
54
60
|
def get_ai_command() -> str | None:
|
|
55
61
|
client = OpenAI()
|
|
56
|
-
|
|
57
|
-
current_dir = os.getcwd()
|
|
62
|
+
current_dir = Path.cwd()
|
|
58
63
|
try:
|
|
59
64
|
repo = git.Repo(current_dir, search_parent_directories=True)
|
|
60
65
|
except git.InvalidGitRepositoryError:
|
|
61
66
|
print("[yellow]Not a git repository[/yellow]")
|
|
62
|
-
return
|
|
67
|
+
return None
|
|
63
68
|
diff = repo.git.diff("--cached")
|
|
64
69
|
if not diff:
|
|
65
70
|
print("[yellow]No changes to commit, please add some changes before using AI[/yellow]")
|
|
66
|
-
return
|
|
67
|
-
types = "|".join(commit_type)
|
|
71
|
+
return None
|
|
68
72
|
try:
|
|
69
73
|
chat_completion = client.beta.chat.completions.parse(
|
|
70
74
|
messages=[
|
|
71
75
|
{
|
|
72
76
|
"role": "system",
|
|
73
|
-
"content":
|
|
74
|
-
+ "Only if the changes are not compatible with previous versions (change the API, break the build, etc.), you should suggest a breaking change. "
|
|
75
|
-
+ f"The type should be one of {types}. The message should in all lowercase. And it shoud be short, in just few words. "
|
|
76
|
-
+ "The scope should be short, it is better to be a single word. "
|
|
77
|
-
+ "The message should cover all the changes in the diff. It should be in present tense. If the change has many parts, you can && to separate them, and you should also shorten the message. ",
|
|
77
|
+
"content": commit_prompt_template.render(types=commit_types),
|
|
78
78
|
},
|
|
79
79
|
{"role": "user", "content": diff},
|
|
80
80
|
],
|
|
@@ -84,16 +84,20 @@ def get_ai_command() -> str | None:
|
|
|
84
84
|
)
|
|
85
85
|
except AuthenticationError:
|
|
86
86
|
print("[red]Could not authenticate with OpenAI, please check your API key.[/red]")
|
|
87
|
-
return
|
|
87
|
+
return None
|
|
88
88
|
resp = chat_completion.choices[0].message.parsed
|
|
89
|
-
return get_commit_command(
|
|
90
|
-
|
|
89
|
+
return get_commit_command(
|
|
90
|
+
resp.type,
|
|
91
|
+
resp.scope,
|
|
92
|
+
resp.msg,
|
|
93
|
+
use_emoji=settings.get("commit", {}).get("emoji", False),
|
|
94
|
+
is_breaking=resp.is_breaking,
|
|
95
|
+
)
|
|
91
96
|
|
|
92
|
-
def handle_commit(args: CommitArgs):
|
|
93
97
|
|
|
94
|
-
|
|
98
|
+
def handle_commit(args: CommitArgs) -> None:
|
|
95
99
|
prefix = ["", "!"]
|
|
96
|
-
choices = ["".join(data) for data in itertools.product(
|
|
100
|
+
choices = ["".join(data) for data in itertools.product(commit_types, prefix)] + ["ci", "test", "version"]
|
|
97
101
|
|
|
98
102
|
if args.ai:
|
|
99
103
|
command = get_ai_command()
|
|
@@ -105,7 +109,7 @@ def handle_commit(args: CommitArgs):
|
|
|
105
109
|
print("Please provide a commit message, or use --ai to generate by AI")
|
|
106
110
|
return
|
|
107
111
|
commit_type = messages[0]
|
|
108
|
-
if len(messages) > 2:
|
|
112
|
+
if len(messages) > 2: # noqa: PLR2004
|
|
109
113
|
commit_scope = messages[1]
|
|
110
114
|
commit_msg = " ".join(messages[2:])
|
|
111
115
|
else:
|
|
@@ -116,8 +120,8 @@ def handle_commit(args: CommitArgs):
|
|
|
116
120
|
print(f"Valid types: {choices}")
|
|
117
121
|
return
|
|
118
122
|
use_emoji = args.emoji
|
|
119
|
-
if use_emoji
|
|
123
|
+
if use_emoji is False:
|
|
120
124
|
use_emoji = settings.get("commit", {}).get("emoji", False)
|
|
121
125
|
is_breaking = args.breaking
|
|
122
|
-
command = get_commit_command(commit_type, commit_scope, commit_msg, use_emoji, is_breaking)
|
|
126
|
+
command = get_commit_command(commit_type, commit_scope, commit_msg, use_emoji=use_emoji, is_breaking=is_breaking)
|
|
123
127
|
run_command(command)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
You are a git bot. You should read the diff and suggest a commit message.
|
|
2
|
+
|
|
3
|
+
Type:
|
|
4
|
+
|
|
5
|
+
The type should be one of {{ types | join(', ') }}.
|
|
6
|
+
|
|
7
|
+
Scope:
|
|
8
|
+
|
|
9
|
+
The scope should be short, it is better to be a single word, if there are more than one word, you should use '-'
|
|
10
|
+
|
|
11
|
+
Message:
|
|
12
|
+
|
|
13
|
+
The message should in all lowercase.
|
|
14
|
+
The message should be short, in just few words.
|
|
15
|
+
The message should cover all the changes in the diff.
|
|
16
|
+
The message should be in present tense.
|
|
17
|
+
If the change has many parts, you can '&&' to separate them, and you should also shorten the message.
|
|
18
|
+
|
|
19
|
+
Breaking Change:
|
|
20
|
+
|
|
21
|
+
Only if the changes are not compatible with previous versions (change the API, break the build, etc.), you should suggest a breaking change.
|
|
22
|
+
|
|
23
|
+
Additional Considerations:
|
|
24
|
+
|
|
25
|
+
Search the name and instruction online for the copyright content check.
|
|
26
|
+
Make sure to search the name and instruction online for the copyright content check.
|
|
@@ -26,7 +26,7 @@ type_emojis = {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
def get_commit_command(commit_type: str, commit_scope:
|
|
29
|
+
def get_commit_command(commit_type: str, commit_scope: str | None, commit_msg: str, *, use_emoji: bool = False, is_breaking: bool = False):
|
|
30
30
|
if commit_type.endswith("!"):
|
|
31
31
|
commit_type = commit_type[:-1]
|
|
32
32
|
is_breaking = True
|
|
@@ -42,8 +42,8 @@ def get_commit_command(commit_type: str, commit_scope: Optional[str], commit_msg
|
|
|
42
42
|
return f'git commit -m "{msg}"'
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def simple_run_command(command: str):
|
|
46
|
-
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
45
|
+
def simple_run_command(command: str) -> None:
|
|
46
|
+
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # noqa: S602
|
|
47
47
|
stdout, stderr = process.communicate()
|
|
48
48
|
if stderr != b"" and process.returncode != 0:
|
|
49
49
|
sys.stderr.write(stderr.decode())
|
|
@@ -51,7 +51,7 @@ def simple_run_command(command: str):
|
|
|
51
51
|
sys.stdout.write(stdout.decode())
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
def run_command(command: str):
|
|
54
|
+
def run_command(command: str) -> None:
|
|
55
55
|
if settings.get("show_command", True):
|
|
56
56
|
panel = Panel.fit(
|
|
57
57
|
Syntax(command, "bash", line_numbers=False, theme="github-dark", background_color="default", word_wrap=True),
|
|
@@ -62,11 +62,10 @@ def run_command(command: str):
|
|
|
62
62
|
title_align="left",
|
|
63
63
|
subtitle_align="right",
|
|
64
64
|
)
|
|
65
|
-
print()
|
|
65
|
+
print() # noqa: T201
|
|
66
66
|
console.print(panel)
|
|
67
67
|
|
|
68
68
|
if not settings.get("skip_confirm", False):
|
|
69
|
-
|
|
70
69
|
ok = inquirer.prompt([inquirer.Confirm("continue", message="Do you want to continue?", default=True)])
|
|
71
70
|
if not ok or not ok["continue"]:
|
|
72
71
|
return
|