Skip to content

Commit e9a3f74

Browse files
authored
feat: 添加分支名生成功能并升级版本至0.1.3 (#5)
- 新增分支名生成功能(--gb/--generate-branch选项) - 添加分支名前缀配置支持 - 更新README文档说明新功能 - 版本升级至0.1.3 Signed-off-by: jinlong <fslongjin@vip.qq.com>
1 parent 09f211a commit e9a3f74

9 files changed

Lines changed: 174 additions & 19 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fastcommit"
3-
version = "0.1.2"
3+
version = "0.1.3"
44
description = "AI-based command line tool to quickly generate standardized commit messages."
55
edition = "2021"
66
authors = ["longjin <fslongjin@vip.qq.com>"]

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ You can install `fastcommit` using the following method:
1010

1111
```bash
1212
# Install using cargo
13-
cargo install --git https://github.com/fslongjin/fastcommit --tag 0.1.2
13+
cargo install --git https://github.com/fslongjin/fastcommit --tag 0.1.3
1414
```
1515

1616
## Usage
@@ -24,9 +24,13 @@ fastcommit
2424

2525
### Options
2626

27+
NOTE: All common config can be configured via `~/.fastcommit/config.toml`
28+
2729
- `-d, --diff-file <DIFF_FILE>`: Specify the path to the file containing the differences.
2830
- `--conventional <CONVENTIONAL>`: Enable or disable conventional commit style analysis. Acceptable values are `true` or `false`.
2931
- `-l, --language <LANGUAGE>`: Specify the language for the commit message. Acceptable values are `en` (English) or `zh` (Chinese).
32+
- `-gb, --generate-branch`: Generate branch name.
33+
- `--branch-prefix`: prefix of the generated branch name
3034
- `-v, --verbosity <VERBOSITY>`: Set the detail level of the commit message. Acceptable values are `verbose` (detailed), `normal`, or `quiet` (concise). The default is `quiet`.
3135
- `-h, --help`: Print help information.
3236
- `-V, --version`: Print version information.

README_CN.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
```bash
1010
# 使用 cargo 安装
11-
cargo install --git https://github.com/fslongjin/fastcommit --tag 0.1.2
11+
cargo install --git https://github.com/fslongjin/fastcommit --tag 0.1.3
1212
```
1313

1414
## 使用
@@ -22,9 +22,13 @@ fastcommit
2222

2323
### 选项
2424

25+
NOTE: All common config can be configured via `~/.fastcommit/config.toml`
26+
2527
- `-d, --diff-file <DIFF_FILE>`: 指定包含差异的文件路径。
2628
- `--conventional <CONVENTIONAL>`: 启用或禁用规范提交风格分析。可选值为 `true``false`
2729
- `-l, --language <LANGUAGE>`: 指定提交信息的语言。可选值为 `en`(英文)或 `zh`(中文)。
30+
- `-gb, --generate-branch`: 模式:生成分支名
31+
- `--branch-prefix`: 生成的分支名的前缀
2832
- `-v, --verbosity <VERBOSITY>`: 设置提交信息的详细级别。可选值为 `verbose`(详细)、`normal`(正常)或 `quiet`(简洁)。 默认为 `quiet`
2933
- `-h, --help`: 打印帮助信息。
3034
- `-V, --version`: 打印版本信息。

src/cli.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,14 @@ pub struct Args {
1616

1717
#[clap(short, long, help = "Set the verbosity level")]
1818
pub verbosity: Option<Verbosity>,
19+
20+
#[clap(
21+
long = "generate-branch",
22+
alias = "gb",
23+
help = "Generate a branch name based on changes (optionally with prefix)"
24+
)]
25+
pub generate_branch: bool,
26+
27+
#[clap(long, help = "Override branch prefix (default from config)")]
28+
pub branch_prefix: Option<String>,
1929
}

