From f5c78bf1c4b19c1ec2a4f53cce5c45333ed9c038 Mon Sep 17 00:00:00 2001 From: jinlong Date: Tue, 24 Feb 2026 17:28:47 +0800 Subject: [PATCH] fix: restore -bm flag functionality for top-level usage - 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 --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 6 +- README_CN.md | 6 +- src/cli.rs | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 12 +++- 6 files changed, 180 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc784c0..4bc07e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -359,7 +359,7 @@ dependencies = [ [[package]] name = "fastcommit" -version = "0.7.0" +version = "0.7.1" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 404999a..a7cf8c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fastcommit" -version = "0.7.0" +version = "0.7.1" description = "AI-based command line tool to quickly generate standardized commit messages." edition = "2021" authors = ["longjin "] diff --git a/README.md b/README.md index 5df65a0..585c453 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ You can install `fastcommit` using the following method: ```bash # Install using cargo -cargo install --git https://github.com/fslongjin/fastcommit --tag v0.7.0 +cargo install --git https://github.com/fslongjin/fastcommit --tag v0.7.1 ``` @@ -78,7 +78,11 @@ NOTE: All common config can be configured via `~/.fastcommit/config.toml` 6. Generate both branch name and commit message: ```bash + # Using separate flags fastcommit -b -m + + # Or using combined short flags + fastcommit -bm ``` 7. Generate commit message for a specific diff range: diff --git a/README_CN.md b/README_CN.md index a184aca..701ab5f 100644 --- a/README_CN.md +++ b/README_CN.md @@ -8,7 +8,7 @@ ```bash # 使用 cargo 安装 -cargo install --git https://github.com/fslongjin/fastcommit --tag v0.7.0 +cargo install --git https://github.com/fslongjin/fastcommit --tag v0.7.1 ``` ## 使用 @@ -75,7 +75,11 @@ NOTE: All common config can be configured via `~/.fastcommit/config.toml` 6. 同时生成分支名和提交信息: ```bash + # 使用分开的选项 fastcommit -b -m + + # 或使用组合短选项 + fastcommit -bm ``` 7. 为特定的差异范围生成提交信息: diff --git a/src/cli.rs b/src/cli.rs index 19059ca..eb3cead 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -8,9 +8,14 @@ use crate::config::{CommitLanguage, Verbosity}; about = concat!( "AI-based command line tool to quickly generate standardized commit messages.\n\n", "Version: ", env!("CARGO_PKG_VERSION") - ) + ), + subcommand_required = false, + arg_required_else_help = false )] pub struct Args { + #[clap(flatten)] + pub commit_args: CommitArgs, + #[clap(subcommand)] pub command: Option, } @@ -126,3 +131,156 @@ pub struct PrArgs { #[clap(flatten)] pub common: CommonArgs, } + +#[cfg(test)] +mod tests { + use super::*; + + /// Helper function to parse args from iterator + fn parse_args(iter: I) -> Result + where + I: IntoIterator, + T: Into + Clone, + { + Args::try_parse_from(iter) + } + + #[test] + fn test_top_level_short_options_combined() { + // Test: fastcommit -bm (generate both branch and message) + let args = parse_args(["fastcommit", "-bm"]).unwrap(); + assert!(args.command.is_none(), "No subcommand should be set"); + assert!( + args.commit_args.generate_branch, + "-b should set generate_branch" + ); + assert!( + args.commit_args.generate_message, + "-m should set generate_message" + ); + } + + #[test] + fn test_top_level_short_options_separate() { + // Test: fastcommit -b -m (generate both branch and message) + let args = parse_args(["fastcommit", "-b", "-m"]).unwrap(); + assert!(args.command.is_none()); + assert!(args.commit_args.generate_branch); + assert!(args.commit_args.generate_message); + } + + #[test] + fn test_top_level_branch_only() { + // Test: fastcommit -b (generate branch only) + let args = parse_args(["fastcommit", "-b"]).unwrap(); + assert!(args.command.is_none()); + assert!(args.commit_args.generate_branch); + assert!(!args.commit_args.generate_message); + } + + #[test] + fn test_top_level_message_only() { + // Test: fastcommit -m (generate message only - default behavior) + let args = parse_args(["fastcommit", "-m"]).unwrap(); + assert!(args.command.is_none()); + assert!(!args.commit_args.generate_branch); + assert!(args.commit_args.generate_message); + } + + #[test] + fn test_no_args_uses_default() { + // Test: fastcommit (no args - default commit behavior) + let args = parse_args(["fastcommit"]).unwrap(); + assert!(args.command.is_none()); + assert!(!args.commit_args.generate_branch); + assert!(!args.commit_args.generate_message); + } + + #[test] + fn test_commit_subcommand_with_options() { + // Test: fastcommit commit -bm (using subcommand explicitly) + let args = parse_args(["fastcommit", "commit", "-bm"]).unwrap(); + assert!(args.command.is_some(), "Subcommand should be set"); + if let Some(Commands::Commit(commit_args)) = args.command { + assert!(commit_args.generate_branch); + assert!(commit_args.generate_message); + } else { + panic!("Expected Commit subcommand"); + } + } + + #[test] + fn test_pr_subcommand() { + // Test: fastcommit pr 123 + let args = parse_args(["fastcommit", "pr", "123"]).unwrap(); + if let Some(Commands::Pr(pr_args)) = args.command { + assert_eq!(pr_args.pr_number, Some(123)); + } else { + panic!("Expected Pr subcommand"); + } + } + + #[test] + fn test_top_level_range_option() { + // Test: fastcommit -r HEAD~1 + let args = parse_args(["fastcommit", "-r", "HEAD~1"]).unwrap(); + assert!(args.command.is_none()); + assert_eq!(args.commit_args.range, Some("HEAD~1".to_string())); + } + + #[test] + fn test_top_level_with_common_args() { + // Test: fastcommit -bm --no-wrap --language en + let args = parse_args(["fastcommit", "-bm", "--no-wrap", "--language", "en"]).unwrap(); + assert!(args.command.is_none()); + assert!(args.commit_args.generate_branch); + assert!(args.commit_args.generate_message); + assert!(args.commit_args.common.no_wrap); + assert_eq!( + args.commit_args.common.language, + Some(CommitLanguage::English) + ); + } + + #[test] + fn test_commit_subcommand_with_range() { + // Test: fastcommit commit -r HEAD~1 -b + let args = parse_args(["fastcommit", "commit", "-r", "HEAD~1", "-b"]).unwrap(); + if let Some(Commands::Commit(commit_args)) = args.command { + assert!(commit_args.generate_branch); + assert_eq!(commit_args.range, Some("HEAD~1".to_string())); + } else { + panic!("Expected Commit subcommand"); + } + } + + #[test] + fn test_diff_file_option() { + // Test: fastcommit --diff-file /path/to/diff + let args = parse_args(["fastcommit", "--diff-file", "/path/to/diff"]).unwrap(); + assert!(args.command.is_none()); + assert_eq!( + args.commit_args.diff_file, + Some("/path/to/diff".to_string()) + ); + } + + #[test] + fn test_auto_commit_option() { + // Test: fastcommit -c (auto commit after generating) + let args = parse_args(["fastcommit", "-c"]).unwrap(); + assert!(args.command.is_none()); + assert!(args.commit_args.common.commit); + } + + #[test] + fn test_combined_all_options() { + // Test: fastcommit -bmc --no-sanitize (all flags combined) + let args = parse_args(["fastcommit", "-bmc", "--no-sanitize"]).unwrap(); + assert!(args.command.is_none()); + assert!(args.commit_args.generate_branch); + assert!(args.commit_args.generate_message); + assert!(args.commit_args.common.commit); + assert!(args.commit_args.common.no_sanitize); + } +} diff --git a/src/main.rs b/src/main.rs index eb379e2..53fa1f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,11 +25,17 @@ async fn main() -> anyhow::Result<()> { let mut config = config::load_config().await?; // Handle subcommands - match args.command.unwrap_or_default() { - cli::Commands::Commit(commit_args) => { + match args.command { + Some(cli::Commands::Commit(commit_args)) => { handle_commit_command(&commit_args, &mut config, &spinner).await } - cli::Commands::Pr(pr_args) => handle_pr_command(&pr_args, &mut config, &spinner).await, + Some(cli::Commands::Pr(pr_args)) => { + handle_pr_command(&pr_args, &mut config, &spinner).await + } + None => { + // No subcommand specified, use top-level commit args + handle_commit_command(&args.commit_args, &mut config, &spinner).await + } } }