Skip to main content

git_commit_helper_cli/
review.rs

1use anyhow::Result;
2use std::process::Command;
3use crate::config::Config;
4use crate::ai_service;
5use crate::github;
6use crate::gerrit;
7use log::{debug, info};
8use crate::terminal_format::Style;
9
10pub async fn review_remote_changes(config: &Config, url: &str) -> Result<String> {
11    debug!("开始审查远程代码改动: {}", url);
12
13    // 获取改动信息和 diff 内容
14    let (change_message, diff) = if url.contains("github.com") {
15        if url.contains("/pull/") {
16            let pr_info = github::get_pr_info(url).await?;
17            let diff = github::get_pr_diff(url).await?;
18            (pr_info, diff)
19        } else if url.contains("/commit/") {
20            let msg = github::get_commit_info(url).await?;
21            let diff = github::get_commit_diff(url).await?;
22            (msg, diff)
23        } else {
24            return Err(anyhow::anyhow!("无效的GitHub URL,必须是PR或commit链接"));
25        }
26    } else if url.contains("/+/") {
27        // Gerrit 链接
28        let msg = gerrit::get_change_info(url).await?;
29        let diff = gerrit::get_change_diff(url).await?;
30        (msg, diff)
31    } else {
32        return Err(anyhow::anyhow!("无效的URL,必须是GitHub或Gerrit链接"));
33    };
34
35    if diff.trim().is_empty() {
36        return Err(anyhow::anyhow!("未发现任何代码改动"));
37    }
38
39    // 翻译改动信息(如果存在且包含英文)
40    let mut review = String::new();
41    if !change_message.is_empty() {
42        // 分离标题和描述
43        let mut parts = change_message.splitn(2, "\n描述:\n");
44        let title = parts.next().unwrap().trim_start_matches("标题:").trim();
45        let description = parts.next();
46
47        let mut info = String::new();
48
49        // 处理标题
50        let title_info = if title.chars().any(|c| c.is_ascii_alphabetic()) {
51            // 如果标题包含英文字符
52            let translator = ai_service::create_translator(config).await?;
53            let prompt = format!("请将以下 PR 标题翻译成中文:\n\n{}", title);
54            let chinese = translator.chat("你是一个代码提交信息翻译助手。", &prompt).await?;
55            format!("标题:{}\n中文翻译:{}\n", title, chinese)
56        } else {
57            format!("标题:{}\n", title)
58        };
59        info.push_str(&title_info);
60
61        // 处理描述(如果存在)
62        if let Some(desc) = description {
63            if !desc.trim().is_empty() {
64                if desc.chars().any(|c| c.is_ascii_alphabetic()) {
65                    // 如果描述包含英文字符
66                    let translator = ai_service::create_translator(config).await?;
67                    let prompt = format!("请将以下 PR 描述翻译成中文:\n\n{}", desc);
68                    let chinese = translator.chat("你是一个代码提交信息翻译助手。", &prompt).await?;
69                    info.push_str(&format!("\n描述:\n{}\n中文翻译:\n{}\n", desc, chinese));
70                } else {
71                    info.push_str(&format!("\n描述:\n{}\n", desc));
72                }
73            }
74        }
75
76        review.push_str(&info);
77        review.push('\n');
78    }
79
80    // 代码审查
81    let translator = ai_service::create_translator(config).await?;
82    info!("正在使用 {:?} 服务进行代码审查...", config.default_service);
83
84    let system_prompt = get_review_prompt();
85    let review_result = translator.chat(&system_prompt, &diff).await?;
86    review.push_str(&review_result);
87
88    // 终端格式化输出
89    let review = format_review_for_terminal(&review);
90    Ok(review)
91}
92
93// 终端格式化review内容
94fn format_review_for_terminal(input: &str) -> String {
95    let mut out = String::new();
96    let mut in_report = false;
97    for line in input.lines() {
98        if line.trim().is_empty() {
99            out.push('\n');
100            continue;
101        }
102        if line.starts_with("标题:") {
103            out.push_str(&Style::separator());
104            out.push_str(&Style::title(line));
105        } else if line.starts_with("中文翻译:") {
106            out.push_str(&Style::green(line));
107        } else if line.starts_with("描述:") {
108            out.push_str(&Style::blue(line));
109        } else if line.starts_with("代码审查报告:") {
110            out.push_str(&Style::separator());
111            out.push_str(&Style::yellow(line));
112            in_report = true;
113        } else if line.starts_with("警告") {
114            out.push_str(&Style::yellow(line));
115        } else if line.starts_with("错误") {
116            out.push_str(&Style::red(line));
117        } else if in_report {
118            out.push_str(&Style::plain(line));
119        } else {
120            out.push_str(&Style::plain(line));
121        }
122    }
123    out.push_str(&Style::separator());
124    out
125}
126
127pub async fn review_changes(config: &Config, no_review: bool) -> Result<Option<String>> {
128    // 如果命令行指定了 --no-review 或配置文件中禁用了 ai_review,则跳过审查
129    if no_review {
130        info!("已通过 --no-review 参数禁用代码审查");
131        return Ok(None);
132    }
133
134    if !config.ai_review {
135        info!("AI代码审查功能已在配置中禁用,可以使用 git-commit-helper ai-review --enable 启用");
136        return Ok(None);
137    }
138
139    // 获取当前改动的差异
140    let diff = get_staged_changes()?;
141    if diff.trim().is_empty() {
142        info!("没有检测到暂存的代码改动");
143        return Ok(None);
144    }
145
146    // 使用配置的 AI 服务进行代码审查
147    let translator = ai_service::create_translator(config).await?;
148    info!("正在使用 {:?} 服务进行代码审查...", config.default_service);
149
150    let system_prompt = get_review_prompt();
151    let review = translator.chat(&system_prompt, &diff).await?;
152
153    Ok(Some(review))
154}
155
156// 构建代码审查提示语
157fn get_review_prompt() -> String {
158    // 获取配置文件路径
159    let prompt_path = crate::config::Config::config_path()
160        .expect("无法获取配置目录")
161        .parent()
162        .expect("无法获取父目录")
163        .join("review_prompt.txt");
164
165    // 如果文件存在则读取,否则使用默认提示语
166    if prompt_path.exists() {
167        info!("正在使用 {:?} 提示词文件, 进行代码审查...", prompt_path.display());
168        std::fs::read_to_string(&prompt_path).unwrap_or_else(|err| {
169            log::error!("Failed to read review prompt file {:?}: {}", prompt_path.display(), err);
170            DEFAULT_REVIEW_PROMPT.to_string()
171        })
172    } else {
173        DEFAULT_REVIEW_PROMPT.to_string()
174    }
175}
176
177const DEFAULT_REVIEW_PROMPT:&str = r#"您是一位专业的代码审查者,请对以下代码变更进行审查并给出中文评价。请着重关注:
178
1791. 代码质量:
180   - 代码是否清晰易懂
181   - 变量和函数命名是否恰当
182   - 代码结构是否合理
183
1842. 潜在问题:
185   - 可能的bug
186   - 边界条件处理
187   - 异常情况的处理
188   - 资源使用和释放
189
1903. 最佳实践:
191   - 是否符合编程规范
192   - 是否遵循设计模式
193   - 代码重用性
194   - 模块化和解耦
195
1964. 性能考虑:
197   - 算法效率
198   - 资源使用效率
199   - 可能的性能瓶颈
200
2015. 安全性:
202   - 输入验证
203   - 数据安全
204   - 权限检查
205
206请以"代码审查报告:"开头,使用简洁的语言描述发现的问题和改进建议。如果代码符合最佳实践,也请给出正面的评价。
207"#;
208
209fn get_staged_changes() -> Result<String> {
210    let output = Command::new("git")
211        .args(&["diff", "--cached"])
212        .output()?;
213
214    if !output.status.success() {
215        return Err(anyhow::anyhow!("获取暂存区改动失败"));
216    }
217
218    Ok(String::from_utf8(output.stdout)?)
219}
220
221pub fn should_skip_review(message: &str) -> bool {
222    message.starts_with("Merge") ||
223    message.starts_with("Cherry-pick") ||
224    message.starts_with("Revert")
225}
226
227pub async fn review_local_commit(config: &Config, commit_id: &str) -> Result<String> {
228    debug!("开始审查本地commit: {}", commit_id);
229
230    // 获取diff内容
231    let diff = get_commit_diff(commit_id)?;
232
233    if diff.trim().is_empty() {
234        return Err(anyhow::anyhow!("未发现任何代码改动"));
235    }
236
237    // 代码审查
238    info!("正在使用 {:?} 服务进行代码审查...", config.default_service);
239    let mut review = String::new();
240    let translator = ai_service::create_translator(config).await?;
241    let system_prompt = get_review_prompt();
242    let review_result = translator.chat(&system_prompt, &diff).await?;
243    review.push_str(&review_result);
244
245    Ok(review)
246}
247
248fn get_commit_diff(commit_id: &str) -> Result<String> {
249    let output = Command::new("git")
250        .args(&["show", "--pretty=format:", commit_id])
251        .output()?;
252
253    if !output.status.success() {
254        return Err(anyhow::anyhow!("获取commit差异失败"));
255    }
256
257    Ok(String::from_utf8(output.stdout)?)
258}