git-gcm 1.0.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.
- git_gcm-1.0.0/LICENSE +17 -0
- git_gcm-1.0.0/PKG-INFO +82 -0
- git_gcm-1.0.0/README.md +53 -0
- git_gcm-1.0.0/gcm/__init__.py +3 -0
- git_gcm-1.0.0/gcm/cli.py +179 -0
- git_gcm-1.0.0/gcm/git_utils.py +130 -0
- git_gcm-1.0.0/gcm/llm_client.py +84 -0
- git_gcm-1.0.0/gcm/prompts.py +87 -0
- git_gcm-1.0.0/git_gcm.egg-info/PKG-INFO +82 -0
- git_gcm-1.0.0/git_gcm.egg-info/SOURCES.txt +14 -0
- git_gcm-1.0.0/git_gcm.egg-info/dependency_links.txt +1 -0
- git_gcm-1.0.0/git_gcm.egg-info/entry_points.txt +2 -0
- git_gcm-1.0.0/git_gcm.egg-info/requires.txt +2 -0
- git_gcm-1.0.0/git_gcm.egg-info/top_level.txt +1 -0
- git_gcm-1.0.0/pyproject.toml +40 -0
- git_gcm-1.0.0/setup.cfg +4 -0
git_gcm-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
Copyright (c) 2026 falconluca
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
The above copyright notice and this permission notice shall be included in all
|
|
10
|
+
copies or substantial portions of the Software.
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
12
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
13
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
14
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
15
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
16
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
17
|
+
SOFTWARE.
|
git_gcm-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: git-gcm
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Git Commit Message 自动生成工具
|
|
5
|
+
Author-email: Luca <yangshaoxiong5545@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/falconluca/gcm
|
|
8
|
+
Project-URL: Repository, https://github.com/falconluca/gcm
|
|
9
|
+
Keywords: git,commit,message,ai,llm,openai,gcm
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: openai>=1.0.0
|
|
27
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# GCM
|
|
31
|
+
|
|
32
|
+
基于 AI 的 Git Commit Message 自动生成工具,支持所有 OpenAI 协议兼容的大模型服务。
|
|
33
|
+
|
|
34
|
+
## 安装
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
make build
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 配置
|
|
41
|
+
|
|
42
|
+
设置环境变量:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
export GCM_API_KEY="your-api-key"
|
|
46
|
+
export GCM_API_URL="https://api.openai.com/v1"
|
|
47
|
+
export GCM_MODEL="gpt-4o-mini"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 使用
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
git add .
|
|
54
|
+
gcm # 生成精简 commit message
|
|
55
|
+
gcm -v # 生成详细 commit message
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 示例
|
|
59
|
+
|
|
60
|
+
**精简模式:**
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
feat(auth): 添加用户登录功能
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**详细模式 (`-v`):**
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
feat(auth): 添加用户登录功能
|
|
70
|
+
|
|
71
|
+
- 实现 JWT token 认证
|
|
72
|
+
- 添加登录表单验证
|
|
73
|
+
- 集成第三方 OAuth 登录
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Git 集成
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git commit -m "$(gcm)"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
MIT License
|
git_gcm-1.0.0/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# GCM
|
|
2
|
+
|
|
3
|
+
基于 AI 的 Git Commit Message 自动生成工具,支持所有 OpenAI 协议兼容的大模型服务。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
make build
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 配置
|
|
12
|
+
|
|
13
|
+
设置环境变量:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
export GCM_API_KEY="your-api-key"
|
|
17
|
+
export GCM_API_URL="https://api.openai.com/v1"
|
|
18
|
+
export GCM_MODEL="gpt-4o-mini"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 使用
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
git add .
|
|
25
|
+
gcm # 生成精简 commit message
|
|
26
|
+
gcm -v # 生成详细 commit message
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 示例
|
|
30
|
+
|
|
31
|
+
**精简模式:**
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
feat(auth): 添加用户登录功能
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**详细模式 (`-v`):**
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
feat(auth): 添加用户登录功能
|
|
41
|
+
|
|
42
|
+
- 实现 JWT token 认证
|
|
43
|
+
- 添加登录表单验证
|
|
44
|
+
- 集成第三方 OAuth 登录
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Git 集成
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git commit -m "$(gcm)"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
MIT License
|
git_gcm-1.0.0/gcm/cli.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""命令行入口模块"""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
|
|
11
|
+
from gcm import __version__
|
|
12
|
+
from gcm.git_utils import (
|
|
13
|
+
is_git_repo,
|
|
14
|
+
get_staged_changes,
|
|
15
|
+
StagedChanges
|
|
16
|
+
)
|
|
17
|
+
from gcm.prompts import build_user_prompt, get_system_prompt
|
|
18
|
+
from gcm.llm_client import LLMClient, LLMConfig
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def find_env_file() -> Optional[Path]:
|
|
22
|
+
"""查找 .env 文件
|
|
23
|
+
|
|
24
|
+
查找顺序:
|
|
25
|
+
1. 当前目录的 .env
|
|
26
|
+
2. 父目录(向上查找直到 git 根目录)
|
|
27
|
+
3. 用户主目录的 .env
|
|
28
|
+
"""
|
|
29
|
+
# 当前目录
|
|
30
|
+
env_path = Path.cwd() / ".env"
|
|
31
|
+
if env_path.exists():
|
|
32
|
+
return env_path
|
|
33
|
+
|
|
34
|
+
# 向上查找直到 git 根目录
|
|
35
|
+
current = Path.cwd()
|
|
36
|
+
while current != current.parent:
|
|
37
|
+
current = current.parent
|
|
38
|
+
env_path = current / ".env"
|
|
39
|
+
if env_path.exists():
|
|
40
|
+
return env_path
|
|
41
|
+
# 如果找到 .git 目录就停止
|
|
42
|
+
if (current / ".git").exists():
|
|
43
|
+
break
|
|
44
|
+
|
|
45
|
+
# 用户主目录
|
|
46
|
+
env_path = Path.home() / ".env"
|
|
47
|
+
if env_path.exists():
|
|
48
|
+
return env_path
|
|
49
|
+
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_env_files():
|
|
54
|
+
"""加载 .env 文件"""
|
|
55
|
+
# 加载用户主目录的 .env(优先级较低)
|
|
56
|
+
home_env = Path.home() / ".env"
|
|
57
|
+
if home_env.exists():
|
|
58
|
+
load_dotenv(home_env)
|
|
59
|
+
|
|
60
|
+
# 加载项目目录的 .env(优先级较高)
|
|
61
|
+
project_env = find_env_file()
|
|
62
|
+
if project_env:
|
|
63
|
+
load_dotenv(project_env)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def parse_args() -> argparse.Namespace:
|
|
67
|
+
"""解析命令行参数"""
|
|
68
|
+
parser = argparse.ArgumentParser(
|
|
69
|
+
prog="gcm",
|
|
70
|
+
description="自动生成 Git Commit Message"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"-v", "--verbose",
|
|
75
|
+
action="store_true",
|
|
76
|
+
help="生成详细的 commit message"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--api-base",
|
|
81
|
+
type=str,
|
|
82
|
+
help="API 基础 URL(覆盖 GCM_API_URL)"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
parser.add_argument(
|
|
86
|
+
"--api-key",
|
|
87
|
+
type=str,
|
|
88
|
+
help="API Key(覆盖 GCM_API_KEY,不推荐在命令行中使用)"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
parser.add_argument(
|
|
92
|
+
"-m", "--model",
|
|
93
|
+
type=str,
|
|
94
|
+
help="使用的模型名称(覆盖 GCM_MODEL)"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
parser.add_argument(
|
|
98
|
+
"--version",
|
|
99
|
+
action="version",
|
|
100
|
+
version=f"%(prog)s {__version__}"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return parser.parse_args()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def generate_commit_message(
|
|
107
|
+
changes: StagedChanges,
|
|
108
|
+
client: LLMClient,
|
|
109
|
+
verbose: bool = False
|
|
110
|
+
) -> str:
|
|
111
|
+
"""生成 commit message
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
changes: 暂存区变更
|
|
115
|
+
client: LLM 客户端
|
|
116
|
+
verbose: 是否详细模式
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
生成的 commit message
|
|
120
|
+
"""
|
|
121
|
+
system_prompt = get_system_prompt()
|
|
122
|
+
user_prompt = build_user_prompt(changes, verbose=verbose)
|
|
123
|
+
|
|
124
|
+
return client.chat(system_prompt, user_prompt)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def main():
|
|
128
|
+
"""主入口函数"""
|
|
129
|
+
# 加载 .env 文件
|
|
130
|
+
load_env_files()
|
|
131
|
+
|
|
132
|
+
args = parse_args()
|
|
133
|
+
|
|
134
|
+
# 检查是否在 git 仓库中
|
|
135
|
+
if not is_git_repo():
|
|
136
|
+
print("错误: 当前目录不在 Git 仓库中", file=sys.stderr)
|
|
137
|
+
sys.exit(1)
|
|
138
|
+
|
|
139
|
+
# 获取暂存区变更
|
|
140
|
+
changes = get_staged_changes()
|
|
141
|
+
|
|
142
|
+
if not changes.files:
|
|
143
|
+
print("暂存区没有变更。请先使用 'git add' 添加变更。", file=sys.stderr)
|
|
144
|
+
sys.exit(1)
|
|
145
|
+
|
|
146
|
+
# 创建配置(从环境变量加载)
|
|
147
|
+
config = LLMConfig()
|
|
148
|
+
|
|
149
|
+
# 命令行参数覆盖环境变量
|
|
150
|
+
if args.api_base:
|
|
151
|
+
config.api_base = args.api_base
|
|
152
|
+
if args.api_key:
|
|
153
|
+
config.api_key = args.api_key
|
|
154
|
+
if args.model:
|
|
155
|
+
config.model = args.model
|
|
156
|
+
|
|
157
|
+
# 创建 LLM 客户端
|
|
158
|
+
client = LLMClient(config)
|
|
159
|
+
|
|
160
|
+
# 生成 commit message
|
|
161
|
+
try:
|
|
162
|
+
commit_msg = generate_commit_message(
|
|
163
|
+
changes,
|
|
164
|
+
client,
|
|
165
|
+
verbose=args.verbose
|
|
166
|
+
)
|
|
167
|
+
except ValueError as e:
|
|
168
|
+
print(f"配置错误: {e}", file=sys.stderr)
|
|
169
|
+
sys.exit(1)
|
|
170
|
+
except RuntimeError as e:
|
|
171
|
+
print(f"生成失败: {e}", file=sys.stderr)
|
|
172
|
+
sys.exit(1)
|
|
173
|
+
|
|
174
|
+
# 输出结果
|
|
175
|
+
print(commit_msg)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if __name__ == "__main__":
|
|
179
|
+
main()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Git 操作工具模块"""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class FileChange:
|
|
11
|
+
"""文件变更信息"""
|
|
12
|
+
status: str # A=新增, M=修改, D=删除, R=重命名
|
|
13
|
+
old_path: Optional[str] # 旧路径(重命名时使用)
|
|
14
|
+
new_path: str # 新路径
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class StagedChanges:
|
|
19
|
+
"""暂存区变更信息"""
|
|
20
|
+
files: List[FileChange]
|
|
21
|
+
diff_content: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def is_git_repo() -> bool:
|
|
25
|
+
"""检查当前目录是否在 git 仓库中"""
|
|
26
|
+
try:
|
|
27
|
+
result = subprocess.run(
|
|
28
|
+
["git", "rev-parse", "--is-inside-work-tree"],
|
|
29
|
+
capture_output=True,
|
|
30
|
+
text=True,
|
|
31
|
+
check=False
|
|
32
|
+
)
|
|
33
|
+
return result.returncode == 0 and result.stdout.strip() == "true"
|
|
34
|
+
except FileNotFoundError:
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_staged_files() -> List[FileChange]:
|
|
39
|
+
"""获取暂存区的文件列表"""
|
|
40
|
+
result = subprocess.run(
|
|
41
|
+
["git", "diff", "--cached", "--name-status", "--diff-filter=ACDMRT"],
|
|
42
|
+
capture_output=True,
|
|
43
|
+
text=True,
|
|
44
|
+
check=False
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if result.returncode != 0:
|
|
48
|
+
return []
|
|
49
|
+
|
|
50
|
+
files = []
|
|
51
|
+
for line in result.stdout.strip().split("\n"):
|
|
52
|
+
if not line:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
parts = line.split("\t")
|
|
56
|
+
status = parts[0][0] # 取状态首字母
|
|
57
|
+
|
|
58
|
+
if status == "R":
|
|
59
|
+
# 重命名: R\told_path\tnew_path
|
|
60
|
+
files.append(FileChange(
|
|
61
|
+
status=status,
|
|
62
|
+
old_path=parts[1],
|
|
63
|
+
new_path=parts[2]
|
|
64
|
+
))
|
|
65
|
+
elif status == "C":
|
|
66
|
+
# 复制: C\told_path\tnew_path
|
|
67
|
+
files.append(FileChange(
|
|
68
|
+
status=status,
|
|
69
|
+
old_path=parts[1],
|
|
70
|
+
new_path=parts[2]
|
|
71
|
+
))
|
|
72
|
+
else:
|
|
73
|
+
# 其他: A/M/D\tpath
|
|
74
|
+
files.append(FileChange(
|
|
75
|
+
status=status,
|
|
76
|
+
old_path=None,
|
|
77
|
+
new_path=parts[1]
|
|
78
|
+
))
|
|
79
|
+
|
|
80
|
+
return files
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_staged_diff() -> str:
|
|
84
|
+
"""获取暂存区的详细差异"""
|
|
85
|
+
result = subprocess.run(
|
|
86
|
+
["git", "diff", "--cached"],
|
|
87
|
+
capture_output=True,
|
|
88
|
+
text=True,
|
|
89
|
+
check=False
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if result.returncode != 0:
|
|
93
|
+
return ""
|
|
94
|
+
|
|
95
|
+
return result.stdout
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_staged_changes(max_diff_lines: int = 500) -> StagedChanges:
|
|
99
|
+
"""获取完整的暂存区变更信息
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
max_diff_lines: diff 内容最大行数限制,防止过长
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
StagedChanges 对象
|
|
106
|
+
"""
|
|
107
|
+
files = get_staged_files()
|
|
108
|
+
diff = get_staged_diff()
|
|
109
|
+
|
|
110
|
+
# 限制 diff 行数
|
|
111
|
+
if diff:
|
|
112
|
+
lines = diff.split("\n")
|
|
113
|
+
if len(lines) > max_diff_lines:
|
|
114
|
+
diff = "\n".join(lines[:max_diff_lines])
|
|
115
|
+
diff += f"\n\n... (truncated, {len(lines) - max_diff_lines} more lines)"
|
|
116
|
+
|
|
117
|
+
return StagedChanges(files=files, diff_content=diff)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get_status_symbol(status: str) -> str:
|
|
121
|
+
"""获取状态对应的符号/描述"""
|
|
122
|
+
symbols = {
|
|
123
|
+
"A": "新增",
|
|
124
|
+
"M": "修改",
|
|
125
|
+
"D": "删除",
|
|
126
|
+
"R": "重命名",
|
|
127
|
+
"C": "复制",
|
|
128
|
+
"T": "类型变更"
|
|
129
|
+
}
|
|
130
|
+
return symbols.get(status, status)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""LLM 客户端模块"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from openai import OpenAI
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class LLMConfig:
|
|
12
|
+
"""LLM 配置"""
|
|
13
|
+
api_base: str = "https://api.openai.com/v1"
|
|
14
|
+
api_key: Optional[str] = None
|
|
15
|
+
model: str = "gpt-4o-mini"
|
|
16
|
+
temperature: float = 0.7
|
|
17
|
+
max_tokens: int = 1000
|
|
18
|
+
timeout: int = 30
|
|
19
|
+
|
|
20
|
+
def __post_init__(self):
|
|
21
|
+
# 从环境变量获取配置
|
|
22
|
+
if self.api_key is None:
|
|
23
|
+
self.api_key = os.environ.get("GCM_API_KEY")
|
|
24
|
+
|
|
25
|
+
# 支持 GCM_API_URL 环境变量(兼容智谱等第三方服务)
|
|
26
|
+
if self.api_base == "https://api.openai.com/v1":
|
|
27
|
+
self.api_base = os.environ.get("GCM_API_URL", self.api_base)
|
|
28
|
+
|
|
29
|
+
# 支持 GCM_MODEL 环境变量
|
|
30
|
+
if self.model == "gpt-4o-mini":
|
|
31
|
+
self.model = os.environ.get("GCM_MODEL", self.model)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class LLMClient:
|
|
35
|
+
"""与大模型交互的客户端"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, config: Optional[LLMConfig] = None):
|
|
38
|
+
self.config = config or LLMConfig()
|
|
39
|
+
self._client: Optional[OpenAI] = None
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def client(self) -> OpenAI:
|
|
43
|
+
"""懒加载 OpenAI 客户端"""
|
|
44
|
+
if self._client is None:
|
|
45
|
+
if not self.config.api_key:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
"API Key 未配置。请设置环境变量 GCM_API_KEY"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
self._client = OpenAI(
|
|
51
|
+
api_key=self.config.api_key,
|
|
52
|
+
base_url=self.config.api_base,
|
|
53
|
+
timeout=self.config.timeout
|
|
54
|
+
)
|
|
55
|
+
return self._client
|
|
56
|
+
|
|
57
|
+
def chat(self, system_prompt: str, user_prompt: str) -> str:
|
|
58
|
+
"""发送聊天请求
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
system_prompt: 系统提示词
|
|
62
|
+
user_prompt: 用户提示词
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
模型生成的响应文本
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
ValueError: API Key 未配置
|
|
69
|
+
RuntimeError: API 调用失败
|
|
70
|
+
"""
|
|
71
|
+
try:
|
|
72
|
+
response = self.client.chat.completions.create(
|
|
73
|
+
model=self.config.model,
|
|
74
|
+
messages=[
|
|
75
|
+
{"role": "system", "content": system_prompt},
|
|
76
|
+
{"role": "user", "content": user_prompt}
|
|
77
|
+
],
|
|
78
|
+
temperature=self.config.temperature,
|
|
79
|
+
max_tokens=self.config.max_tokens
|
|
80
|
+
)
|
|
81
|
+
return response.choices[0].message.content
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
raise RuntimeError(f"API 调用失败: {e}")
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""提示词模板模块"""
|
|
2
|
+
|
|
3
|
+
from gcm.git_utils import StagedChanges, get_status_symbol
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# 系统提示词
|
|
7
|
+
SYSTEM_PROMPT = """你是一位专业的 Git 提交信息生成助手。你的任务是根据代码变更生成清晰、准确、符合规范的 commit message。
|
|
8
|
+
|
|
9
|
+
## 输出规则
|
|
10
|
+
|
|
11
|
+
1. 必须直接输出 commit message,不要有任何前言或解释
|
|
12
|
+
2. commit message 应该使用中文(除非用户指定英文)
|
|
13
|
+
3. 遵循 Conventional Commits 规范
|
|
14
|
+
|
|
15
|
+
## Commit Message 格式
|
|
16
|
+
|
|
17
|
+
### 精简模式
|
|
18
|
+
一行简洁的描述,格式:<type>(<scope>): <description>
|
|
19
|
+
|
|
20
|
+
### 详细模式
|
|
21
|
+
第一行:<type>(<scope>): <description>
|
|
22
|
+
(空行)
|
|
23
|
+
(body,详细描述变更内容和原因)
|
|
24
|
+
(空行)
|
|
25
|
+
(footer,可选,如 Breaking Changes 等)
|
|
26
|
+
|
|
27
|
+
## Type 类型说明
|
|
28
|
+
- feat: 新功能
|
|
29
|
+
- fix: 修复 bug
|
|
30
|
+
- docs: 文档变更
|
|
31
|
+
- style: 代码格式(不影响代码运行的变动)
|
|
32
|
+
- refactor: 重构(既不是新增功能,也不是修复 bug)
|
|
33
|
+
- perf: 性能优化
|
|
34
|
+
- test: 增加测试
|
|
35
|
+
- chore: 构建过程或辅助工具的变动
|
|
36
|
+
- revert: 回滚
|
|
37
|
+
- ci: CI 配置文件和脚本的变动
|
|
38
|
+
- build: 影响构建系统或外部依赖的变更
|
|
39
|
+
|
|
40
|
+
## 要求
|
|
41
|
+
- 准确描述变更内容,不要臆测
|
|
42
|
+
- 使用动词原形开头(如:添加、修复、更新、优化)
|
|
43
|
+
- 简洁明了,避免冗余"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def build_user_prompt(changes: StagedChanges, verbose: bool = False) -> str:
|
|
47
|
+
"""构建用户提示词
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
changes: 暂存区变更信息
|
|
51
|
+
verbose: 是否使用详细模式
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
用户提示词字符串
|
|
55
|
+
"""
|
|
56
|
+
if not changes.files:
|
|
57
|
+
return "暂存区没有任何变更。"
|
|
58
|
+
|
|
59
|
+
# 构建文件变更摘要
|
|
60
|
+
file_summary = []
|
|
61
|
+
for file in changes.files:
|
|
62
|
+
status_text = get_status_symbol(file.status)
|
|
63
|
+
if file.status in ("R", "C") and file.old_path:
|
|
64
|
+
file_summary.append(f"- [{status_text}] {file.old_path} -> {file.new_path}")
|
|
65
|
+
else:
|
|
66
|
+
file_summary.append(f"- [{status_text}] {file.new_path}")
|
|
67
|
+
|
|
68
|
+
# 构建提示词
|
|
69
|
+
mode = "详细" if verbose else "精简"
|
|
70
|
+
prompt = f"""请根据以下暂存区变更生成{mode}的 commit message。
|
|
71
|
+
|
|
72
|
+
## 变更文件
|
|
73
|
+
{chr(10).join(file_summary)}
|
|
74
|
+
|
|
75
|
+
## 详细变更内容(diff)
|
|
76
|
+
```
|
|
77
|
+
{changes.diff_content}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
请生成{mode}的 commit message:"""
|
|
81
|
+
|
|
82
|
+
return prompt
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_system_prompt() -> str:
|
|
86
|
+
"""获取系统提示词"""
|
|
87
|
+
return SYSTEM_PROMPT
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: git-gcm
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Git Commit Message 自动生成工具
|
|
5
|
+
Author-email: Luca <yangshaoxiong5545@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/falconluca/gcm
|
|
8
|
+
Project-URL: Repository, https://github.com/falconluca/gcm
|
|
9
|
+
Keywords: git,commit,message,ai,llm,openai,gcm
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: openai>=1.0.0
|
|
27
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# GCM
|
|
31
|
+
|
|
32
|
+
基于 AI 的 Git Commit Message 自动生成工具,支持所有 OpenAI 协议兼容的大模型服务。
|
|
33
|
+
|
|
34
|
+
## 安装
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
make build
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 配置
|
|
41
|
+
|
|
42
|
+
设置环境变量:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
export GCM_API_KEY="your-api-key"
|
|
46
|
+
export GCM_API_URL="https://api.openai.com/v1"
|
|
47
|
+
export GCM_MODEL="gpt-4o-mini"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 使用
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
git add .
|
|
54
|
+
gcm # 生成精简 commit message
|
|
55
|
+
gcm -v # 生成详细 commit message
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 示例
|
|
59
|
+
|
|
60
|
+
**精简模式:**
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
feat(auth): 添加用户登录功能
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**详细模式 (`-v`):**
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
feat(auth): 添加用户登录功能
|
|
70
|
+
|
|
71
|
+
- 实现 JWT token 认证
|
|
72
|
+
- 添加登录表单验证
|
|
73
|
+
- 集成第三方 OAuth 登录
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Git 集成
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git commit -m "$(gcm)"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
MIT License
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
gcm/__init__.py
|
|
5
|
+
gcm/cli.py
|
|
6
|
+
gcm/git_utils.py
|
|
7
|
+
gcm/llm_client.py
|
|
8
|
+
gcm/prompts.py
|
|
9
|
+
git_gcm.egg-info/PKG-INFO
|
|
10
|
+
git_gcm.egg-info/SOURCES.txt
|
|
11
|
+
git_gcm.egg-info/dependency_links.txt
|
|
12
|
+
git_gcm.egg-info/entry_points.txt
|
|
13
|
+
git_gcm.egg-info/requires.txt
|
|
14
|
+
git_gcm.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gcm
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "git-gcm"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Git Commit Message 自动生成工具"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
authors = [{ name = "Luca", email = "yangshaoxiong5545@gmail.com" }]
|
|
13
|
+
keywords = ["git", "commit", "message", "ai", "llm", "openai", "gcm"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Topic :: Software Development :: Version Control :: Git",
|
|
27
|
+
"Topic :: Utilities",
|
|
28
|
+
]
|
|
29
|
+
dependencies = ["openai>=1.0.0", "python-dotenv>=1.0.0"]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
gcm = "gcm.cli:main"
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/falconluca/gcm"
|
|
36
|
+
Repository = "https://github.com/falconluca/gcm"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
where = ["."]
|
|
40
|
+
include = ["gcm*"]
|
git_gcm-1.0.0/setup.cfg
ADDED