src/config.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::constants::{DEFAULT_MAX_TOKENS, DEFAULT_OPENAI_API_BASE, DEFAULT_OPEN
55

66
#[derive(Debug, Serialize, Deserialize)]
77
pub struct Config {
8-
pub api_base: Option<String>,
8+
api_base: Option<String>,
99
pub api_key: String,
1010
pub model: Option<String>,
1111
/// The maximum number of tokens to generate in the commit message.
@@ -14,6 +14,25 @@ pub struct Config {
1414
pub conventional: bool,
1515
pub language: CommitLanguage,
1616
pub verbosity: Verbosity,
17+
/// Prefix for generated branch names (e.g. username in monorepo)
18+
pub branch_prefix: Option<String>,
19+
}
20+
21+
impl Config {
22+
pub fn api_base(&self) -> String {
23+
let api_base = self
24+
.api_base
25+
.as_deref()
26+
.unwrap_or("https://api.openai.com/v1");
27+
28+
let api_base = if api_base.ends_with("/") {
29+
api_base.to_owned()
30+
} else {
31+
format!("{}/", api_base)
32+
};
33+
34+
api_base
35+
}
1736
}
1837

1938
/// Commit message verbosity level.
@@ -84,6 +103,7 @@ impl Default for Config {
84103
conventional: true,
85104
language: CommitLanguage::default(),
86105
verbosity: Verbosity::default(),
106+
branch_prefix: None,
87107
}
88108
}
89109
}

src/constants.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,42 @@ lazy_static! {
9191
}
9292

9393
pub const DEFAULT_MAX_TOKENS: u32 = 2048;
94+
95+
pub const BRANCH_NAME_PROMPT: &str = r#"
96+
# 角色
97+
作为代码版本控制专家,请根据以下变更生成一个简洁、描述性的分支名。
98+
99+
# 要求:
100+
1. 使用英文小写字母和连字符
101+
2. 长度不超过40个字符
102+
3. 能准确反映变更内容
103+
4. 如果有前缀,请在前面加上前缀
104+
5. 直接返回分支名,不要包含其他内容或解释
105+
6. branch name内容要使用<aicommit></aicommit>标签包裹
106+
107+
# 示例:
108+
109+
branch name内容要使用<aicommit>标签包裹,例如:
110+
111+
## 示例1:
112+
113+
<aicommit>
114+
fix-login-issue
115+
</aicommit>
116+
117+
## 示例2:
118+
119+
<aicommit>
120+
feat-add-user-auth
121+
</aicommit>
122+
123+
## 示例3:
124+
125+
<aicommit>
126+
username-refactor-payment-module
127+
</aicommit>
128+
129+
变更内容:
130+
131+
{{diff}}
132+
"#;

src/generate.rs

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,17 @@ use std::process::Command;
44

55
use crate::cli;
66
use crate::config::{self, Config};
7+
8+
use crate::constants::BRANCH_NAME_PROMPT;
79
use crate::constants::{
810
DEFAULT_MAX_TOKENS, DEFAULT_OPENAI_MODEL, DEFAULT_PROMPT_TEMPLATE, STOP_WORDS,
911
};
1012
use crate::template_engine::{render_template, TemplateContext};
1113

1214
async fn generate_commit_message(diff: &str, config: &config::Config) -> anyhow::Result<String> {
1315
let auth = Auth::new(config.api_key.as_str());
14-
let api_base = config
15-
.api_base
16-
.as_deref()
17-
.unwrap_or("https://api.openai.com/v1");
1816

19-
let api_base_url = if api_base.ends_with("/") {
20-
api_base.to_owned()
21-
} else {
22-
format!("{}/", api_base)
23-
};
24-
let openai = OpenAI::new(auth, &api_base_url);
17+
let openai = OpenAI::new(auth, &config.api_base());
2518

2619
let template_ctx =
2720
TemplateContext::new(config.conventional, config.language, config.verbosity, diff);
@@ -64,7 +57,7 @@ async fn generate_commit_message(diff: &str, config: &config::Config) -> anyhow:
6457
.ok_or(anyhow::anyhow!("No message in response"))?
6558
.content;
6659
// Extract content between <aicommit> tags
67-
let commit_message = extract_commit_message(msg)?;
60+
let commit_message = extract_aicommit_message(msg)?;
6861
Ok(commit_message)
6962
}
7063

