From c3cd01dbcd2051163852d4d4ebc8170e134fbb5f Mon Sep 17 00:00:00 2001 From: SidneyZhang Date: Sun, 1 Feb 2026 15:09:39 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E6=96=B0=E5=A2=9E=E5=AF=B9=20Kimi?= =?UTF-8?q?=E3=80=81DeepSeek=E3=80=81OpenRouter=20=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=B9=B6=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC=E8=87=B3?= =?UTF-8?q?=200.1.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 6 +-- src/commands/commit.rs | 19 ++++--- src/commands/init.rs | 27 +++++++++- src/commands/profile.rs | 6 +-- src/commands/tag.rs | 1 - src/config/mod.rs | 5 +- src/generator/mod.rs | 107 ---------------------------------------- src/git/commit.rs | 44 ++++++----------- src/git/mod.rs | 2 +- src/git/tag.rs | 17 ------- 10 files changed, 61 insertions(+), 173 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1ff23b3..9b2074d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "quicommit" -version = "0.1.0" +version = "0.1.2" edition = "2024" authors = ["Sidney Zhang "] -description = "A powerful Git assistant tool with AI-powered commit/tag/changelog generation" +description = "A powerful Git assistant tool with AI-powered commit/tag/changelog generation(alpha version)" license = "MIT" -repository = "https://github.com/yourusername/quicommit" +repository = "https://git.lyz.one/SidneyZhang/QuiCommit" keywords = ["git", "commit", "ai", "cli", "automation"] categories = ["command-line-utilities", "development-tools"] diff --git a/src/commands/commit.rs b/src/commands/commit.rs index 24b9506..e578e4c 100644 --- a/src/commands/commit.rs +++ b/src/commands/commit.rs @@ -148,17 +148,22 @@ impl CommitCommand { } } - if self.dry_run { - println!("\n{}", "Dry run - commit not created.".yellow()); - return Ok(()); - } - - // Execute commit let result = if self.amend { + if self.dry_run { + println!("\n{}", "Dry run - commit not amended.".yellow()); + return Ok(()); + } self.amend_commit(&repo, &commit_message)?; None } else { - Some(repo.commit(&commit_message, self.sign)?) + if self.dry_run { + println!("\n{}", "Dry run - commit not created.".yellow()); + return Ok(()); + } + CommitBuilder::new() + .message(&commit_message) + .sign(self.sign) + .execute(&repo)? }; if let Some(commit_oid) = result { diff --git a/src/commands/init.rs b/src/commands/init.rs index 4557b64..f1602e0 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -185,7 +185,14 @@ impl InitCommand { // LLM provider selection println!("\n{}", "Select your preferred LLM provider:".bold()); - let providers = vec!["Ollama (local)", "OpenAI", "Anthropic Claude"]; + let providers = vec![ + "Ollama (local)", + "OpenAI", + "Anthropic Claude", + "Kimi (Moonshot AI)", + "DeepSeek", + "OpenRouter" + ]; let provider_idx = Select::new() .items(&providers) .default(0) @@ -195,6 +202,9 @@ impl InitCommand { 0 => "ollama", 1 => "openai", 2 => "anthropic", + 3 => "kimi", + 4 => "deepseek", + 5 => "openrouter", _ => "ollama", }; @@ -211,6 +221,21 @@ impl InitCommand { .with_prompt("Anthropic API key") .interact_text()?; manager.set_anthropic_api_key(api_key); + } else if provider == "kimi" { + let api_key: String = Input::new() + .with_prompt("Kimi API key") + .interact_text()?; + manager.set_kimi_api_key(api_key); + } else if provider == "deepseek" { + let api_key: String = Input::new() + .with_prompt("DeepSeek API key") + .interact_text()?; + manager.set_deepseek_api_key(api_key); + } else if provider == "openrouter" { + let api_key: String = Input::new() + .with_prompt("OpenRouter API key") + .interact_text()?; + manager.set_openrouter_api_key(api_key); } Ok(()) diff --git a/src/commands/profile.rs b/src/commands/profile.rs index 082b499..d038623 100644 --- a/src/commands/profile.rs +++ b/src/commands/profile.rs @@ -1,12 +1,12 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{bail, Result}; use clap::{Parser, Subcommand}; use colored::Colorize; use dialoguer::{Confirm, Input, Select}; use crate::config::manager::ConfigManager; -use crate::config::{GitProfile, TokenConfig, TokenType, ProfileComparison}; +use crate::config::{GitProfile, TokenConfig, TokenType}; use crate::config::profile::{GpgConfig, SshConfig}; -use crate::git::{find_repo, GitConfigHelper, UserConfig}; +use crate::git::find_repo; use crate::utils::validators::validate_profile_name; /// Manage Git profiles diff --git a/src/commands/tag.rs b/src/commands/tag.rs index c6abbec..d84c64d 100644 --- a/src/commands/tag.rs +++ b/src/commands/tag.rs @@ -144,7 +144,6 @@ impl TagCommand { return Ok(()); } - // Create tag let builder = TagBuilder::new() .name(&tag_name) .message_opt(message) diff --git a/src/config/mod.rs b/src/config/mod.rs index 540f973..5ee9dad 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -7,10 +7,9 @@ use std::path::{Path, PathBuf}; pub mod manager; pub mod profile; -pub use manager::ConfigManager; pub use profile::{ - GitProfile, ProfileSettings, SshConfig, GpgConfig, TokenConfig, TokenType, - UsageStats, ProfileComparison, ConfigDifference + GitProfile, TokenConfig, TokenType, + UsageStats, ProfileComparison }; /// Application configuration diff --git a/src/generator/mod.rs b/src/generator/mod.rs index fe900b0..d1cbe29 100644 --- a/src/generator/mod.rs +++ b/src/generator/mod.rs @@ -190,115 +190,8 @@ impl ContentGenerator { } } -/// Batch generator for multiple operations -pub struct BatchGenerator { - generator: ContentGenerator, -} - -impl BatchGenerator { - /// Create new batch generator - pub async fn new(config: &LlmConfig) -> Result { - let generator = ContentGenerator::new(config).await?; - Ok(Self { generator }) - } - - /// Generate commits for multiple repositories - pub async fn generate_commits_batch<'a>( - &self, - repos: &[&'a GitRepo], - format: CommitFormat, - ) -> Vec<(&'a str, Result)> { - let mut results = vec![]; - - for repo in repos { - let result = self.generator.generate_commit_from_repo(repo, format).await; - results.push((repo.path().to_str().unwrap_or("unknown"), result)); - } - - results - } - - /// Generate changelog for multiple versions - pub async fn generate_changelog_batch( - &self, - repo: &GitRepo, - versions: &[String], - ) -> Vec<(String, Result)> { - let mut results = vec![]; - - // Get all tags - let tags = repo.get_tags().unwrap_or_default(); - - for (i, version) in versions.iter().enumerate() { - let from_tag = if i + 1 < tags.len() { - tags.get(i + 1).map(|t| t.name.as_str()) - } else { - None - }; - - let result = self.generator.generate_changelog_from_repo(repo, version, from_tag).await; - results.push((version.clone(), result)); - } - - results - } -} - -/// Generator options -#[derive(Debug, Clone)] -pub struct GeneratorOptions { - pub auto_commit: bool, - pub auto_push: bool, - pub interactive: bool, - pub dry_run: bool, -} - -impl Default for GeneratorOptions { - fn default() -> Self { - Self { - auto_commit: false, - auto_push: false, - interactive: true, - dry_run: false, - } - } -} - -/// Generate with options -pub async fn generate_with_options( - repo: &GitRepo, - config: &LlmConfig, - format: CommitFormat, - options: GeneratorOptions, -) -> Result> { - let generator = ContentGenerator::new(config).await?; - - let generated = if options.interactive { - generator.generate_commit_interactive(repo, format).await? - } else { - generator.generate_commit_from_repo(repo, format).await? - }; - - if options.dry_run { - println!("{}", generated.to_conventional()); - return Ok(Some(generated)); - } - - if options.auto_commit { - let message = generated.to_conventional(); - repo.commit(&message, false)?; - - if options.auto_push { - repo.push("origin", "HEAD")?; - } - } - - Ok(Some(generated)) -} - /// Fallback generators when LLM is not available pub mod fallback { - use super::*; use crate::git::commit::create_date_commit_message; /// Generate simple commit message without LLM diff --git a/src/git/commit.rs b/src/git/commit.rs index 31987f5..53da566 100644 --- a/src/git/commit.rs +++ b/src/git/commit.rs @@ -9,11 +9,11 @@ pub struct CommitBuilder { description: Option, body: Option, footer: Option, + message: Option, breaking: bool, sign: bool, amend: bool, no_verify: bool, - dry_run: bool, format: crate::config::CommitFormat, } @@ -26,11 +26,11 @@ impl CommitBuilder { description: None, body: None, footer: None, + message: None, breaking: false, sign: false, amend: false, no_verify: false, - dry_run: false, format: crate::config::CommitFormat::Conventional, } } @@ -65,6 +65,12 @@ impl CommitBuilder { self } + /// Set message + pub fn message(mut self, message: impl Into) -> Self { + self.message = Some(message.into()); + self + } + /// Mark as breaking change pub fn breaking(mut self, breaking: bool) -> Self { self.breaking = breaking; @@ -89,12 +95,6 @@ impl CommitBuilder { self } - /// Dry run (don't actually commit) - pub fn dry_run(mut self, dry_run: bool) -> Self { - self.dry_run = dry_run; - self - } - /// Set commit format pub fn format(mut self, format: crate::config::CommitFormat) -> Self { self.format = format; @@ -103,6 +103,10 @@ impl CommitBuilder { /// Build commit message pub fn build_message(&self) -> Result { + if let Some(ref msg) = self.message { + return Ok(msg.clone()); + } + let commit_type = self.commit_type.as_ref() .ok_or_else(|| anyhow::anyhow!("Commit type is required"))?; @@ -139,33 +143,13 @@ impl CommitBuilder { pub fn execute(&self, repo: &GitRepo) -> Result> { let message = self.build_message()?; - if self.dry_run { - return Ok(Some(message)); - } - - // Check if there are staged changes - let staged_files = repo.get_staged_files()?; - if staged_files.is_empty() && !self.amend { - bail!("No staged changes to commit. Use 'git add' to stage files first."); - } - - // Validate message - match self.format { - crate::config::CommitFormat::Conventional => { - crate::utils::validators::validate_conventional_commit(&message)?; - } - crate::config::CommitFormat::Commitlint => { - crate::utils::validators::validate_commitlint_commit(&message)?; - } - } - if self.amend { self.amend_commit(repo, &message)?; + Ok(None) } else { repo.commit(&message, self.sign)?; + Ok(None) } - - Ok(None) } fn amend_commit(&self, repo: &GitRepo, message: &str) -> Result<()> { diff --git a/src/git/mod.rs b/src/git/mod.rs index 6b86b51..fbccb1e 100644 --- a/src/git/mod.rs +++ b/src/git/mod.rs @@ -128,7 +128,7 @@ impl GitRepo { } /// Create a signature using repository configuration - pub fn create_signature(&self) -> Result { + pub fn create_signature(&self) -> Result> { let name = self.get_user_name()?; let email = self.get_user_email()?; let time = git2::Time::new(std::time::SystemTime::now() diff --git a/src/git/tag.rs b/src/git/tag.rs index 28d6e02..e14526a 100644 --- a/src/git/tag.rs +++ b/src/git/tag.rs @@ -9,7 +9,6 @@ pub struct TagBuilder { annotate: bool, sign: bool, force: bool, - dry_run: bool, version_prefix: String, } @@ -22,7 +21,6 @@ impl TagBuilder { annotate: true, sign: false, force: false, - dry_run: false, version_prefix: "v".to_string(), } } @@ -57,12 +55,6 @@ impl TagBuilder { self } - /// Dry run (don't actually create tag) - pub fn dry_run(mut self, dry_run: bool) -> Self { - self.dry_run = dry_run; - self - } - /// Set version prefix pub fn version_prefix(mut self, prefix: impl Into) -> Self { self.version_prefix = prefix.into(); @@ -92,15 +84,6 @@ impl TagBuilder { let name = self.name.as_ref() .ok_or_else(|| anyhow::anyhow!("Tag name is required"))?; - if self.dry_run { - println!("Would create tag: {}", name); - if self.annotate { - println!("Message: {}", self.build_message()?); - } - return Ok(()); - } - - // Check if tag already exists if !self.force { let existing_tags = repo.get_tags()?; if existing_tags.iter().any(|t| t.name == *name) {