feat(gitea+mcp): PR 状态轮询 + poll_now agent 按钮(公网 Gitea webhook 不可达降级方案)
This commit is contained in:
parent
97b116857f
commit
6d10c71d15
@ -535,9 +535,85 @@ pub fn poll_all_repos() -> Result<(usize, usize), String> {
|
|||||||
crate::db::log_event("gitea_poll", "info",
|
crate::db::log_event("gitea_poll", "info",
|
||||||
&format!("轮询 {polled} 个仓库,新采集 {ingested} 条 commit"), None);
|
&format!("轮询 {polled} 个仓库,新采集 {ingested} 条 commit"), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PR 状态轮询:弥补公网 Gitea + 内网炼境时 webhook 不可达的场景
|
||||||
|
let mut pr_ingested = 0usize;
|
||||||
|
for (project_id, owner, repo, base_url, token) in &repos {
|
||||||
|
pr_ingested += poll_pr_events(&conn, project_id, owner, repo, base_url, token);
|
||||||
|
}
|
||||||
|
if pr_ingested > 0 {
|
||||||
|
crate::db::log_event("gitea_poll", "info",
|
||||||
|
&format!("PR 轮询新采集 {pr_ingested} 条事件"), None);
|
||||||
|
}
|
||||||
|
|
||||||
Ok((polled, ingested))
|
Ok((polled, ingested))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 轮询单个仓库的 PR 状态,将未记录的事件写入 pr_events,返回新增条数
|
||||||
|
fn poll_pr_events(
|
||||||
|
conn: &rusqlite::Connection,
|
||||||
|
project_id: &str,
|
||||||
|
owner: &str,
|
||||||
|
repo: &str,
|
||||||
|
base_url: &str,
|
||||||
|
token: &str,
|
||||||
|
) -> usize {
|
||||||
|
let mut count = 0;
|
||||||
|
for state in ["open", "closed"] {
|
||||||
|
let url = format!(
|
||||||
|
"{base_url}/api/v1/repos/{owner}/{repo}/pulls?state={state}&limit=50&type=pullrequest"
|
||||||
|
);
|
||||||
|
let resp = match ureq::get(&url)
|
||||||
|
.set("Authorization", &format!("token {token}"))
|
||||||
|
.call()
|
||||||
|
{
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
let prs: serde_json::Value = match resp.into_json() {
|
||||||
|
Ok(j) => j,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
let prs = match prs.as_array() {
|
||||||
|
Some(a) => a,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
for pr in prs {
|
||||||
|
let pr_number = match pr["number"].as_i64() {
|
||||||
|
Some(n) => n,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
let title = pr["title"].as_str().unwrap_or("").to_string();
|
||||||
|
let head_branch = pr["head"]["ref"].as_str().unwrap_or("").to_string();
|
||||||
|
let base_branch = pr["base"]["ref"].as_str().unwrap_or("").to_string();
|
||||||
|
let author = pr["user"]["login"].as_str().unwrap_or("").to_string();
|
||||||
|
let merged = pr["merged"].as_bool().unwrap_or(false);
|
||||||
|
let pr_state = pr["state"].as_str().unwrap_or("open");
|
||||||
|
|
||||||
|
// 确定动作:已合并 > 关闭 > 开启
|
||||||
|
let action = if merged { "merged" } else if pr_state == "closed" { "closed" } else { "opened" };
|
||||||
|
|
||||||
|
let existing: i64 = conn.query_row(
|
||||||
|
"SELECT COUNT(*) FROM pr_events WHERE project_id = ?1 AND pr_number = ?2 AND action = ?3",
|
||||||
|
params![project_id, pr_number, action],
|
||||||
|
|r| r.get(0),
|
||||||
|
).unwrap_or(0);
|
||||||
|
|
||||||
|
if existing == 0 {
|
||||||
|
let id = uuid::Uuid::new_v4().to_string();
|
||||||
|
if conn.execute(
|
||||||
|
"INSERT INTO pr_events (id, project_id, pr_number, title, action, head_branch, base_branch, author)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
||||||
|
params![id, project_id, pr_number, title, action, head_branch, base_branch, author],
|
||||||
|
).is_ok() {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
/// 手动触发一次轮询(UI「立即拉取」按钮)
|
/// 手动触发一次轮询(UI「立即拉取」按钮)
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn gitea_poll_now() -> Result<serde_json::Value, String> {
|
pub fn gitea_poll_now() -> Result<serde_json::Value, String> {
|
||||||
|
|||||||
@ -540,6 +540,15 @@ fn tools_list_result() -> Value {
|
|||||||
"required": ["project_id"]
|
"required": ["project_id"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "poll_now",
|
||||||
|
"description": "立即触发一次 Gitea 轮询:拉取所有绑定仓库的最新 commit 和 PR 状态写入飞轮。等价于炼境 UI「立即拉取」按钮。适用于公网 Gitea + 内网炼境(webhook 不可达)场景。",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {},
|
||||||
|
"required": []
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "setup_webhook",
|
"name": "setup_webhook",
|
||||||
"description": "为项目重新注册 Gitea webhook(订阅 push + pull_request 事件)。等价于炼境 UI「重设」按钮。导入新项目后、或需要更新事件订阅时调用。",
|
"description": "为项目重新注册 Gitea webhook(订阅 push + pull_request 事件)。等价于炼境 UI「重设」按钮。导入新项目后、或需要更新事件订阅时调用。",
|
||||||
@ -651,6 +660,10 @@ async fn tools_call(group_id: &str, params: Option<&Value>) -> Value {
|
|||||||
let body = args.get("body").and_then(|v| v.as_str()).unwrap_or("");
|
let body = args.get("body").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
crate::commands::gitea::gitea_api_create_pull_request(pid, head, base, title, body)
|
crate::commands::gitea::gitea_api_create_pull_request(pid, head, base, title, body)
|
||||||
}
|
}
|
||||||
|
"poll_now" => {
|
||||||
|
crate::commands::gitea::poll_all_repos()
|
||||||
|
.map(|(polled, ingested)| format!("轮询完成:{polled} 个仓库,新 commit {ingested} 条,PR 事件已同步"))
|
||||||
|
}
|
||||||
"setup_webhook" => {
|
"setup_webhook" => {
|
||||||
let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or("");
|
let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
let port = read_port();
|
let port = read_port();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user