Skip to content

Commit da580ab

Browse files
authored
fix: restore -bm flag functionality for top-level usage (#28)
- Add CommitArgs struct to top-level Args to handle commit flags without subcommand - Update main.rs to use top-level commit args when no subcommand is specified - Add comprehensive tests for combined short flag parsing (-bm) - Update version to 0.7.1 in Cargo.toml, Cargo.lock, and README files - Fix documentation to show both separate and combined flag usage examples Signed-off-by: jinlong <jinlong@tencent.com>
1 parent b792f42 commit da580ab

File tree

6 files changed

+180
-8
lines changed

6 files changed

+180
-8
lines changed

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.7.0"
3+
version = "0.7.1"
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 v0.7.0
13+
cargo install --git https://github.com/fslongjin/fastcommit --tag v0.7.1
1414
```
1515

1616

@@ -78,7 +78,11 @@ NOTE: All common config can be configured via `~/.fastcommit/config.toml`
7878
6. Generate both branch name and commit message:
7979

8080
```bash
81+
# Using separate flags
8182
fastcommit -b -m
83+
84+
# Or using combined short flags
85+
fastcommit -bm
8286
```
8387

8488
7. Generate commit message for a specific diff range:

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 v0.7.0
11+
cargo install --git https://github.com/fslongjin/fastcommit --tag v0.7.1
1212
```
1313

1414
## 使用
@@ -75,7 +75,11 @@ NOTE: All common config can be configured via `~/.fastcommit/config.toml`
7575
6. 同时生成分支名和提交信息:
7676

7777
```bash
78+
# 使用分开的选项
7879
fastcommit -b -m
80+
81+
# 或使用组合短选项
82+
fastcommit -bm
7983
```
8084

8185
7. 为特定的差异范围生成提交信息:

src/cli.rs

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@ use crate::config::{CommitLanguage, Verbosity};
88
about = concat!(
99
"AI-based command line tool to quickly generate standardized commit messages.\n\n",
1010
"Version: ", env!("CARGO_PKG_VERSION")
11-
)
11+
),
12+
subcommand_required = false,
13+
arg_required_else_help = false
1214
)]
1315
pub struct Args {
16+
#[clap(flatten)]
17+
pub commit_args: CommitArgs,
18+
1419
#[clap(subcommand)]
1520
pub command: Option<Commands>,
1621
}
@@ -126,3 +131,156 @@ pub struct PrArgs {
126131
#[clap(flatten)]
127132
pub common: CommonArgs,
128133
}
134+
135+
#[cfg(test)]
136+
mod tests {
137+
use super::*;
138+
139+
/// Helper function to parse args from iterator
140+
fn parse_args<I, T>(iter: I) -> Result<Args, clap::Error>
141+
where
142+
I: IntoIterator<Item = T>,
143+
T: Into<std::ffi::OsString> + Clone,
144+
{
145+
Args::try_parse_from(iter)
146+
}
147+
148+
#[test]
149+
fn test_top_level_short_options_combined() {
150+
// Test: fastcommit -bm (generate both branch and message)
151+
let args = parse_args(["fastcommit", "-bm"]).unwrap();
152+
assert!(args.command.is_none(), "No subcommand should be set");
153+
assert!(
154+
args.commit_args.generate_branch,
155+
"-b should set generate_branch"
156+
);
157+
assert!(
158+
args.commit_args.generate_message,
159+
"-m should set generate_message"
160+
);
161+
}
162+
163+
#[test]
164+
fn test_top_level_short_options_separate() {
165+
// Test: fastcommit -b -m (generate both branch and message)
166+
let args = parse_args(["fastcommit", "-b", "-m"]).unwrap();
167+
assert!(args.command.is_none());
168+
assert!(args.commit_args.generate_branch);
169+
assert!(args.commit_args.generate_message);
170+
}
171+
172+
#[test]
173+
fn test_top_level_branch_only() {
174+
// Test: fastcommit -b (generate branch only)
175+
let args = parse_args(["fastcommit", "-b"]).unwrap();
176+
assert!(args.command.is_none());
177+
assert!(args.commit_args.generate_branch);
178+
assert!(!args.commit_args.generate_message);
179+
}
180+
181+
#[test]
182+
fn test_top_level_message_only() {
183+
// Test: fastcommit -m (generate message only - default behavior)
184+
let args = parse_args(["fastcommit", "-m"]).unwrap();
185+
assert!(args.command.is_none());
186+
assert!(!args.commit_args.generate_branch);
187+
assert!(args.commit_args.generate_message);
188+
}
189+
190+
#[test]
191+
fn test_no_args_uses_default() {
192+
// Test: fastcommit (no args - default commit behavior)
193+
let args = parse_args(["fastcommit"]).unwrap();
194+
assert!(args.command.is_none());
195+
assert!(!args.commit_args.generate_branch);
196+
assert!(!args.commit_args.generate_message);
197+
}
198+
199+
#[test]
200+
fn test_commit_subcommand_with_options() {
201+
// Test: fastcommit commit -bm (using subcommand explicitly)
202+
let args = parse_args(["fastcommit", "commit", "-bm"]).unwrap();
203+
assert!(args.command.is_some(), "Subcommand should be set");
204+
if let Some(Commands::Commit(commit_args)) = args.command {
205+
assert!(commit_args.generate_branch);
206+
assert!(commit_args.generate_message);
207+
} else {
208+
panic!("Expected Commit subcommand");
209+
}
210+
}
211+
212+
#[test]
213+
fn test_pr_subcommand() {
214+
// Test: fastcommit pr 123
215+
let args = parse_args(["fastcommit", "pr", "123"]).unwrap();
216+
if let Some(Commands::Pr(pr_args)) = args.command {
217+
assert_eq!(pr_args.pr_number, Some(123));
218+
} else {
219+
panic!("Expected Pr subcommand");
220+
}
221+
}
222+
223+
#[test]
224+
fn test_top_level_range_option() {
225+
// Test: fastcommit -r HEAD~1
226+
let args = parse_args(["fastcommit", "-r", "HEAD~1"]).unwrap();
227+
assert!(args.command.is_none());
228+
assert_eq!(args.commit_args.range, Some("HEAD~1".to_string()));
229+
}
230+
231+
#[test]
232+
fn test_top_level_with_common_args() {
233+
// Test: fastcommit -bm --no-wrap --language en
234+
let args = parse_args(["fastcommit", "-bm", "--no-wrap", "--language", "en"]).unwrap();
235+
assert!(args.command.is_none());
236+
assert!(args.commit_args.generate_branch);
237+
assert!(args.commit_args.generate_message);
238+
assert!(args.commit_args.common.no_wrap);
239+
assert_eq!(
240+
args.commit_args.common.language,
241+
Some(CommitLanguage::English)
242+
);
243+
}
244+
245+
#[test]
246+
fn test_commit_subcommand_with_range() {
247+
// Test: fastcommit commit -r HEAD~1 -b
248+
let args = parse_args(["fastcommit", "commit", "-r", "HEAD~1", "-b"]).unwrap();
249+
if let Some(Commands::Commit(commit_args)) = args.command {
250+
assert!(commit_args.generate_branch);
251+
assert_eq!(commit_args.range, Some("HEAD~1".to_string()));
252+
} else {
253+
panic!("Expected Commit subcommand");
254+
}
255+
}
256+
257+
#[test]
258+
fn test_diff_file_option() {
259+
// Test: fastcommit --diff-file /path/to/diff
260+
let args = parse_args(["fastcommit", "--diff-file", "/path/to/diff"]).unwrap();
261+
assert!(args.command.is_none());
262+
assert_eq!(
263+
args.commit_args.diff_file,
264+
Some("/path/to/diff".to_string())
265+
);
266+
}
267+
268+
#[test]
269+
fn test_auto_commit_option() {
270+
// Test: fastcommit -c (auto commit after generating)
271+
let args = parse_args(["fastcommit", "-c"]).unwrap();
272+
assert!(args.command.is_none());
273+
assert!(args.commit_args.common.commit);
274+
}
275+
276+
#[test]
277+
fn test_combined_all_options() {
278+
// Test: fastcommit -bmc --no-sanitize (all flags combined)
279+
let args = parse_args(["fastcommit", "-bmc", "--no-sanitize"]).unwrap();
280+
assert!(args.command.is_none());
281+
assert!(args.commit_args.generate_branch);
282+
assert!(args.commit_args.generate_message);
283+
assert!(args.commit_args.common.commit);
284+
assert!(args.commit_args.common.no_sanitize);
285+
}
286+
}

src/main.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,17 @@ async fn main() -> anyhow::Result<()> {
2525
let mut config = config::load_config().await?;
2626

2727
// Handle subcommands
28-
match args.command.unwrap_or_default() {
29-
cli::Commands::Commit(commit_args) => {
28+
match args.command {
29+
Some(cli::Commands::Commit(commit_args)) => {
3030
handle_commit_command(&commit_args, &mut config, &spinner).await
3131
}
32-
cli::Commands::Pr(pr_args) => handle_pr_command(&pr_args, &mut config, &spinner).await,
32+
Some(cli::Commands::Pr(pr_args)) => {
33+
handle_pr_command(&pr_args, &mut config, &spinner).await
34+
}
35+
None => {
36+
// No subcommand specified, use top-level commit args
37+
handle_commit_command(&args.commit_args, &mut config, &spinner).await
38+
}
3339
}
3440
}
3541

0 commit comments

Comments
 (0)