@@ -91,7 +84,7 @@ fn delete_thinking_contents(orig: &str) -> String {
9184
s
9285
}
9386

94-
fn extract_commit_message(response: &str) -> anyhow::Result<String> {
87+
fn extract_aicommit_message(response: &str) -> anyhow::Result<String> {
9588
let start_tag = "<aicommit>";
9689
let end_tag = "</aicommit>";
9790

@@ -133,3 +126,83 @@ pub async fn generate(args: &cli::Args, config: &Config) -> anyhow::Result<Strin
133126
let message = generate_commit_message(&diff, config).await?;
134127
Ok(message)
135128
}
129+
130+
async fn generate_branch_name_with_ai(
131+
diff: &str,
132+
prefix: Option<&str>,
133+
config: &Config,
134+
) -> anyhow::Result<String> {
135+
let auth = Auth::new(config.api_key.as_str());
136+
137+
let openai = OpenAI::new(auth, &config.api_base());
138+
139+
let prompt = BRANCH_NAME_PROMPT.replace("{{diff}}", diff);
140+
let messages = vec![
141+
Message {
142+
role: Role::System,
143+
content: "你是一个代码版本控制专家,擅长创建描述性的分支名。".to_string(),
144+
},
145+
Message {
146+
role: Role::User,
147+
content: prompt,
148+
},
149+
];
150+
151+
let chat = ChatBody {
152+
model: config
153+
.model
154+
.as_deref()
155+
.unwrap_or(DEFAULT_OPENAI_MODEL)
156+
.to_owned(),
157+
messages,
158+
temperature: Some(0.2f32),
159+
top_p: None,
160+
n: None,
161+
stream: Some(false),
162+
stop: Some(STOP_WORDS.to_owned()),
163+
max_tokens: Some(40),
164+
presence_penalty: None,
165+
frequency_penalty: None,
166+
logit_bias: None,
167+
user: None,
168+
};
169+
170+
let response = openai
171+
.chat_completion_create(&chat)
172+
.map_err(|e| anyhow::anyhow!("Failed to create chat completion: {}", e))?;
173+
let msg = response
174+
.choices
175+
.first()
176+
.ok_or(anyhow::anyhow!("No choices in response"))?
177+
.message
178+
.as_ref()
179+
.ok_or(anyhow::anyhow!("No message in response"))?
180+
.content
181+
.trim()
182+
.to_string();
183+
184+
let branch_name = extract_aicommit_message(&msg)?;
185+
186+
// Clean up the branch name
187+
let branch_name = if let Some(prefix) = prefix {
188+
format!("{}{}", prefix.trim(), branch_name.trim())
189+
} else {
190+
branch_name.trim().to_string()
191+
};
192+
193+
if branch_name.is_empty() {
194+
return Err(anyhow::anyhow!("Failed to generate valid branch name"));
195+
}
196+
197+
Ok(branch_name)
198+
}
199+
200+
pub async fn generate_branch(args: &cli::Args, config: &Config) -> anyhow::Result<String> {
201+
let diff = get_diff(args.diff_file.as_deref())?;
202+
let prefix = args
203+
.branch_prefix
204+
.as_deref()
205+
.or(config.branch_prefix.as_deref());
206+
let branch_name = generate_branch_name_with_ai(&diff, prefix, config).await?;
207+
Ok(branch_name)
208+
}

src/main.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ async fn main() -> anyhow::Result<()> {
2626
config.verbosity = v;
2727
}
2828

29-
let msg = generate::generate(&args, &config).await?;
30-
println!("{}", msg);
29+
if args.generate_branch {
30+
let branch_name = generate::generate_branch(&args, &config).await?;
31+
println!("Generated branch name: {}", branch_name);
32+
} else {
33+
let msg = generate::generate(&args, &config).await?;
34+
println!("{}", msg);
35+
}
3136
Ok(())
3237
}

0 commit comments

Comments
 (0)