From bc06bd7a4fd917ae61f65e66a9f5b26a6388a502 Mon Sep 17 00:00:00 2001 From: staryxchen Date: Tue, 24 Feb 2026 11:29:50 +0800 Subject: [PATCH 1/5] chore: ignore .codebuddy directory - add .codebuddy/ to .gitignore Signed-off-by: staryxchen --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ea8c4bf..ebb58f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.codebuddy/ \ No newline at end of file From 85d5ddc41be228e3dc21f936495a64f970739c24 Mon Sep 17 00:00:00 2001 From: staryxchen Date: Tue, 24 Feb 2026 11:29:13 +0800 Subject: [PATCH 2/5] chore: add pre-commit configuration for Rust - Add pre-commit hooks for rustfmt, clippy, and cargo-check - Update documentation Signed-off-by: staryxchen --- .pre-commit-config.yaml | 9 +++++++++ README.md | 23 +++++++++++++++++++++++ README_CN.md | 23 +++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..aaacdc3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +repos: + - repo: https://github.com/doublify/pre-commit-rust + rev: v1.0 + hooks: + - id: fmt + args: ["--all", "--", "--check"] + - id: clippy + args: ["--all-targets", "--all-features", "--", "-D", "warnings"] + - id: cargo-check diff --git a/README.md b/README.md index 9067db2..cb72ecf 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,29 @@ NOTE: All common config can be configured via `~/.fastcommit/config.toml` fastcommit -c --commit-args "-s" --commit-args "--no-verify" ``` +## Development + +### Pre-commit Hooks + +This project uses [pre-commit](https://pre-commit.com/) to run code quality checks before each commit. + +```bash +# Install pre-commit +pip install pre-commit + +# Install the git hooks +pre-commit install + +# (Optional) Run all hooks manually +pre-commit run --all-files +``` + +The following checks will run automatically on `git commit`: + +- **rustfmt** — Code formatting check +- **clippy** — Static analysis with warnings as errors +- **cargo-check** — Compilation check + ## GitHub PR Integration `fastcommit` can generate commit messages for GitHub Pull Requests, which is useful when merging PRs. diff --git a/README_CN.md b/README_CN.md index 0cb5076..cbf1a72 100644 --- a/README_CN.md +++ b/README_CN.md @@ -151,6 +151,29 @@ fastcommit pr 123 -l zh 更多详情请参阅 [GitHub PR 集成指南](docs/github-pr-integration.md)。 +## 开发 + +### Pre-commit Hooks + +本项目使用 [pre-commit](https://pre-commit.com/) 在每次提交前自动运行代码质量检查。 + +```bash +# 安装 pre-commit +pip install pre-commit + +# 安装 git hooks +pre-commit install + +# (可选)手动运行所有检查 +pre-commit run --all-files +``` + +以下检查会在 `git commit` 时自动执行: + +- **rustfmt** — 代码格式化检查 +- **clippy** — 静态分析,警告视为错误 +- **cargo-check** — 编译检查 + ## 贡献 欢迎贡献代码或提出建议!请先阅读 [贡献指南](CONTRIBUTING.md)。 From 6b8aa0bba0aafe732c7fb6cf73b6b784020ba376 Mon Sep 17 00:00:00 2001 From: staryxchen Date: Tue, 24 Feb 2026 11:36:55 +0800 Subject: [PATCH 3/5] refactor: modernize Rust syntax and simplify code structure - Use `format!` inline variables throughout the codebase. - Derive `Default` trait for enums instead of manual implementation. - Replace `loop { if let ... }` patterns with `while let ...`. - Simplify test configuration initialization using struct update syntax. - Update assertions from `assert_eq!(..., true)` to `assert!(...)`. Signed-off-by: staryxchen --- src/config.rs | 22 ++--- src/generate.rs | 15 +-- src/main.rs | 8 +- src/sanitizer.rs | 32 +++---- src/template_engine.rs | 2 +- src/text_wrapper/hybrid_wrapper.rs | 4 +- src/text_wrapper/mod.rs | 148 +++++++++++++++++------------ src/text_wrapper/text_wrapper.rs | 2 +- src/update_checker.rs | 56 +++++------ 9 files changed, 144 insertions(+), 145 deletions(-) diff --git a/src/config.rs b/src/config.rs index 116a055..543e165 100644 --- a/src/config.rs +++ b/src/config.rs @@ -102,7 +102,7 @@ impl Config { let api_base = if api_base.ends_with("/") { api_base.to_owned() } else { - format!("{}/", api_base) + format!("{api_base}/") }; api_base @@ -110,7 +110,7 @@ impl Config { } /// Commit message verbosity level. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Copy, clap::ValueEnum)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Copy, clap::ValueEnum, Default)] pub enum Verbosity { /// Detailed commit message. #[serde(rename = "verbose")] @@ -123,17 +123,12 @@ pub enum Verbosity { /// Quiet commit message. #[serde(rename = "quiet")] #[clap(name = "quiet")] + #[default] Quiet, } -impl Default for Verbosity { - fn default() -> Self { - Verbosity::Quiet - } -} - impl Verbosity { - pub fn to_template_level(&self) -> &'static str { + pub fn as_template_level(self) -> &'static str { match self { Verbosity::Verbose => "详细", Verbosity::Normal => "中等", @@ -142,22 +137,17 @@ impl Verbosity { } } -#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, clap::ValueEnum)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, clap::ValueEnum, Default)] pub enum CommitLanguage { #[clap(name = "en")] #[serde(rename = "en")] English, #[clap(name = "zh")] #[serde(rename = "zh")] + #[default] Chinese, } -impl Default for CommitLanguage { - fn default() -> Self { - CommitLanguage::Chinese - } -} - impl Display for CommitLanguage { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/src/generate.rs b/src/generate.rs index 2e95f2a..e343c0a 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -33,7 +33,7 @@ pub async fn generate_commit_message( if desc.trim().is_empty() { desc } else { - format!("commit message: {}", desc) + format!("commit message: {desc}") } }); @@ -91,22 +91,17 @@ fn delete_thinking_contents(orig: &str) -> String { let end_tag = ""; let start_idx = orig.find(start_tag).unwrap_or(orig.len()); - let end_idx = orig.find(end_tag).unwrap_or_else(|| 0); - let s = if start_idx < end_idx { + let end_idx = orig.find(end_tag).unwrap_or(0); + if start_idx < end_idx { let mut result = orig[..start_idx].to_string(); result.push_str(&orig[end_idx..]); log::debug!( - "Delete thinking contents, start_idx: {}, end_idx: {}: {:?} => {:?}", - start_idx, - end_idx, - orig, - result + "Delete thinking contents, start_idx: {start_idx}, end_idx: {end_idx}: {orig:?} => {result:?}" ); result } else { orig.to_string() - }; - s + } } fn extract_aicommit_message(response: &str) -> anyhow::Result { diff --git a/src/main.rs b/src/main.rs index 1fdda9b..eb379e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -155,12 +155,10 @@ fn print_wrapped_content(wrapper: &Option, content: &str, prefix: O } else { println!("{}", wrapper.wrap(content)); } + } else if let Some(p) = prefix { + println!("{p} {content}"); } else { - if let Some(p) = prefix { - println!("{} {}", p, content); - } else { - println!("{}", content); - } + println!("{content}"); } } diff --git a/src/sanitizer.rs b/src/sanitizer.rs index d18048f..d927745 100644 --- a/src/sanitizer.rs +++ b/src/sanitizer.rs @@ -76,31 +76,23 @@ pub fn sanitize( // Apply builtin patterns for (kind, re) in builtin.iter() { - loop { - if let Some(m) = re.find(&sanitized) { - counter += 1; - let placeholder = format!("[REDACTED:{}#{}]", kind, counter); - sanitized.replace_range(m.start()..m.end(), &placeholder); - redactions.push(Redaction::new(kind, placeholder)); - } else { - break; - } + while let Some(m) = re.find(&sanitized) { + counter += 1; + let placeholder = format!("[REDACTED:{kind}#{counter}]"); + sanitized.replace_range(m.start()..m.end(), &placeholder); + redactions.push(Redaction::new(kind, placeholder)); } } // Apply custom patterns – use their provided name (converted to static via leak for simplicity) for meta in custom_patterns { - loop { - if let Some(m) = meta.regex.find(&sanitized) { - counter += 1; - let placeholder = format!("[REDACTED:{}#{}]", meta.name, counter); - sanitized.replace_range(m.start()..m.end(), &placeholder); - // We leak the string to get a 'static str; acceptable given tiny count and CLI nature - let leaked: &'static str = Box::leak(meta.name.clone().into_boxed_str()); - redactions.push(Redaction::new(leaked, placeholder)); - } else { - break; - } + while let Some(m) = meta.regex.find(&sanitized) { + counter += 1; + let placeholder = format!("[REDACTED:{}#{counter}]", meta.name); + sanitized.replace_range(m.start()..m.end(), &placeholder); + // We leak the string to get a 'static str; acceptable given tiny count and CLI nature + let leaked: &'static str = Box::leak(meta.name.clone().into_boxed_str()); + redactions.push(Redaction::new(leaked, placeholder)); } } diff --git a/src/template_engine.rs b/src/template_engine.rs index ed10d41..8e35e72 100644 --- a/src/template_engine.rs +++ b/src/template_engine.rs @@ -41,7 +41,7 @@ pub fn render_template(template: &str, context: TemplateContext) -> anyhow::Resu ) .replace( PromptTemplateReplaceLabel::VerbosityLevel.get_label(), - context.verbosity.to_template_level(), + context.verbosity.as_template_level(), ) .replace( PromptTemplateReplaceLabel::Diff.get_label(), diff --git a/src/text_wrapper/hybrid_wrapper.rs b/src/text_wrapper/hybrid_wrapper.rs index ce1411d..08f105b 100644 --- a/src/text_wrapper/hybrid_wrapper.rs +++ b/src/text_wrapper/hybrid_wrapper.rs @@ -250,14 +250,14 @@ impl HybridWrapper { } TextSegment::CodeBlock(code) => { if config.handle_code_blocks { - format!("\n{}\n", code) + format!("\n{code}\n") } else { code.clone() } } TextSegment::Link(url, text) => { if config.preserve_links { - format!("[{}]({})", text, url) + format!("[{text}]({url})") } else { text.clone() } diff --git a/src/text_wrapper/mod.rs b/src/text_wrapper/mod.rs index aab8e38..1b53179 100644 --- a/src/text_wrapper/mod.rs +++ b/src/text_wrapper/mod.rs @@ -1,4 +1,5 @@ pub mod hybrid_wrapper; +#[allow(clippy::module_inception)] pub mod text_wrapper; pub mod types; @@ -12,8 +13,10 @@ mod tests { #[test] fn test_basic_wrapping() { - let mut config = WrapConfig::default(); - config.max_width = 20; + let config = WrapConfig { + max_width: 20, + ..Default::default() + }; let wrapper = TextWrapper::new(config); let text = "This is a long line of text that should be wrapped"; let result = wrapper.wrap(text); @@ -28,9 +31,11 @@ mod tests { #[test] fn test_long_word_handling() { - let mut config = WrapConfig::default(); - config.max_width = 10; - config.break_long_words = true; + let config = WrapConfig { + max_width: 10, + break_long_words: true, + ..Default::default() + }; let wrapper = TextWrapper::new(config); let text = "Thisisaverylongwordthatneedstobebroken"; let result = wrapper.wrap(text); @@ -41,9 +46,11 @@ mod tests { #[test] fn test_code_block_preservation() { - let mut config = WrapConfig::default(); - config.max_width = 20; - config.handle_code_blocks = true; + let config = WrapConfig { + max_width: 20, + handle_code_blocks: true, + ..Default::default() + }; let wrapper = TextWrapper::new(config); let text = "Here is some text ```code block``` and more text"; let result = wrapper.wrap(text); @@ -54,9 +61,11 @@ mod tests { #[test] fn test_code_block_disabled() { - let mut config = WrapConfig::default(); - config.max_width = 20; - config.handle_code_blocks = false; + let config = WrapConfig { + max_width: 20, + handle_code_blocks: false, + ..Default::default() + }; let wrapper = TextWrapper::new(config); let text = "Here is some text ```code block``` and more text"; let result = wrapper.wrap(text); @@ -67,9 +76,11 @@ mod tests { #[test] fn test_link_preservation() { - let mut config = WrapConfig::default(); - config.max_width = 30; - config.preserve_links = true; + let config = WrapConfig { + max_width: 30, + preserve_links: true, + ..Default::default() + }; let wrapper = TextWrapper::new(config); let text = "Check out this link: https://example.com/some/long/url for more info"; let result = wrapper.wrap(text); @@ -81,9 +92,11 @@ mod tests { #[test] fn test_link_disabled() { - let mut config = WrapConfig::default(); - config.max_width = 30; - config.preserve_links = false; + let config = WrapConfig { + max_width: 30, + preserve_links: false, + ..Default::default() + }; let wrapper = TextWrapper::new(config); let text = "Check out this link: https://example.com/some/long/url for more info"; let result = wrapper.wrap(text); @@ -94,9 +107,11 @@ mod tests { #[test] fn test_markdown_links() { - let mut config = WrapConfig::default(); - config.max_width = 30; - config.preserve_links = true; + let config = WrapConfig { + max_width: 30, + preserve_links: true, + ..Default::default() + }; let wrapper = TextWrapper::new(config); let text = "See [Example](https://example.com) for details"; let result = wrapper.wrap(text); @@ -106,8 +121,10 @@ mod tests { #[test] fn test_inline_code() { - let mut config = WrapConfig::default(); - config.max_width = 20; + let config = WrapConfig { + max_width: 20, + ..Default::default() + }; let wrapper = TextWrapper::new(config); let text = "Use the `command` to run it"; let result = wrapper.wrap(text); @@ -118,8 +135,10 @@ mod tests { #[test] fn test_inline_code_no_double_backticks() { // 测试修复:行内代码不应该重复添加反引号 - let mut config = WrapConfig::default(); - config.max_width = 80; + let config = WrapConfig { + max_width: 80, + ..Default::default() + }; let wrapper = TextWrapper::new(config); let text = "Change the default path from ``config.json`` to ``settings.json``"; let result = wrapper.wrap(text); @@ -135,8 +154,10 @@ mod tests { #[test] fn test_inline_code_wrapping() { // 测试行内代码的换行处理:应该作为整体,不应该被拆分 - let mut config = WrapConfig::default(); - config.max_width = 30; // 设置较小的宽度 + let config = WrapConfig { + max_width: 30, + ..Default::default() + }; let wrapper = TextWrapper::new(config); let text = "Change the default path from ``config.json`` to ``settings.json``"; let result = wrapper.wrap(text); @@ -150,18 +171,19 @@ mod tests { assert_eq!( backtick_count % 2, 0, - "行内代码的反引号应该成对出现: {}", - line + "行内代码的反引号应该成对出现: {line}" ); } } #[test] fn test_mixed_content() { - let mut config = WrapConfig::default(); - config.max_width = 25; - config.handle_code_blocks = true; - config.preserve_links = true; + let config = WrapConfig { + max_width: 25, + handle_code_blocks: true, + preserve_links: true, + ..Default::default() + }; let wrapper = TextWrapper::new(config); let text = "Here is a link: https://example.com and some ```code block``` with `inline code`"; @@ -174,11 +196,13 @@ mod tests { #[test] fn test_complex_scenario() { - let mut config = WrapConfig::default(); - config.max_width = 80; // 更合理的宽度 - config.handle_code_blocks = true; - config.preserve_links = true; - config.preserve_words = true; + let config = WrapConfig { + max_width: 80, + handle_code_blocks: true, + preserve_links: true, + preserve_words: true, + ..Default::default() + }; let wrapper = TextWrapper::new(config); let text = "This is a long commit message that contains https://example.com and some ```rust\nfn main() {\n println!(\"Hello\");\n}\n``` plus `inline_code` for reference."; let result = wrapper.wrap(text); @@ -199,7 +223,7 @@ mod tests { && !line.trim().starts_with("fn") && !line.trim().starts_with("println") { - assert!(line.width() <= 80, "Line '{}' exceeds width 80", line); + assert!(line.width() <= 80, "Line '{line}' exceeds width 80"); } } } @@ -207,9 +231,11 @@ mod tests { #[test] fn test_list_item_no_wrap_after_dash() { // 测试列表项在 `-` 后不换行 - let mut config = WrapConfig::default(); - config.max_width = 80; - config.preserve_paragraphs = true; + let config = WrapConfig { + max_width: 80, + preserve_paragraphs: true, + ..Default::default() + }; let wrapper = TextWrapper::new(config); // 测试用例:列表项以 `-` 开头,后续文本很长 @@ -227,9 +253,11 @@ mod tests { #[test] fn test_list_item_with_inline_code_as_whole() { // 测试列表项包含行内代码时作为整体处理 - let mut config = WrapConfig::default(); - config.max_width = 80; - config.preserve_paragraphs = true; + let config = WrapConfig { + max_width: 80, + preserve_paragraphs: true, + ..Default::default() + }; let wrapper = TextWrapper::new(config); // 测试用例:列表项包含行内代码,不应该在行内代码前后换行 @@ -246,7 +274,6 @@ mod tests { assert!(lines[0].contains("class"), "第一行应该包含后续文本"); // 验证行内代码前后不换行(即第一行应该包含 `-` 和 `Use` 以及 `Config`) - // 注意:由于文本处理,可能 `-` 和 `Use` 之间有空格,所以检查它们都在第一行 assert!( lines[0].contains("-") && lines[0].contains("Use") && lines[0].contains("`Config`"), "列表项应该在行内代码前后保持整体,第一行: '{}'", @@ -257,9 +284,11 @@ mod tests { #[test] fn test_list_item_with_multiple_inline_codes() { // 测试列表项包含多个行内代码时作为整体处理 - let mut config = WrapConfig::default(); - config.max_width = 80; - config.preserve_paragraphs = true; + let config = WrapConfig { + max_width: 80, + preserve_paragraphs: true, + ..Default::default() + }; let wrapper = TextWrapper::new(config); // 测试用例:列表项包含多个行内代码 @@ -275,7 +304,6 @@ mod tests { ); // 验证列表项作为整体处理,行内代码前后不换行 - // 注意:由于文本处理,可能 `-` 和 `Add` 之间有空格,所以检查它们都在第一行 assert!( lines[0].contains("-") && lines[0].contains("Add") && lines[0].contains("`getUser`"), "列表项应该在第一个行内代码前后保持整体,第一行: '{}'", @@ -286,9 +314,11 @@ mod tests { #[test] fn test_list_item_preserve_paragraphs() { // 测试列表项在保留段落格式时的换行行为 - let mut config = WrapConfig::default(); - config.max_width = 80; - config.preserve_paragraphs = true; + let config = WrapConfig { + max_width: 80, + preserve_paragraphs: true, + ..Default::default() + }; let wrapper = TextWrapper::new(config); // 测试用例:包含多个列表项的段落 @@ -305,8 +335,7 @@ mod tests { // 验证列表项的第一行不应该只有 `-` assert!( line.trim().len() > 1, - "列表项不应该在 `-` 后立即换行: {}", - line + "列表项不应该在 `-` 后立即换行: {line}" ); } } @@ -316,9 +345,11 @@ mod tests { #[test] fn test_list_item_long_text_wrapping() { // 测试列表项文本很长时的换行行为 - let mut config = WrapConfig::default(); - config.max_width = 50; // 设置较小的宽度以触发换行 - config.preserve_paragraphs = true; + let config = WrapConfig { + max_width: 50, + preserve_paragraphs: true, + ..Default::default() + }; let wrapper = TextWrapper::new(config); // 测试用例:列表项文本很长,应该换行,但 `-` 不应该单独一行 @@ -338,8 +369,7 @@ mod tests { if !line.trim().is_empty() { assert!( !line.trim().starts_with("-") || line.trim().starts_with("- "), - "后续行不应该意外包含列表标记: {}", - line + "后续行不应该意外包含列表标记: {line}" ); } } diff --git a/src/text_wrapper/text_wrapper.rs b/src/text_wrapper/text_wrapper.rs index 5a00a97..a4d13d7 100644 --- a/src/text_wrapper/text_wrapper.rs +++ b/src/text_wrapper/text_wrapper.rs @@ -68,7 +68,7 @@ impl WordWrapper for CharacterWrapper { let mut lines = Vec::new(); let mut current_line = indent.clone(); - for (_i, ch) in text.chars().enumerate() { + for ch in text.chars() { if current_line.width() >= max_width && !current_line.trim().is_empty() { lines.push(current_line); current_line = hanging.clone(); diff --git a/src/update_checker.rs b/src/update_checker.rs index 7803d28..7a02f4f 100644 --- a/src/update_checker.rs +++ b/src/update_checker.rs @@ -58,11 +58,11 @@ pub struct Cache { pub async fn check_for_updates() -> Result> { // 获取当前版本 let current_version = env!("CARGO_PKG_VERSION"); - debug!("当前版本: {}", current_version); + debug!("当前版本: {current_version}"); // 获取缓存路径 let cache_path = get_cache_path()?; - debug!("缓存路径: {}", cache_path); + debug!("缓存路径: {cache_path}"); // 检查是否有有效的缓存 if let Some(cached_info) = load_cached_info(&cache_path)? { @@ -113,7 +113,7 @@ fn get_cache_path() -> Result { // 加载缓存的更新信息 fn load_cached_info(path: &str) -> Result> { - debug!("尝试加载缓存文件: {}", path); + debug!("尝试加载缓存文件: {path}"); if !Path::new(path).exists() { debug!("缓存文件不存在"); return Ok(None); @@ -129,7 +129,7 @@ fn load_cached_info(path: &str) -> Result> { // 保存更新信息到缓存 fn save_cached_info(path: &str, info: &UpdateInfo) -> Result<()> { - debug!("保存更新信息到缓存: {}", path); + debug!("保存更新信息到缓存: {path}"); let content = serde_json::to_string_pretty(info)?; fs::write(path, content)?; debug!("缓存保存成功"); @@ -147,10 +147,7 @@ fn is_cache_valid(info: &UpdateInfo) -> bool { if let Some(expired_time) = expired_time { let is_valid = current_time < expired_time; - debug!( - "过期时间: {}, 当前时间: {}, 缓存有效: {}", - expired_time, current_time, is_valid - ); + debug!("过期时间: {expired_time}, 当前时间: {current_time}, 缓存有效: {is_valid}"); is_valid } else { debug!("无法解析缓存时间,缓存无效"); @@ -160,17 +157,17 @@ fn is_cache_valid(info: &UpdateInfo) -> bool { // 解析时间字符串 fn parse_time(time_str: &str) -> Option { - debug!("解析时间字符串: {}", time_str); + debug!("解析时间字符串: {time_str}"); // 处理两种时间格式 if let Ok(ts) = time_str.parse::() { - debug!("解析为时间戳: {}", ts); + debug!("解析为时间戳: {ts}"); return Some(ts); } // 尝试解析RFC3339格式的时间 if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(time_str) { let timestamp = datetime.timestamp() as u64; - debug!("解析为RFC3339时间: {}", timestamp); + debug!("解析为RFC3339时间: {timestamp}"); Some(timestamp) } else { debug!("无法解析时间字符串"); @@ -181,7 +178,7 @@ fn parse_time(time_str: &str) -> Option { // 从网络获取最新版本信息 async fn fetch_latest_version() -> Result { let url = UPDATE_CHECKER_URL; - debug!("发送请求到: {}", url); + debug!("发送请求到: {url}"); let response = reqwest::get(url) .await @@ -237,10 +234,7 @@ fn create_default_update_info() -> UpdateInfo { // 比较版本号 fn is_newer_version(remote_version: &str, current_version: &str) -> bool { - debug!( - "比较版本号: 远程版本={}, 当前版本={}", - remote_version, current_version - ); + debug!("比较版本号: 远程版本={remote_version}, 当前版本={current_version}"); // 移除版本号前的'v'字符(如果存在) let remote = remote_version.strip_prefix('v').unwrap_or(remote_version); let current = current_version.strip_prefix('v').unwrap_or(current_version); @@ -257,7 +251,7 @@ fn is_newer_version(remote_version: &str, current_version: &str) -> bool { for (r, c) in remote_parts.iter().zip(current_parts.iter()) { match (r.parse::(), c.parse::()) { (Ok(r_num), Ok(c_num)) => { - debug!("比较数字版本段: {} vs {}", r_num, c_num); + debug!("比较数字版本段: {r_num} vs {c_num}"); if r_num > c_num { debug!("远程版本更新"); return true; @@ -270,7 +264,7 @@ fn is_newer_version(remote_version: &str, current_version: &str) -> bool { } _ => { // 如果无法解析为数字,按字典序比较 - debug!("按字典序比较版本段: {} vs {}", r, c); + debug!("按字典序比较版本段: {r} vs {c}"); if r > c { debug!("远程版本更新"); return true; @@ -284,7 +278,7 @@ fn is_newer_version(remote_version: &str, current_version: &str) -> bool { // 如果所有段都相等,检查是否有更多段 let result = remote_parts.len() > current_parts.len(); - debug!("版本段比较完成,远程版本是否更新: {}", result); + debug!("版本段比较完成,远程版本是否更新: {result}"); result } @@ -298,12 +292,12 @@ pub fn display_update_info(update_info: &UpdateInfo) { let vertical = "│"; let content = vec![ - format!("✨ fastcommit has a new version available!"), + "✨ fastcommit has a new version available!".to_string(), String::new(), format!("📦 New version: {}", update_info.version), format!("📅 Release date: {}", update_info.published_at), String::new(), - format!("🚀 Install the new version with the following command:"), + "🚀 Install the new version with the following command:".to_string(), format!( " cargo install --git https://github.com/fslongjin/fastcommit --tag {}", update_info.tag @@ -331,7 +325,7 @@ pub fn display_update_info(update_info: &UpdateInfo) { let box_width = max_width + 4; // 2 spaces padding on each side // Top border - println!("{}{}{}", corner_tl, border.repeat(box_width), corner_tr); + println!("{corner_tl}{}{corner_tr}", border.repeat(box_width)); // Content lines for line in content { @@ -346,17 +340,17 @@ pub fn display_update_info(update_info: &UpdateInfo) { } } - print!("{} ", vertical); - print!("{}", line); + print!("{vertical} "); + print!("{line}"); // Add padding to align the right border for _ in 0..(max_width - display_width) { print!(" "); } - println!(" {}", vertical); + println!(" {vertical}"); } // Bottom border - println!("{}{}{}", corner_bl, border.repeat(box_width), corner_br); + println!("{corner_bl}{}{corner_br}", border.repeat(box_width)); println!(); } @@ -366,10 +360,10 @@ mod tests { #[test] fn test_version_comparison() { - assert_eq!(is_newer_version("v0.1.8", "v0.1.7"), true); - assert_eq!(is_newer_version("v0.2.0", "v0.1.7"), true); - assert_eq!(is_newer_version("v1.0.0", "v0.1.7"), true); - assert_eq!(is_newer_version("v0.1.7", "v0.1.7"), false); - assert_eq!(is_newer_version("v0.1.6", "v0.1.7"), false); + assert!(is_newer_version("v0.1.8", "v0.1.7")); + assert!(is_newer_version("v0.2.0", "v0.1.7")); + assert!(is_newer_version("v1.0.0", "v0.1.7")); + assert!(!is_newer_version("v0.1.7", "v0.1.7")); + assert!(!is_newer_version("v0.1.6", "v0.1.7")); } } From 77a6f5c542bc5c12de1c35c20a90f6c1d372541f Mon Sep 17 00:00:00 2001 From: staryxchen Date: Tue, 24 Feb 2026 11:51:55 +0800 Subject: [PATCH 4/5] chore(pre-commit): add standard pre-commit hooks - add trailing-whitespace hook - add end-of-file-fixer hook - add check-yaml hook - add check-toml hook - add check-merge-conflict hook - add check-added-large-files hook Signed-off-by: staryxchen --- .pre-commit-config.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aaacdc3..b48341f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,13 @@ repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-toml + - id: check-merge-conflict + - id: check-added-large-files - repo: https://github.com/doublify/pre-commit-rust rev: v1.0 hooks: From dec3ff62619fd1ddded8a7e7b2974674b1c8d9f1 Mon Sep 17 00:00:00 2001 From: staryxchen Date: Tue, 24 Feb 2026 11:52:30 +0800 Subject: [PATCH 5/5] style: clean up whitespace and line endings - Remove trailing whitespace in workflow, gitignore, and readme files - Ensure newline at end of file in docs/TEXT_WRAPPING.md Signed-off-by: staryxchen --- .github/workflows/release.yml | 2 +- .gitignore | 2 +- docs/TEXT_WRAPPING.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d742d44..53c8c88 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,7 @@ on: tags: - 'v*' workflow_dispatch: - + jobs: build: diff --git a/.gitignore b/.gitignore index ebb58f0..abb25c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ /target -.codebuddy/ \ No newline at end of file +.codebuddy/ diff --git a/docs/TEXT_WRAPPING.md b/docs/TEXT_WRAPPING.md index 4161d6e..7d357a6 100644 --- a/docs/TEXT_WRAPPING.md +++ b/docs/TEXT_WRAPPING.md @@ -420,4 +420,4 @@ Contributions are welcome! Please refer to: ## License -This feature follows the project's MIT license. \ No newline at end of file +This feature follows the project's MIT license.