tgit 0.13.2__tar.gz → 0.16.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.
- {tgit-0.13.2 → tgit-0.16.0}/CHANGELOG.md +86 -2
- {tgit-0.13.2 → tgit-0.16.0}/PKG-INFO +1 -1
- {tgit-0.13.2 → tgit-0.16.0}/pyproject.toml +1 -1
- {tgit-0.13.2 → tgit-0.16.0}/tgit/changelog.py +78 -30
- {tgit-0.13.2 → tgit-0.16.0}/tgit/config.py +1 -1
- tgit-0.16.0/tgit/prompts/commit.txt +62 -0
- {tgit-0.13.2 → tgit-0.16.0}/tgit/version.py +33 -4
- {tgit-0.13.2 → tgit-0.16.0}/uv.lock +276 -276
- tgit-0.13.2/tgit/prompts/commit.txt +0 -45
- {tgit-0.13.2 → tgit-0.16.0}/.github/workflows/build.yml +0 -0
- {tgit-0.13.2 → tgit-0.16.0}/.gitignore +0 -0
- {tgit-0.13.2 → tgit-0.16.0}/.python-version +0 -0
- {tgit-0.13.2 → tgit-0.16.0}/.tgit.yml +0 -0
- {tgit-0.13.2 → tgit-0.16.0}/README.md +0 -0
- {tgit-0.13.2 → tgit-0.16.0}/scripts/publish.sh +0 -0
- {tgit-0.13.2 → tgit-0.16.0}/tgit/__init__.py +0 -0
- {tgit-0.13.2 → tgit-0.16.0}/tgit/add.py +0 -0
- {tgit-0.13.2 → tgit-0.16.0}/tgit/cli.py +0 -0
- {tgit-0.13.2 → tgit-0.16.0}/tgit/commit.py +0 -0
- {tgit-0.13.2 → tgit-0.16.0}/tgit/settings.py +0 -0
- {tgit-0.13.2 → tgit-0.16.0}/tgit/utils.py +0 -0
|
@@ -1,6 +1,90 @@
|
|
|
1
|
-
##
|
|
1
|
+
## v0.16.0
|
|
2
2
|
|
|
3
|
-
[v0.
|
|
3
|
+
[v0.15.1...v0.16.0](https://github.com/Jannchie/tgit/compare/v0.15.1...v0.16.0)
|
|
4
|
+
|
|
5
|
+
### :sparkles: Features
|
|
6
|
+
|
|
7
|
+
- **version-changelog**: add interactive changelog generation on version bump - By [Jianqi Pan](mailto:jannchie@gmail.com) in [2c6c3b3](https://github.com/Jannchie/tgit/commit/2c6c3b3)
|
|
8
|
+
|
|
9
|
+
### :adhesive_bandage: Fixes
|
|
10
|
+
|
|
11
|
+
- **changelog**: report no changes when changelog empty - By [Jianqi Pan](mailto:jannchie@gmail.com) in [02d94ff](https://github.com/Jannchie/tgit/commit/02d94ff)
|
|
12
|
+
|
|
13
|
+
### :art: Refactors
|
|
14
|
+
|
|
15
|
+
- **changelog**: simplify current tag handling in changelog - By [Jianqi Pan](mailto:jannchie@gmail.com) in [6ac7903](https://github.com/Jannchie/tgit/commit/6ac7903)
|
|
16
|
+
|
|
17
|
+
## v0.15.1
|
|
18
|
+
|
|
19
|
+
[v0.15.0...v0.15.1](https://github.com/Jannchie/tgit/compare/v0.15.0...v0.15.1)
|
|
20
|
+
|
|
21
|
+
### :wrench: Chores
|
|
22
|
+
|
|
23
|
+
- **deps**: update lock file - By [Jianqi Pan](mailto:jannchie@gmail.com) in [4ab3b1e](https://github.com/Jannchie/tgit/commit/4ab3b1e)
|
|
24
|
+
|
|
25
|
+
## v0.15.0
|
|
26
|
+
|
|
27
|
+
[v0.14.2...v0.15.0](https://github.com/Jannchie/tgit/compare/v0.14.2...v0.15.0)
|
|
28
|
+
|
|
29
|
+
### :sparkles: Features
|
|
30
|
+
|
|
31
|
+
- **prompts**: revise commit message instructions for clarity - By [Jianqi Pan](mailto:jannchie@gmail.com) in [b58f9ec](https://github.com/Jannchie/tgit/commit/b58f9ec)
|
|
32
|
+
|
|
33
|
+
## v0.14.2
|
|
34
|
+
|
|
35
|
+
[v0.14.1...v0.14.2](https://github.com/Jannchie/tgit/compare/v0.14.1...v0.14.2)
|
|
36
|
+
|
|
37
|
+
### :adhesive_bandage: Fixes
|
|
38
|
+
|
|
39
|
+
- **version**: fix bump logic for v0 breaking changes to minor - By [Jianqi Pan](mailto:jannchie@gmail.com) in [0f14045](https://github.com/Jannchie/tgit/commit/0f14045)
|
|
40
|
+
|
|
41
|
+
### :art: Refactors
|
|
42
|
+
|
|
43
|
+
- **changelog**: extract changelog segment, write helpers && simplify handle logic - By [Jianqi Pan](mailto:jannchie@gmail.com) in [ebc1e17](https://github.com/Jannchie/tgit/commit/ebc1e17)
|
|
44
|
+
|
|
45
|
+
### :lipstick: Styles
|
|
46
|
+
|
|
47
|
+
- **changelog**: add extra newline before old changelog content && remove noqa for rich print import - By [Jianqi Pan](mailto:jannchie@gmail.com) in [ca93aaf](https://github.com/Jannchie/tgit/commit/ca93aaf)
|
|
48
|
+
|
|
49
|
+
### :memo: Documentation
|
|
50
|
+
|
|
51
|
+
- **changelog**: update changelog for v0.14.0 and v0.14.1 - By [Jianqi Pan](mailto:jannchie@gmail.com) in [3d26e26](https://github.com/Jannchie/tgit/commit/3d26e26)
|
|
52
|
+
|
|
53
|
+
## v0.14.1
|
|
54
|
+
|
|
55
|
+
[v0.14.0...v0.14.1](https://github.com/Jannchie/tgit/compare/v0.14.0...v0.14.1)
|
|
56
|
+
|
|
57
|
+
### :adhesive_bandage: Fixes
|
|
58
|
+
|
|
59
|
+
- **changelog**: prepend new changelog entries if file exists - By [Jianqi Pan](mailto:jannchie@gmail.com) in [c4b3dff](https://github.com/Jannchie/tgit/commit/c4b3dff)
|
|
60
|
+
|
|
61
|
+
## v0.14.0
|
|
62
|
+
|
|
63
|
+
[v0.13.2...v0.14.0](https://github.com/Jannchie/tgit/compare/v0.13.2...v0.14.0)
|
|
64
|
+
|
|
65
|
+
### :sparkles: Features
|
|
66
|
+
|
|
67
|
+
- **changelog**: add incremental changelog generation && support appending to existing file - By [Jianqi Pan](mailto:jannchie@gmail.com) in [2d193a5](https://github.com/Jannchie/tgit/commit/2d193a5)
|
|
68
|
+
|
|
69
|
+
### :adhesive_bandage: Fixes
|
|
70
|
+
|
|
71
|
+
- **changelog**: remove head from default changelog range && update changelog entries - By [Jianqi Pan](mailto:jannchie@gmail.com) in [f6509e3](https://github.com/Jannchie/tgit/commit/f6509e3)
|
|
72
|
+
|
|
73
|
+
## v0.13.2
|
|
74
|
+
|
|
75
|
+
[v0.13.1...v0.13.2](https://github.com/Jannchie/tgit/compare/v0.13.1...v0.13.2)
|
|
76
|
+
|
|
77
|
+
### :adhesive_bandage: Fixes
|
|
78
|
+
|
|
79
|
+
- **cli**: suppress import error for openai in thread - By [Jianqi Pan](mailto:jannchie@gmail.com) in [4973127](https://github.com/Jannchie/tgit/commit/4973127)
|
|
80
|
+
|
|
81
|
+
## v0.13.1
|
|
82
|
+
|
|
83
|
+
[v0.13.0...v0.13.1](https://github.com/Jannchie/tgit/compare/v0.13.0...v0.13.1)
|
|
84
|
+
|
|
85
|
+
### :adhesive_bandage: Fixes
|
|
86
|
+
|
|
87
|
+
- **commit**: update no-files message for ai command - By [Jianqi Pan](mailto:jannchie@gmail.com) in [cda3740](https://github.com/Jannchie/tgit/commit/cda3740)
|
|
4
88
|
|
|
5
89
|
### :wrench: Chores
|
|
6
90
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tgit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.0
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tgit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.16.0"
|
|
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 = [
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import logging
|
|
2
3
|
import re
|
|
3
4
|
import warnings
|
|
@@ -231,27 +232,88 @@ def generate_changelog(commits_by_type: dict[str, list[TGITCommit]], from_ref: s
|
|
|
231
232
|
return out_str
|
|
232
233
|
|
|
233
234
|
|
|
234
|
-
def
|
|
235
|
-
|
|
235
|
+
def extract_latest_tag_from_changelog(filepath: str) -> str | None:
|
|
236
|
+
filepath = Path(filepath)
|
|
237
|
+
with contextlib.suppress(FileNotFoundError), filepath.open(encoding="utf-8") as f:
|
|
238
|
+
for line in f:
|
|
239
|
+
if line.startswith("## "):
|
|
240
|
+
return line.strip().removeprefix("## ").strip()
|
|
241
|
+
return None
|
|
236
242
|
|
|
243
|
+
|
|
244
|
+
def prepare_changelog_segments(
|
|
245
|
+
repo: git.Repo,
|
|
246
|
+
latest_tag_in_file: str | None = None,
|
|
247
|
+
current_tag: str | None = None,
|
|
248
|
+
) -> list[tuple[str, str, str, str]]:
|
|
249
|
+
tags = sorted(repo.tags, key=lambda t: t.commit.committed_datetime)
|
|
250
|
+
if not tags:
|
|
251
|
+
print("[yellow]No tags found in the repository.[/yellow]")
|
|
252
|
+
return []
|
|
253
|
+
first_commit = get_first_commit_hash(repo)
|
|
254
|
+
|
|
255
|
+
points = [first_commit] + [tag.commit.hexsha for tag in tags]
|
|
256
|
+
point_names = [first_commit] + [tag.name for tag in tags]
|
|
257
|
+
if current_tag is not None:
|
|
258
|
+
point_names += ["HEAD"]
|
|
259
|
+
points += ["HEAD"]
|
|
260
|
+
start_idx = 1
|
|
261
|
+
if latest_tag_in_file and latest_tag_in_file in point_names:
|
|
262
|
+
idx = point_names.index(latest_tag_in_file)
|
|
263
|
+
start_idx = idx + 1
|
|
264
|
+
segments: list[tuple[str, str, str, str]] = [
|
|
265
|
+
(points[i - 1], points[i], point_names[i - 1], point_names[i]) for i in reversed(range(start_idx, len(points)))
|
|
266
|
+
]
|
|
267
|
+
if current_tag is not None:
|
|
268
|
+
segments[0] = (segments[0][0], "HEAD", segments[0][2], current_tag)
|
|
269
|
+
return segments
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def write_changelog_prepend(filepath: str, new_content: str) -> None:
|
|
273
|
+
path = Path(filepath)
|
|
274
|
+
if path.exists():
|
|
275
|
+
with path.open("r", encoding="utf-8") as f:
|
|
276
|
+
old_content = f.read()
|
|
277
|
+
with path.open("w", encoding="utf-8") as f:
|
|
278
|
+
f.write(new_content.strip("\n") + "\n\n" + old_content)
|
|
279
|
+
else:
|
|
280
|
+
with path.open("w", encoding="utf-8") as f:
|
|
281
|
+
f.write(new_content.strip("\n") + "\n")
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def print_and_write_changelog(
|
|
285
|
+
changelog: str,
|
|
286
|
+
output_path: str | None = None,
|
|
287
|
+
*,
|
|
288
|
+
prepend: bool = False,
|
|
289
|
+
) -> None:
|
|
290
|
+
if not changelog or not changelog.strip():
|
|
291
|
+
print("[yellow]No changes found, nothing to output.[/yellow]")
|
|
292
|
+
return
|
|
293
|
+
print()
|
|
294
|
+
print(changelog.strip("\n"))
|
|
295
|
+
if output_path:
|
|
296
|
+
if prepend:
|
|
297
|
+
write_changelog_prepend(output_path, changelog)
|
|
298
|
+
else:
|
|
299
|
+
with Path(output_path).open("w", encoding="utf-8") as output_file:
|
|
300
|
+
output_file.write(changelog.strip("\n") + "\n")
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def handle_changelog(args: ChangelogArgs, current_tag: str | None = None) -> None:
|
|
304
|
+
repo = git.Repo(args.path)
|
|
237
305
|
from_raw = args.from_raw
|
|
238
306
|
to_raw = args.to_raw
|
|
239
|
-
|
|
240
|
-
|
|
307
|
+
latest_tag_in_file = None
|
|
308
|
+
if args.output and Path(args.output).exists() and from_raw is None and to_raw is None:
|
|
309
|
+
latest_tag_in_file = extract_latest_tag_from_changelog(args.output)
|
|
310
|
+
# 默认:统计所有 tag 之间的差分(不包含最新 tag 到 HEAD)
|
|
241
311
|
if from_raw is None and to_raw is None:
|
|
242
|
-
|
|
243
|
-
first_commit = get_first_commit_hash(repo)
|
|
244
|
-
points = [first_commit] + [tag.commit.hexsha for tag in tags] + [repo.head.commit.hexsha]
|
|
245
|
-
point_names = [first_commit] + [tag.name for tag in tags] + ["HEAD"]
|
|
312
|
+
segments = prepare_changelog_segments(repo, latest_tag_in_file, current_tag)
|
|
246
313
|
changelogs = ""
|
|
247
314
|
with Progress() as progress:
|
|
248
|
-
task = progress.add_task("Generating changelog...", total=len(
|
|
249
|
-
|
|
250
|
-
for i in reversed(range(len(points) - 1)):
|
|
251
|
-
from_hash = points[i]
|
|
252
|
-
to_hash = points[i + 1]
|
|
253
|
-
from_name = point_names[i]
|
|
254
|
-
to_name = point_names[i + 1]
|
|
315
|
+
task = progress.add_task("Generating changelog...", total=len(segments))
|
|
316
|
+
for from_hash, to_hash, from_name, to_name in segments:
|
|
255
317
|
raw_commits = list(repo.iter_commits(f"{from_hash}...{to_hash}"))
|
|
256
318
|
tgit_commits = []
|
|
257
319
|
for commit in raw_commits:
|
|
@@ -267,23 +329,9 @@ def handle_changelog(args: ChangelogArgs) -> None:
|
|
|
267
329
|
changelog = generate_changelog(commits_by_type, from_name, to_name, remote_uri)
|
|
268
330
|
changelogs += changelog
|
|
269
331
|
progress.update(task, advance=1)
|
|
270
|
-
|
|
271
|
-
with Path(args.output).open("w") as output_file:
|
|
272
|
-
output_file.write(changelogs.strip("\n") + "\n")
|
|
273
|
-
print()
|
|
274
|
-
print(changelogs.strip("\n"))
|
|
332
|
+
print_and_write_changelog(changelogs, args.output, prepend=bool(latest_tag_in_file))
|
|
275
333
|
return
|
|
276
334
|
|
|
277
|
-
# 否则输出指定范围的 changelog
|
|
278
|
-
from_ref, to_ref = get_git_commits_range(repo, from_raw, to_raw)
|
|
279
|
-
changelog = get_changelog_by_range(repo, from_ref, to_ref)
|
|
280
|
-
if args.output:
|
|
281
|
-
with Path(args.output).open("w") as output_file:
|
|
282
|
-
output_file.write(changelog.strip("\n") + "\n")
|
|
283
|
-
else:
|
|
284
|
-
print()
|
|
285
|
-
print(changelog)
|
|
286
|
-
|
|
287
335
|
|
|
288
336
|
def get_changelog_by_range(repo: git.Repo, from_ref: str, to_ref: str) -> str:
|
|
289
337
|
try:
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Git Commit Message Generator
|
|
2
|
+
|
|
3
|
+
You are a git commit message generator. Analyze the provided diff and generate an appropriate commit message following the Conventional Commits specification.
|
|
4
|
+
|
|
5
|
+
## Current Context
|
|
6
|
+
- **Branch**: {{ branch }}
|
|
7
|
+
- **Commit Type**: {% if specified_type is defined %}"{{ specified_type }}" (user-specified - MUST be used){% else %}Choose from: {{ types | join(', ') }}{% endif %}
|
|
8
|
+
|
|
9
|
+
## Commit Message Requirements
|
|
10
|
+
|
|
11
|
+
### Type
|
|
12
|
+
{% if specified_type is defined %}
|
|
13
|
+
**MANDATORY**: Use "{{ specified_type }}" as specified by the user.
|
|
14
|
+
{% else %}
|
|
15
|
+
Select the most appropriate type from the available options based on the nature of the changes.
|
|
16
|
+
{% endif %}
|
|
17
|
+
|
|
18
|
+
### Scope
|
|
19
|
+
- Use a single word when possible
|
|
20
|
+
- If multiple words needed, separate with hyphens (e.g., `user-auth`)
|
|
21
|
+
- Keep it concise and descriptive
|
|
22
|
+
- Optional if changes are global or unclear in scope
|
|
23
|
+
|
|
24
|
+
### Message
|
|
25
|
+
- Write in lowercase
|
|
26
|
+
- Use present tense (e.g., "add feature" not "added feature")
|
|
27
|
+
- Be concise but descriptive (aim for 3-7 words)
|
|
28
|
+
- Cover the primary change(s) in the diff
|
|
29
|
+
- If multiple distinct changes, separate with " && " (e.g., "update api && fix validation")
|
|
30
|
+
|
|
31
|
+
### Breaking Changes
|
|
32
|
+
Mark `is_breaking: true` only if changes:
|
|
33
|
+
- Break existing API contracts
|
|
34
|
+
- Require user action for compatibility
|
|
35
|
+
- Remove or significantly change existing functionality
|
|
36
|
+
|
|
37
|
+
## Analysis Process
|
|
38
|
+
1. Review the diff comprehensively
|
|
39
|
+
2. Identify the primary type of change
|
|
40
|
+
3. Determine appropriate scope from modified files/areas
|
|
41
|
+
4. Craft a concise message covering main changes
|
|
42
|
+
5. Assess backward compatibility impact
|
|
43
|
+
|
|
44
|
+
## Output Format
|
|
45
|
+
Return valid JSON matching this structure:
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"type": "string",
|
|
49
|
+
"scope": "string|null",
|
|
50
|
+
"msg": "string",
|
|
51
|
+
"is_breaking": "boolean"
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
```json
|
|
57
|
+
{"type": "feat", "scope": "auth", "msg": "add oauth2 login", "is_breaking": false}
|
|
58
|
+
{"type": "fix", "scope": "api", "msg": "handle null user responses", "is_breaking": false}
|
|
59
|
+
{"type": "refactor", "scope": null, "msg": "restructure project layout", "is_breaking": true}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Now analyze the provided diff and generate the commit message.
|
|
@@ -273,9 +273,12 @@ def get_version_from_git(path: Path) -> Version | None:
|
|
|
273
273
|
return None
|
|
274
274
|
|
|
275
275
|
|
|
276
|
-
def get_default_bump_by_commits_dict(commits_by_type: dict[str, list[git.Commit]]) -> str:
|
|
277
|
-
#
|
|
278
|
-
if
|
|
276
|
+
def get_default_bump_by_commits_dict(commits_by_type: dict[str, list[git.Commit]], prev_version: Version | None = None) -> str:
|
|
277
|
+
# v0.x.x breaking change 只 bump minor,v1+ 才 bump major
|
|
278
|
+
if prev_version and prev_version.major == 0:
|
|
279
|
+
if commits_by_type.get("breaking"):
|
|
280
|
+
return "minor"
|
|
281
|
+
elif commits_by_type.get("breaking"):
|
|
279
282
|
return "major"
|
|
280
283
|
if commits_by_type.get("feat"):
|
|
281
284
|
return "minor"
|
|
@@ -289,6 +292,32 @@ def handle_version(args: VersionArgs) -> None:
|
|
|
289
292
|
reclusive = args.recursive
|
|
290
293
|
|
|
291
294
|
if next_version := get_next_version(args, prev_version, verbose):
|
|
295
|
+
# 获取目标 tag 名
|
|
296
|
+
target_tag = f"v{next_version}"
|
|
297
|
+
# 询问是否生成 changelog
|
|
298
|
+
ans = inquirer.prompt(
|
|
299
|
+
[
|
|
300
|
+
inquirer.Confirm(
|
|
301
|
+
"gen_changelog",
|
|
302
|
+
message=f"should generate changelog for {target_tag}?",
|
|
303
|
+
default=True,
|
|
304
|
+
),
|
|
305
|
+
],
|
|
306
|
+
)
|
|
307
|
+
if ans and ans.get("gen_changelog"):
|
|
308
|
+
# 构造 changelog 参数对象
|
|
309
|
+
from argparse import Namespace
|
|
310
|
+
|
|
311
|
+
changelog_args = Namespace(
|
|
312
|
+
path=path,
|
|
313
|
+
from_raw=None,
|
|
314
|
+
to_raw=None,
|
|
315
|
+
output="CHANGELOG.md",
|
|
316
|
+
verbose=verbose,
|
|
317
|
+
)
|
|
318
|
+
from tgit.changelog import handle_changelog
|
|
319
|
+
|
|
320
|
+
handle_changelog(changelog_args, current_tag=target_tag)
|
|
292
321
|
update_version_files(args, next_version, verbose, reclusive=reclusive)
|
|
293
322
|
execute_git_commands(args, next_version, verbose)
|
|
294
323
|
|
|
@@ -311,7 +340,7 @@ def get_next_version(args: VersionArgs, prev_version: Version, verbose: int) ->
|
|
|
311
340
|
from_ref, to_ref = get_git_commits_range(repo, None, None)
|
|
312
341
|
tgit_commits = get_commits(repo, from_ref, to_ref)
|
|
313
342
|
commits_by_type = group_commits_by_type(tgit_commits)
|
|
314
|
-
default_bump = get_default_bump_by_commits_dict(commits_by_type)
|
|
343
|
+
default_bump = get_default_bump_by_commits_dict(commits_by_type, prev_version)
|
|
315
344
|
|
|
316
345
|
choices = [
|
|
317
346
|
VersionChoice(prev_version, bump) for bump in ["patch", "minor", "major", "prepatch", "preminor", "premajor", "previous", "custom"]
|