Skip to main content

git_commit_helper_cli/
gerrit.rs

1use anyhow::Result;
2use reqwest::Client;
3use log::debug;
4use base64::Engine;
5use serde::Deserialize;
6use std::collections::HashMap;
7
8#[derive(Debug, Deserialize)]
9struct CommitInfo {
10    message: String,
11}
12
13#[derive(Debug, Deserialize)]
14struct RevisionInfo {
15    #[serde(rename = "commit")]
16    commit_info: CommitInfo,
17}
18
19#[derive(Debug, Deserialize)]
20struct ChangeInfo {
21    subject: String,
22    revisions: HashMap<String, RevisionInfo>,
23    #[serde(rename = "current_revision")]
24    current_revision: String,
25}
26
27use crate::terminal_format::print_progress;
28
29pub async fn get_change_info(url: &str) -> Result<String> {
30    debug!("开始获取 Gerrit 改动信息: {}", url);
31
32    // 解析 Gerrit URL
33    // 示例: https://gerrit.uniontech.com/c/udcp/udcp-uim/+/179042
34    let parts: Vec<&str> = url.split("/+/").collect();
35    if parts.len() != 2 {
36        return Err(anyhow::anyhow!("无效的 Gerrit URL"));
37    }
38
39    // 从 URL 中提取项目名和改动 ID
40    let parts: Vec<&str> = url.split("/c/").collect();
41    if parts.len() != 2 {
42        return Err(anyhow::anyhow!("无效的 Gerrit URL"));
43    }
44
45    let base_url = parts[0];
46    let mut path_parts = parts[1].split("/+/");
47
48    let _project = path_parts.next()
49        .ok_or_else(|| anyhow::anyhow!("无法解析项目路径"))?
50        .trim_end_matches('/');
51
52    let change_id = path_parts.next()
53        .ok_or_else(|| anyhow::anyhow!("无法解析改动ID"))?;
54
55    // 构建 API URL,添加选项以获取完整的 commit 信息
56    let api_url = format!(
57        "{}/a/changes/{}?o=CURRENT_REVISION&o=CURRENT_COMMIT",
58        base_url,
59        change_id
60    );
61
62    // 进度提示
63    print_progress("正在请求 gerrit 获取改动信息", None);
64
65    // 创建 HTTP 客户端并发送请求
66    let mut request = Client::new()
67        .get(&api_url)
68        .header("Accept", "application/json");
69
70    // 添加认证信息
71    request = add_auth(request);
72
73    let response = request.send().await?;
74
75    print_progress("正在请求 gerrit 获取改动信息", Some(100));
76
77    if !response.status().is_success() {
78        return Err(anyhow::anyhow!(
79            "获取 Gerrit 改动信息失败: HTTP {}",
80            response.status()
81        ));
82    }
83
84    let json_text = response.text().await?;
85    // Gerrit API 返回的 JSON 数据前面会有一个防止 XSS 的 )]}'
86    let json = json_text.trim_start_matches(")]}'\n");
87
88    let info: ChangeInfo = serde_json::from_str(json)?;
89    let current_revision = info.revisions.get(&info.current_revision)
90        .ok_or_else(|| anyhow::anyhow!("未找到当前版本信息"))?;
91
92    let mut result = format!("标题:{}", info.subject);
93    let message = current_revision.commit_info.message.lines()
94        .skip(1)  // 跳过第一行(标题)
95        .collect::<Vec<&str>>()
96        .join("\n")
97        .trim()
98        .to_string();
99
100    if !message.is_empty() {
101        result.push_str(&format!("\n\n描述:\n{}", message));
102    }
103    Ok(result)
104}
105
106pub async fn get_change_diff(url: &str) -> Result<String> {
107    debug!("开始获取 Gerrit 改动内容: {}", url);
108
109    // 解析 Gerrit URL
110    // 示例: https://gerrit.uniontech.com/c/udcp/udcp-uim/+/179042
111    let parts: Vec<&str> = url.split("/+/").collect();
112    if parts.len() != 2 {
113        return Err(anyhow::anyhow!("无效的 Gerrit URL"));
114    }
115
116    // 从 URL 中提取项目名和改动 ID
117    let parts: Vec<&str> = url.split("/c/").collect();
118    if parts.len() != 2 {
119        return Err(anyhow::anyhow!("无效的 Gerrit URL"));
120    }
121
122    let base_url = parts[0];
123    let mut path_parts = parts[1].split("/+/");
124
125    let project = path_parts.next()
126        .ok_or_else(|| anyhow::anyhow!("无法解析项目路径"))?
127        .trim_end_matches('/');
128
129    let change_id = path_parts.next()
130        .ok_or_else(|| anyhow::anyhow!("无法解析改动ID"))?;
131
132    // 构造 API URL
133    // Gerrit API 文档: https://gerrit-review.googlesource.com/Documentation/rest-api.html
134    let api_url = format!(
135        "{}/a/changes/{}~{}/revisions/current/patch",  // 添加 /a/ 表示需要认证的 API
136        base_url,
137        project.replace("/", "%2F"),
138        change_id
139    );
140
141    debug!("Gerrit API URL: {}", api_url);
142
143    // 进度提示
144    print_progress("正在请求 gerrit 获取改动内容", None);
145
146    // 创建 HTTP 客户端并准备请求
147    let mut request = Client::new()
148        .get(&api_url)
149        .header("Accept", "text/plain");
150
151    request = add_auth(request);
152
153    let response = request.send().await?;
154
155    print_progress("正在请求 gerrit 获取改动内容", Some(100));
156
157    if !response.status().is_success() {
158        return Err(anyhow::anyhow!(
159            "获取 Gerrit 改动失败: HTTP {}",
160            response.status()
161        ));
162    }
163
164    let encoded_diff = response.text().await?;
165    if encoded_diff.trim().is_empty() {
166        return Err(anyhow::anyhow!("未发现任何代码改动"));
167    }
168
169    // base64 解码 patch 内容
170    let diff = match base64::engine::general_purpose::STANDARD.decode(encoded_diff.trim()) {
171        Ok(bytes) => String::from_utf8_lossy(&bytes).into_owned(),
172        Err(e) => return Err(anyhow::anyhow!("解析 patch 内容失败: {}", e)),
173    };
174
175    debug!("获取到的 Gerrit patch 内容:\n{}", diff);
176
177    Ok(diff)
178}
179
180// 添加认证信息到请求
181fn add_auth(mut request: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
182    // 优先使用配置文件中的认证信息
183    let mut auth_added = false;
184    if let Ok(config) = crate::config::Config::load() {
185        if let Some(gerrit_config) = config.gerrit {
186            if let Some(token) = gerrit_config.token {
187                debug!("使用配置文件中的 Token 认证");
188                request = request.bearer_auth(&token);
189                auth_added = true;
190            } else if let Some(username) = &gerrit_config.username {
191                if let Some(password) = &gerrit_config.password {
192                    debug!("使用配置文件中的用户名密码认证: {}", username);
193                    request = request.basic_auth(username, Some(password));
194                    auth_added = true;
195                }
196            }
197        }
198    }
199
200    // 如果配置文件中没有认证信息,尝试使用环境变量
201    if !auth_added {
202        if let Ok(username) = std::env::var("GERRIT_USERNAME") {
203            if let Ok(password) = std::env::var("GERRIT_PASSWORD") {
204                debug!("使用环境变量中的用户名认证: {}", username);
205                request = request.basic_auth(&username, Some(&password));
206                auth_added = true;
207            }
208        } else if let Ok(token) = std::env::var("GERRIT_TOKEN") {
209            debug!("使用环境变量中的 Token 认证");
210            request = request.bearer_auth(&token);
211            auth_added = true;
212        }
213    }
214
215    if !auth_added {
216        debug!("未找到任何认证信息,尝试匿名访问");
217    }
218
219    request
220}