Skip to content
Clone
971c37c
144 lines 4.5 KB Rust
use git::context::facade::GitContext;
/// 组装好的模型上下文:提示词正文 + 是否发生截断。
#[derive(Debug, Clone)]
pub struct BuiltContext {
pub text: String,
pub truncated: bool,
}
const EMPTY_TREE: &str = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
impl super::AgentService {
/// PR 评审上下文:merge-base..head 的三点差异。
pub async fn build_pull_diff(
&self,
space_name: &str,
repo_name: &str,
head_sha: &str,
base_ref: &str,
) -> Result<(BuiltContext, String), git::share::errors::GitError> {
let ctx = self
.git
.context(space_name.to_string(), repo_name.to_string())
.await?;
let plan = self
.git
.merge_plan(&ctx, &format!("refs/heads/{base_ref}"), head_sha)
.await?;
let from = plan
.merge_base
.clone()
.unwrap_or_else(|| plan.base_sha.clone());
let diff = self
.git
.diff_raw(
space_name.to_string(),
repo_name.to_string(),
from.clone(),
plan.head_sha.clone(),
)
.await?;
let label = format!("{}..{}", short(&from), short(&plan.head_sha));
Ok((self.truncate_diff(diff), label))
}
/// Commit 评审上下文:父提交..该提交的差异。
pub async fn build_commit_diff(
&self,
space_name: &str,
repo_name: &str,
commit_sha: &str,
) -> Result<(BuiltContext, String), git::share::errors::GitError> {
let ctx = self
.git
.context(space_name.to_string(), repo_name.to_string())
.await?;
let baby = self.git.baby(&ctx);
let info = baby
.get_commit(commit_sha)
.await
.map_err(git::share::errors::map_baby)?;
let parent = info
.parents
.first()
.map(|p| p.to_string())
.unwrap_or_else(|| EMPTY_TREE.to_string());
let sha = info.id.to_string();
let diff = self
.git
.diff_raw(
space_name.to_string(),
repo_name.to_string(),
parent.clone(),
sha.clone(),
)
.await?;
let label = format!("{}..{}", short(&parent), short(&sha));
Ok((self.truncate_diff(diff), label))
}
/// 提交列表(评审用不到,但总结/release 复用同一格式化)。
pub(crate) fn format_commits(commits: &[git::service::merge::MergeCommitDto]) -> String {
let mut out = String::new();
for commit in commits {
out.push_str(&format!(
"- {} {} ({}, {})\n",
short(&commit.sha),
commit.subject.trim(),
commit.author_name,
commit.author_at
));
}
out
}
/// 按字节预算截断差异:超限时逐文件裁剪并标记 truncated。
pub(crate) fn truncate_diff(&self, diff: String) -> BuiltContext {
let limit = self.config().ai.context_max_bytes;
if diff.len() <= limit {
return BuiltContext {
text: diff,
truncated: false,
};
}
let mut text = String::with_capacity(limit);
let mut truncated = false;
for file in split_diff_files(&diff) {
if text.len() + file.len() > limit {
truncated = true;
break;
}
text.push_str(&file);
}
if text.is_empty() {
// 单个文件本身就超限:按字符边界硬截断。
text = diff.chars().take(limit).collect();
truncated = true;
}
BuiltContext { text, truncated }
}
}
/// 按 `diff --git` 切分,保留每段完整补丁文本。
pub fn split_diff_files(diff: &str) -> Vec<String> {
let mut files: Vec<String> = Vec::new();
let mut current = String::new();
for line in diff.split_inclusive('\n') {
if line.starts_with("diff --git ") && !current.is_empty() {
files.push(std::mem::take(&mut current));
}
current.push_str(line);
}
if !current.is_empty() {
files.push(current);
}
files
}
pub(crate) fn short(sha: &str) -> String {
sha.chars().take(8).collect()
}
/// 让 `GitContext` 在本 crate 内可用(避免调用方各自引入 git crate)。
pub type RepoContext = GitContext;