From bfc1812ebfe2b21beca4e4ff848b3f1a4ed1058f Mon Sep 17 00:00:00 2001 From: SidneyZhang Date: Sun, 1 Feb 2026 13:50:09 +0000 Subject: [PATCH] docs: update readme with new installation methods and cli options --- README.md | 234 ++++++++++++++++++++++++++++++++++--- readme_zh.md | 240 ++++++++++++++++++++++++++++++++++---- src/commands/changelog.rs | 8 +- src/commands/commit.rs | 8 +- src/commands/tag.rs | 3 +- src/generator/mod.rs | 9 +- src/git/mod.rs | 85 ++++++++------ src/llm/mod.rs | 30 ++++- 8 files changed, 531 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 1c8c373..20d5252 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ English | [中文文档](README_zh.md) A powerful AI-powered Git assistant for generating conventional commits, tags, and changelogs. Manage multiple Git profiles for different work contexts. +[Still in early development, some features may not be complete. Feedback and contributions are welcome.] + ![Rust](https://img.shields.io/badge/rust-%23000000.svg?style=for-the-badge&logo=rust&logoColor=white) ![License](https://img.shields.io/badge/license-MIT-blue.svg) @@ -19,9 +21,19 @@ A powerful AI-powered Git assistant for generating conventional commits, tags, a ## Installation +### Cargo Install + +The cargo-installed version may temporarily lag behind the source code progress. + ```bash -git clone https://github.com/yourusername/quicommit.git -cd quicommit +cargo install quicommit +``` + +### Install from Source + +```bash +git clone https://git.lyz.one/SidneyZhang/QuiCommit.git +cd QuiCommit cargo build --release cargo install --path . ``` @@ -50,6 +62,12 @@ quicommit commit -a # Skip confirmation quicommit commit --yes + +# Use date-based commit message +quicommit commit --date + +# Push after committing +quicommit commit --push ``` ### Create Tag @@ -63,6 +81,12 @@ quicommit tag --bump minor # Custom tag name quicommit tag -n v1.0.0 + +# AI-generate tag message +quicommit tag --generate + +# Create tag and push to remote +quicommit tag --push ``` ### Generate Changelog @@ -73,6 +97,15 @@ quicommit changelog # Generate for specific version quicommit changelog -v 1.0.0 + +# AI-generate changelog +quicommit changelog --generate + +# Initialize new changelog file +quicommit changelog --init + +# Specify output file +quicommit changelog -o RELEASE_NOTES.md ``` ### Manage Profiles @@ -84,11 +117,41 @@ quicommit profile add # List profiles quicommit profile list +# Show profile details +quicommit profile show + # Switch profile quicommit profile switch +# Set default profile +quicommit profile set-default personal + # Set profile for current repo quicommit profile set-repo personal + +# Apply profile to current repo +quicommit profile apply + +# Apply profile globally +quicommit profile apply --global + +# Copy profile +quicommit profile copy personal work + +# Edit profile +quicommit profile edit personal + +# Remove profile +quicommit profile remove old-profile + +# Check profile +quicommit profile check + +# View usage statistics +quicommit profile stats + +# Manage profile tokens +quicommit profile token ``` ### Configure LLM @@ -109,17 +172,41 @@ quicommit config set-anthropic-key YOUR_API_KEY # Configure Kimi (Moonshot AI) quicommit config set-llm kimi quicommit config set-kimi-key YOUR_API_KEY +quicommit config set-kimi --base-url https://api.moonshot.cn/v1 --model moonshot-v1-8k # Configure DeepSeek quicommit config set-llm deepseek quicommit config set-deepseek-key YOUR_API_KEY +quicommit config set-deepseek --base-url https://api.deepseek.com/v1 --model deepseek-chat # Configure OpenRouter quicommit config set-llm openrouter quicommit config set-openrouter-key YOUR_API_KEY +quicommit config set-openrouter --base-url https://openrouter.ai/api/v1 --model openai/gpt-4 + +# Set commit format +quicommit config set-commit-format conventional + +# Set version prefix +quicommit config set-version-prefix v + +# Set changelog path +quicommit config set-changelog-path CHANGELOG.md + +# Set output language +quicommit config set-language en + +# Set keep commit types in English +quicommit config set-keep-types-english true + +# Set keep changelog types in English +quicommit config set-keep-changelog-types-english true # Test LLM connection quicommit config test-llm + +# Reset configuration to defaults +quicommit config reset ``` ## Command Reference @@ -137,17 +224,23 @@ quicommit config test-llm | Option | Description | |--------|-------------| -| `-t, --commit-type` | Commit type (feat, fix, etc.) | +| `--commit-type` | Commit type (feat, fix, etc.) | | `-s, --scope` | Commit scope | | `-m, --message` | Commit description | | `--body` | Commit body | -| `--breaking` | Mark as breaking change | +| `-b, --breaking` | Mark as breaking change | +| `-d, --date` | Use date-based commit message | | `--manual` | Manual input, skip AI | | `-a, --all` | Stage all changes | | `-S, --sign` | GPG sign commit | | `--amend` | Amend previous commit | | `--dry-run` | Show without committing | +| `--conventional` | Use Conventional Commits format | +| `--commitlint` | Use commitlint format | +| `--no-verify` | Skip commit message verification | | `-y, --yes` | Skip confirmation | +| `--push` | Push after committing | +| `--remote` | Specify remote repository (default: origin) | ### Tag Options @@ -158,14 +251,35 @@ quicommit config test-llm | `-m, --message` | Tag message | | `-g, --generate` | AI-generate message | | `-S, --sign` | GPG sign tag | -| `--lightweight` | Create lightweight tag | -| `--push` | Push to remote | +| `-l, --lightweight` | Create lightweight tag | +| `-f, --force` | Force overwrite existing tag | +| `-p, --push` | Push to remote | +| `-r, --remote` | Specify remote repository (default: origin) | +| `--dry-run` | Dry run | +| `-y, --yes` | Skip confirmation | + +### Changelog Options + +| Option | Description | +|--------|-------------| +| `-o, --output` | Output file path | +| `-v, --version` | Generate for specific version | +| `-f, --from` | Generate from specific tag | +| `-t, --to` | Generate to specific ref (default: HEAD) | +| `-i, --init` | Initialize new changelog file | +| `-g, --generate` | AI-generate changelog | +| `--prepend` | Prepend to existing changelog | +| `--include-hashes` | Include commit hashes | +| `--include-authors` | Include authors | +| `--format` | Format (keep-a-changelog, github-releases) | +| `--dry-run` | Dry run (output to stdout) | | `-y, --yes` | Skip confirmation | ## Configuration File Location: -- Linux/macOS: `~/.config/quicommit/config.toml` +- Linux: `~/.config/quicommit/config.toml` +- macOS: `~/Library/Application Support/quicommit/config.toml` - Windows: `%APPDATA%\quicommit\config.toml` ```toml @@ -174,15 +288,27 @@ default_profile = "personal" [profiles.personal] name = "personal" -user_name = "John Doe" -user_email = "john@example.com" +user_name = "Your Name" +user_email = "your.email@example.com" +description = "Personal projects" +is_work = false [profiles.work] name = "work" -user_name = "John Doe" -user_email = "john@company.com" +user_name = "Your Name" +user_email = "your.name@company.com" +description = "Work projects" is_work = true -organization = "Acme Corp" +organization = "Your Company" + +[profiles.work.ssh] +private_key_path = "/home/user/.ssh/id_rsa_work" +agent_forwarding = true + +[profiles.work.gpg] +key_id = "YOUR_GPG_KEY_ID" +program = "gpg" +use_agent = true [llm] provider = "ollama" @@ -198,19 +324,50 @@ model = "llama2" model = "gpt-4" base_url = "https://api.openai.com/v1" +[llm.anthropic] +model = "claude-3-sonnet-20240229" + +[llm.kimi] +model = "moonshot-v1-8k" + +[llm.deepseek] +model = "deepseek-chat" + +[llm.openrouter] +model = "openai/gpt-4" + [commit] format = "conventional" auto_generate = true +allow_empty = false +gpg_sign = false max_subject_length = 100 +require_scope = false +require_body = false +body_required_types = ["feat", "fix"] [tag] version_prefix = "v" auto_generate = true +gpg_sign = false +include_changelog = true [changelog] path = "CHANGELOG.md" auto_generate = true +format = "keep-a-changelog" +include_hashes = false +include_authors = false group_by_type = true + +[theme] +colors = true +icons = true +date_format = "%Y-%m-%d" + +[repo_profiles] +"/path/to/work/project" = "work" +"/path/to/personal/project" = "personal" ``` ## Environment Variables @@ -227,14 +384,32 @@ group_by_type = true # View current configuration quicommit config list +# Show configuration details +quicommit config show + +# Edit configuration file +quicommit config edit + +# Set configuration value +quicommit config set llm.provider ollama + +# Get configuration value +quicommit config get llm.provider + # Test LLM connection quicommit config test-llm # List available models quicommit config list-models -# Edit configuration -quicommit config edit +# Export configuration +quicommit config export -o config-backup.toml + +# Import configuration +quicommit config import -i config-backup.toml + +# Reset configuration +quicommit config reset --force ``` ## Contributing @@ -253,8 +428,8 @@ Contributions are welcome! Please follow these steps: ```bash # Clone repository -git clone https://github.com/YOUR_USERNAME/quicommit.git -cd quicommit +git clone https://git.lyz.one/SidneyZhang/QuiCommit.git +cd QuiCommit # Fetch dependencies cargo fetch @@ -282,11 +457,36 @@ cargo fmt --check ``` src/ ├── commands/ # CLI command implementations +│ ├── commit.rs +│ ├── tag.rs +│ ├── changelog.rs +│ ├── profile.rs +│ ├── config.rs +│ └── init.rs ├── config/ # Configuration management +│ ├── manager.rs +│ └── profile.rs ├── generator/ # AI content generation ├── git/ # Git operations +│ ├── commit.rs +│ ├── tag.rs +│ └── changelog.rs ├── llm/ # LLM provider implementations -└── utils/ # Utility functions +│ ├── ollama.rs +│ ├── openai.rs +│ ├── anthropic.rs +│ ├── kimi.rs +│ ├── deepseek.rs +│ └── openrouter.rs +├── i18n/ # Internationalization support +│ ├── messages.rs +│ └── translator.rs +├── utils/ # Utility functions +│ ├── validators.rs +│ ├── formatter.rs +│ ├── crypto.rs +│ └── editor.rs +└── main.rs # Program entry point ``` ## License diff --git a/readme_zh.md b/readme_zh.md index 973de27..c1c202b 100644 --- a/readme_zh.md +++ b/readme_zh.md @@ -4,8 +4,10 @@ 一款强大的AI驱动的Git助手,用于生成规范化的提交信息、标签和变更日志,并支持管理多个Git配置。 -![Rust](https://img.shields.io/badge/rust-%23000000.svg?logo=rust&logoColor=white) -![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg) +【目前还处在早期开发阶段,依然有一些功能未完善,欢迎反馈和贡献。】 + +![Rust](https://img.shields.io/badge/rust-%23000000.svg?style=for-the-badge&logo=rust&logoColor=white) +![License](https://img.shields.io/badge/license-MIT-blue.svg) ## 主要功能 @@ -19,11 +21,19 @@ ## 安装 -目前,整体工具还在开发,并不保证各项功能准确达到既定目标。但依然十分欢迎参与贡献、反馈问题和建议。 +### cargo安装 + +cargo安装版本可能暂时不如源码进展快速。 ```bash -git clone https://github.com/yourusername/quicommit.git -cd quicommit +cargo install quicommit +``` + +### 从源代码安装 + +```bash +git clone https://git.lyz.one/SidneyZhang/QuiCommit.git +cd QuiCommit cargo build --release cargo install --path . ``` @@ -52,6 +62,12 @@ quicommit commit -a # 跳过确认直接提交 quicommit commit --yes + +# 使用日期格式的提交信息 +quicommit commit --date + +# 提交后推送到远程 +quicommit commit --push ``` ### 创建标签 @@ -65,6 +81,12 @@ quicommit tag --bump minor # 自定义标签名 quicommit tag -n v1.0.0 + +# AI生成标签信息 +quicommit tag --generate + +# 创建标签并推送到远程 +quicommit tag --push ``` ### 生成变更日志 @@ -75,6 +97,15 @@ quicommit changelog # 为特定版本生成 quicommit changelog -v 1.0.0 + +# AI生成变更日志 +quicommit changelog --generate + +# 初始化新的变更日志文件 +quicommit changelog --init + +# 指定输出文件 +quicommit changelog -o RELEASE_NOTES.md ``` ### 配置管理 @@ -86,11 +117,41 @@ quicommit profile add # 查看配置列表 quicommit profile list +# 显示配置详情 +quicommit profile show + # 切换配置 quicommit profile switch +# 设置默认配置 +quicommit profile set-default personal + # 设置当前仓库的配置 quicommit profile set-repo personal + +# 应用配置到当前仓库 +quicommit profile apply + +# 全局应用配置 +quicommit profile apply --global + +# 复制配置 +quicommit profile copy personal work + +# 编辑配置 +quicommit profile edit personal + +# 删除配置 +quicommit profile remove old-profile + +# 检查配置 +quicommit profile check + +# 查看使用统计 +quicommit profile stats + +# 管理配置的令牌 +quicommit profile token ``` ### LLM配置 @@ -111,17 +172,41 @@ quicommit config set-anthropic-key YOUR_API_KEY # 配置Kimi quicommit config set-llm kimi quicommit config set-kimi-key YOUR_API_KEY +quicommit config set-kimi --base-url https://api.moonshot.cn/v1 --model moonshot-v1-8k # 配置DeepSeek quicommit config set-llm deepseek quicommit config set-deepseek-key YOUR_API_KEY +quicommit config set-deepseek --base-url https://api.deepseek.com/v1 --model deepseek-chat # 配置OpenRouter quicommit config set-llm openrouter quicommit config set-openrouter-key YOUR_API_KEY +quicommit config set-openrouter --base-url https://openrouter.ai/api/v1 --model openai/gpt-4 + +# 设置提交格式 +quicommit config set-commit-format conventional + +# 设置版本前缀 +quicommit config set-version-prefix v + +# 设置变更日志路径 +quicommit config set-changelog-path CHANGELOG.md + +# 设置输出语言 +quicommit config set-language zh + +# 设置保持提交类型为英文 +quicommit config set-keep-types-english true + +# 设置保持变更日志类型为英文 +quicommit config set-keep-changelog-types-english true # 测试LLM连接 quicommit config test-llm + +# 重置配置为默认值 +quicommit config reset ``` ## 命令参考 @@ -139,17 +224,23 @@ quicommit config test-llm | 选项 | 说明 | |------|------| -| `-t, --commit-type` | 提交类型(feat、fix等) | +| `--commit-type` | 提交类型(feat、fix等) | | `-s, --scope` | 提交范围 | | `-m, --message` | 提交描述 | | `--body` | 提交正文 | -| `--breaking` | 标记为破坏性变更 | +| `-b, --breaking` | 标记为破坏性变更 | +| `-d, --date` | 使用日期格式的提交信息 | | `--manual` | 手动输入,跳过AI生成 | | `-a, --all` | 暂存所有更改 | | `-S, --sign` | GPG签名提交 | | `--amend` | 修改上一次提交 | | `--dry-run` | 试运行,不实际提交 | +| `--conventional` | 使用Conventional Commits格式 | +| `--commitlint` | 使用commitlint格式 | +| `--no-verify` | 不验证提交信息 | | `-y, --yes` | 跳过确认提示 | +| `--push` | 提交后推送到远程 | +| `--remote` | 指定远程仓库(默认:origin) | ### tag命令选项 @@ -160,15 +251,36 @@ quicommit config test-llm | `-m, --message` | 标签信息 | | `-g, --generate` | AI生成标签信息 | | `-S, --sign` | GPG签名标签 | -| `--lightweight` | 创建轻量标签 | -| `--push` | 推送到远程 | +| `-l, --lightweight` | 创建轻量标签 | +| `-f, --force` | 强制覆盖已存在的标签 | +| `-p, --push` | 推送到远程 | +| `-r, --remote` | 指定远程仓库(默认:origin) | +| `--dry-run` | 试运行 | +| `-y, --yes` | 跳过确认提示 | + +### changelog命令选项 + +| 选项 | 说明 | +|------|------| +| `-o, --output` | 输出文件路径 | +| `-v, --version` | 为特定版本生成 | +| `-f, --from` | 从指定标签生成 | +| `-t, --to` | 生成到指定引用(默认:HEAD) | +| `-i, --init` | 初始化新的变更日志文件 | +| `-g, --generate` | AI生成变更日志 | +| `--prepend` | 添加到现有变更日志开头 | +| `--include-hashes` | 包含提交哈希 | +| `--include-authors` | 包含作者信息 | +| `--format` | 格式(keep-a-changelog、github-releases) | +| `--dry-run` | 试运行(输出到stdout) | | `-y, --yes` | 跳过确认提示 | ## 配置文件 配置文件位置: -- Linux/macOS: `~/.config/quicommit/config.toml` -- Windows: `%APPDATA%\quicommit\config.toml` +- Linux: `~/.config/quicommit/config.toml` +- macOS: `~/Library/Application Support/quicommit/config.toml` +- Windows: `%APPDATA%\quicommit/config.toml` ```toml version = "1" @@ -176,15 +288,27 @@ default_profile = "personal" [profiles.personal] name = "personal" -user_name = "John Doe" -user_email = "john@example.com" +user_name = "Your Name" +user_email = "your.email@example.com" +description = "个人项目" +is_work = false [profiles.work] name = "work" -user_name = "John Doe" -user_email = "john@company.com" +user_name = "Your Name" +user_email = "your.name@company.com" +description = "工作项目" is_work = true -organization = "Acme Corp" +organization = "Your Company" + +[profiles.work.ssh] +private_key_path = "/home/user/.ssh/id_rsa_work" +agent_forwarding = true + +[profiles.work.gpg] +key_id = "YOUR_GPG_KEY_ID" +program = "gpg" +use_agent = true [llm] provider = "ollama" @@ -200,19 +324,50 @@ model = "llama2" model = "gpt-4" base_url = "https://api.openai.com/v1" +[llm.anthropic] +model = "claude-3-sonnet-20240229" + +[llm.kimi] +model = "moonshot-v1-8k" + +[llm.deepseek] +model = "deepseek-chat" + +[llm.openrouter] +model = "openai/gpt-4" + [commit] format = "conventional" auto_generate = true +allow_empty = false +gpg_sign = false max_subject_length = 100 +require_scope = false +require_body = false +body_required_types = ["feat", "fix"] [tag] version_prefix = "v" auto_generate = true +gpg_sign = false +include_changelog = true [changelog] path = "CHANGELOG.md" auto_generate = true +format = "keep-a-changelog" +include_hashes = false +include_authors = false group_by_type = true + +[theme] +colors = true +icons = true +date_format = "%Y-%m-%d" + +[repo_profiles] +"/path/to/work/project" = "work" +"/path/to/personal/project" = "personal" ``` ## 环境变量 @@ -229,14 +384,32 @@ group_by_type = true # 查看当前配置 quicommit config list +# 显示配置详情 +quicommit config show + +# 编辑配置文件 +quicommit config edit + +# 设置配置值 +quicommit config set llm.provider ollama + +# 获取配置值 +quicommit config get llm.provider + # 测试LLM连接 quicommit config test-llm # 列出可用模型 quicommit config list-models -# 编辑配置文件 -quicommit config edit +# 导出配置 +quicommit config export -o config-backup.toml + +# 导入配置 +quicommit config import -i config-backup.toml + +# 重置配置 +quicommit config reset --force ``` ## 贡献 @@ -255,8 +428,8 @@ quicommit config edit ```bash # 克隆仓库 -git clone https://github.com/YOUR_USERNAME/quicommit.git -cd quicommit +git clone https://git.lyz.one/SidneyZhang/QuiCommit.git +cd QuiCommit # 安装依赖 cargo fetch @@ -284,11 +457,36 @@ cargo fmt --check ``` src/ ├── commands/ # CLI命令实现 +│ ├── commit.rs +│ ├── tag.rs +│ ├── changelog.rs +│ ├── profile.rs +│ ├── config.rs +│ └── init.rs ├── config/ # 配置管理 +│ ├── manager.rs +│ └── profile.rs ├── generator/ # AI内容生成 ├── git/ # Git操作封装 +│ ├── commit.rs +│ ├── tag.rs +│ └── changelog.rs ├── llm/ # LLM提供商实现 -└── utils/ # 工具函数 +│ ├── ollama.rs +│ ├── openai.rs +│ ├── anthropic.rs +│ ├── kimi.rs +│ ├── deepseek.rs +│ └── openrouter.rs +├── i18n/ # 国际化支持 +│ ├── messages.rs +│ └── translator.rs +├── utils/ # 工具函数 +│ ├── validators.rs +│ ├── formatter.rs +│ ├── crypto.rs +│ └── editor.rs +└── main.rs # 程序入口 ``` ## 许可证 diff --git a/src/commands/changelog.rs b/src/commands/changelog.rs index 66a6eff..c483a0f 100644 --- a/src/commands/changelog.rs +++ b/src/commands/changelog.rs @@ -8,7 +8,7 @@ use std::path::PathBuf; use crate::config::{Language, manager::ConfigManager}; use crate::generator::ContentGenerator; use crate::git::find_repo; -use crate::git::{changelog::*, CommitInfo, GitRepo}; +use crate::git::{changelog::*, CommitInfo}; use crate::i18n::{Messages, translate_changelog_category}; /// Generate changelog @@ -120,7 +120,7 @@ impl ChangelogCommand { // Generate changelog let changelog = if self.generate || (config.changelog.auto_generate && !self.yes) { - self.generate_with_ai(&repo, &version, &commits, &messages).await? + self.generate_with_ai(&version, &commits, &messages).await? } else { self.generate_with_template(format, &version, &commits, language)? }; @@ -173,18 +173,18 @@ impl ChangelogCommand { async fn generate_with_ai( &self, - repo: &GitRepo, version: &str, commits: &[CommitInfo], messages: &Messages, ) -> Result { let manager = ConfigManager::new()?; let config = manager.config(); + let language = manager.get_language().unwrap_or(Language::English); println!("{}", messages.ai_generating_changelog()); let generator = ContentGenerator::new(&config.llm).await?; - generator.generate_changelog_entry(version, commits).await + generator.generate_changelog_entry(version, commits, language).await } fn generate_with_template( diff --git a/src/commands/commit.rs b/src/commands/commit.rs index 2d08036..983ac4b 100644 --- a/src/commands/commit.rs +++ b/src/commands/commit.rs @@ -8,7 +8,7 @@ use crate::config::CommitFormat; use crate::generator::ContentGenerator; use crate::git::{find_repo, GitRepo}; use crate::git::commit::{CommitBuilder, create_date_commit_message}; -use crate::i18n::{Messages, translate_commit_type}; +use crate::i18n::Messages; use crate::utils::validators::get_commit_types; /// Generate and execute conventional commits @@ -114,6 +114,12 @@ impl CommitCommand { println!("{}", messages.auto_stage_changes().yellow()); repo.stage_all()?; println!("{}", messages.staged_all().green()); + + // Re-check status after staging to ensure changes are detected + let new_status = repo.status_summary()?; + if new_status.staged == 0 { + bail!("Failed to stage changes. Please try running 'git add -A' manually."); + } } // Stage all if requested diff --git a/src/commands/tag.rs b/src/commands/tag.rs index eecaf4d..2e55f34 100644 --- a/src/commands/tag.rs +++ b/src/commands/tag.rs @@ -266,6 +266,7 @@ impl TagCommand { async fn generate_tag_message(&self, repo: &GitRepo, version: &str, messages: &Messages) -> Result { let manager = ConfigManager::new()?; let config = manager.config(); + let language = manager.get_language().unwrap_or(Language::English); // Get commits since last tag let tags = repo.get_tags()?; @@ -282,7 +283,7 @@ impl TagCommand { println!("{}", messages.ai_generating_tag(commits.len())); let generator = ContentGenerator::new(&config.llm).await?; - generator.generate_tag_message(version, &commits).await + generator.generate_tag_message(version, &commits, language).await } fn input_message_interactive(&self, version: &str, messages: &Messages) -> Result { diff --git a/src/generator/mod.rs b/src/generator/mod.rs index 999d40d..a637ae4 100644 --- a/src/generator/mod.rs +++ b/src/generator/mod.rs @@ -62,13 +62,14 @@ impl ContentGenerator { &self, version: &str, commits: &[CommitInfo], + language: Language, ) -> Result { let commit_messages: Vec = commits .iter() .map(|c| c.subject().to_string()) .collect(); - self.llm_client.generate_tag_message(version, &commit_messages).await + self.llm_client.generate_tag_message(version, &commit_messages, language).await } /// Generate changelog entry @@ -76,6 +77,7 @@ impl ContentGenerator { &self, version: &str, commits: &[CommitInfo], + language: Language, ) -> Result { let typed_commits: Vec<(String, String)> = commits .iter() @@ -85,7 +87,7 @@ impl ContentGenerator { }) .collect(); - self.llm_client.generate_changelog_entry(version, &typed_commits).await + self.llm_client.generate_changelog_entry(version, &typed_commits, language).await } /// Generate changelog from repository @@ -94,6 +96,7 @@ impl ContentGenerator { repo: &GitRepo, version: &str, from_tag: Option<&str>, + language: Language, ) -> Result { let commits = if let Some(tag) = from_tag { repo.get_commits_between(tag, "HEAD")? @@ -101,7 +104,7 @@ impl ContentGenerator { repo.get_commits(50)? }; - self.generate_changelog_entry(version, &commits).await + self.generate_changelog_entry(version, &commits, language).await } /// Interactive commit generation with user feedback diff --git a/src/git/mod.rs b/src/git/mod.rs index 3a3b261..d409740 100644 --- a/src/git/mod.rs +++ b/src/git/mod.rs @@ -157,28 +157,19 @@ impl GitRepo { /// Get staged diff pub fn get_staged_diff(&self) -> Result { - let head = self.repo.head().ok(); - let head_tree = head.as_ref() - .and_then(|h| h.peel_to_tree().ok()); + // Use git CLI to get staged diff for better compatibility + let output = std::process::Command::new("git") + .args(&["diff", "--cached"]) + .current_dir(&self.path) + .output() + .with_context(|| "Failed to get staged diff with git command")?; - let mut index = self.repo.index()?; - let index_tree = index.write_tree()?; - let index_tree = self.repo.find_tree(index_tree)?; - - let diff = if let Some(head) = head_tree { - self.repo.diff_tree_to_index(Some(&head), Some(&index), None)? - } else { - self.repo.diff_tree_to_index(None, Some(&index), None)? - }; - - let mut diff_text = String::new(); - diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| { - if let Ok(content) = std::str::from_utf8(line.content()) { - diff_text.push_str(content); - } - true - })?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("Failed to get staged diff: {}", stderr); + } + let diff_text = String::from_utf8_lossy(&output.stdout).to_string(); Ok(diff_text) } @@ -277,6 +268,9 @@ impl GitRepo { bail!("Failed to stage changes: {}", stderr); } + // Force refresh the git2 index to pick up changes from git CLI + let _ = self.repo.index()?.write(); + Ok(()) } @@ -562,33 +556,50 @@ impl GitRepo { /// Get repository status summary pub fn status_summary(&self) -> Result { - let statuses = self.repo.statuses(Some(StatusOptions::new().include_untracked(true)))?; + // Use git CLI for more reliable status detection + let output = std::process::Command::new("git") + .args(&["status", "--porcelain"]) + .current_dir(&self.path) + .output() + .with_context(|| "Failed to get status with git command")?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("Failed to get status: {}", stderr); + } + + let stdout = String::from_utf8_lossy(&output.stdout); let mut staged = 0; let mut unstaged = 0; let mut untracked = 0; let mut conflicted = 0; - for entry in statuses.iter() { - let status = entry.status(); + for line in stdout.lines() { + if line.len() >= 2 { + let index_status = line.chars().next().unwrap(); + let worktree_status = line.chars().nth(1).unwrap(); - if status.is_index_new() || status.is_index_modified() || - status.is_index_deleted() || status.is_index_renamed() || - status.is_index_typechange() { - staged += 1; - } + // Staged changes (first column not space) + if index_status != ' ' && index_status != '?' { + staged += 1; + } - if status.is_wt_modified() || status.is_wt_deleted() || - status.is_wt_renamed() || status.is_wt_typechange() { - unstaged += 1; - } + // Unstaged changes (second column not space) + if worktree_status != ' ' && worktree_status != '?' { + unstaged += 1; + } - if status.is_wt_new() { - untracked += 1; - } + // Untracked files (both columns are ?) + if index_status == '?' && worktree_status == '?' { + untracked += 1; + } - if status.is_conflicted() { - conflicted += 1; + // Conflicted files (both columns are U or DD, AA, etc.) + if (index_status == 'U' || worktree_status == 'U') || + (index_status == 'A' && worktree_status == 'A') || + (index_status == 'D' && worktree_status == 'D') { + conflicted += 1; + } } } diff --git a/src/llm/mod.rs b/src/llm/mod.rs index 02426da..0f3dee3 100644 --- a/src/llm/mod.rs +++ b/src/llm/mod.rs @@ -136,8 +136,9 @@ impl LlmClient { &self, version: &str, commits: &[String], + language: Language, ) -> Result { - let system_prompt = TAG_MESSAGE_SYSTEM_PROMPT; + let system_prompt = get_tag_system_prompt(language); let commits_text = commits.join("\n"); let prompt = format!("Version: {}\n\nCommits:\n{}", version, commits_text); @@ -149,8 +150,9 @@ impl LlmClient { &self, version: &str, commits: &[(String, String)], // (type, message) + language: Language, ) -> Result { - let system_prompt = CHANGELOG_SYSTEM_PROMPT; + let system_prompt = get_changelog_system_prompt(language); let commits_text = commits .iter() @@ -385,6 +387,30 @@ fn get_commit_system_prompt(format: crate::config::CommitFormat, language: Langu } } +fn get_tag_system_prompt(language: Language) -> &'static str { + match language { + Language::Chinese => TAG_MESSAGE_SYSTEM_PROMPT_ZH, + Language::Japanese => TAG_MESSAGE_SYSTEM_PROMPT_JA, + Language::Korean => TAG_MESSAGE_SYSTEM_PROMPT_KO, + Language::Spanish => TAG_MESSAGE_SYSTEM_PROMPT_ES, + Language::French => TAG_MESSAGE_SYSTEM_PROMPT_FR, + Language::German => TAG_MESSAGE_SYSTEM_PROMPT_DE, + _ => TAG_MESSAGE_SYSTEM_PROMPT, + } +} + +fn get_changelog_system_prompt(language: Language) -> &'static str { + match language { + Language::Chinese => CHANGELOG_SYSTEM_PROMPT_ZH, + Language::Japanese => CHANGELOG_SYSTEM_PROMPT_JA, + Language::Korean => CHANGELOG_SYSTEM_PROMPT_KO, + Language::Spanish => CHANGELOG_SYSTEM_PROMPT_ES, + Language::French => CHANGELOG_SYSTEM_PROMPT_FR, + Language::German => CHANGELOG_SYSTEM_PROMPT_DE, + _ => CHANGELOG_SYSTEM_PROMPT, + } +} + // System prompts for LLM const CONVENTIONAL_COMMIT_SYSTEM_PROMPT: &str = r#"You are a helpful assistant that generates conventional commit messages.