Skip to content
Clone
02d8172
343 lines 11.1 KB Rust
use crate::rpcgit::diff::types::{
ChangedPath, ChangedPathDiff, ChangedPathRequest, DiffOptions, DiffRequest, NumStat,
RangeDiffSpec, ShortStat,
};
use crate::rpcgit::oid::ObjectId;
use serde::{Deserialize, Serialize};
use crate::context::facade::GitContext;
use crate::service::GitService;
use crate::service::cache::{self, IMMUTABLE_TTL};
use crate::share::errors::{GitError, map_baby};
pub fn parse_oid(hex: &str) -> Result<ObjectId, GitError> {
ObjectId::from_hex(hex.trim().as_bytes())
.map_err(|_| GitError::InvalidArgument(format!("invalid object id `{hex}`")))
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct DiffOptionsDto {
#[serde(default)]
pub paths: Option<Vec<std::path::PathBuf>>,
#[serde(default)]
pub detect_renames: Option<u8>,
}
impl From<DiffOptionsDto> for DiffOptions {
fn from(d: DiffOptionsDto) -> Self {
Self {
paths: d.paths,
detect_renames: d.detect_renames,
..Default::default()
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ChangedPathDto {
pub old_path: Option<String>,
pub new_path: String,
pub old_mode: u32,
pub new_mode: u32,
pub old_oid: Option<String>,
pub new_oid: Option<String>,
pub status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ChangedPathDiffDto {
pub path: ChangedPathDto,
pub lines_added: u64,
pub lines_removed: u64,
pub binary: bool,
pub patch: Option<String>,
}
impl From<ChangedPathDiff> for ChangedPathDiffDto {
fn from(c: ChangedPathDiff) -> Self {
let patch = c.patch.and_then(|p| {
if c.binary {
None
} else {
Some(String::from_utf8_lossy(&p).into_owned())
}
});
Self {
path: ChangedPathDto {
old_path: c.path.old_path.map(|p| p.to_string_lossy().into_owned()),
new_path: c.path.new_path.to_string_lossy().into_owned(),
old_mode: c.path.old_mode,
new_mode: c.path.new_mode,
old_oid: c.path.old_oid.map(|o| o.to_string()),
new_oid: c.path.new_oid.map(|o| o.to_string()),
status: format!("{:?}", c.path.status),
},
lines_added: c.lines_added,
lines_removed: c.lines_removed,
binary: c.binary,
patch,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct DiffTreeRecordDto {
pub status: String,
pub score: u8,
pub head_path: Option<String>,
pub base_path: Option<String>,
pub head_mode: Option<u32>,
pub base_mode: Option<u32>,
pub head_blob_id: Option<String>,
pub base_blob_id: Option<String>,
}
impl GitService {
async fn diff_request(
&self,
ctx: &crate::context::facade::GitContext,
base: &str,
head: &str,
) -> Result<DiffRequest, GitError> {
let baby = self.baby(ctx);
let empty_tree = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
let left = if base.trim() == empty_tree {
ObjectId::from_hex(empty_tree.as_bytes())
.map_err(|_| GitError::InvalidArgument("empty tree oid".into()))?
} else {
baby.rev_parse(base).await.map_err(map_baby)?
};
let right = if head.trim() == empty_tree {
ObjectId::from_hex(empty_tree.as_bytes())
.map_err(|_| GitError::InvalidArgument("empty tree oid".into()))?
} else {
baby.rev_parse(head).await.map_err(map_baby)?
};
Ok(DiffRequest { left, right })
}
pub async fn diff_raw(
&self,
ctx: &GitContext,
base: String,
head: String,
) -> Result<String, GitError> {
let req = self.diff_request(&ctx, &base, &head).await?;
let key = format!("repo.{}.diff_raw.{}.{}", ctx.model.id, req.left, req.right);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
self.baby(&ctx).raw_diff(&req, None).await.map_err(map_baby)
})
.await
}
pub async fn diff_files_changed_between(
&self,
ctx: &GitContext,
base: String,
head: String,
) -> Result<Vec<String>, GitError> {
let baby = self.baby(&ctx);
let left = baby.rev_parse(&base).await.map_err(map_baby)?;
let right = baby.rev_parse(&head).await.map_err(map_baby)?;
let key = format!(
"repo.{}.diff_changed_paths.{}.{}",
ctx.model.id, left, right
);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
let paths = baby
.find_changed_paths(
vec![ChangedPathRequest::TreePair(left, right)],
None,
None,
false,
)
.await
.map_err(map_baby)?;
Ok(paths
.into_iter()
.map(|p| p.new_path.to_string_lossy().into_owned())
.collect())
})
.await
}
pub async fn diff_short_stat(
&self,
ctx: &GitContext,
base: String,
head: String,
) -> Result<ShortStat, GitError> {
let req = self.diff_request(&ctx, &base, &head).await?;
let key = format!(
"repo.{}.diff_short_stat.{}.{}",
ctx.model.id, req.left, req.right
);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
self.baby(&ctx)
.shortstat(&req, &DiffOptions::default())
.await
.map_err(map_baby)
})
.await
}
pub async fn diff_stats(
&self,
ctx: &GitContext,
base: String,
head: String,
opts: DiffOptionsDto,
) -> Result<Vec<NumStat>, GitError> {
let req = self.diff_request(&ctx, &base, &head).await?;
let key = format!(
"repo.{}.diff_stats.{}.{}:{:?}",
ctx.model.id, req.left, req.right, opts.paths
);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
self.baby(&ctx)
.diff_stats(&req, &opts.into())
.await
.map_err(map_baby)
})
.await
}
pub async fn diff_files(
&self,
ctx: &GitContext,
base: String,
head: String,
opts: DiffOptionsDto,
) -> Result<Vec<ChangedPathDiffDto>, GitError> {
let req = self.diff_request(&ctx, &base, &head).await?;
let key = format!(
"repo.{}.diff_files_dto.{}.{}:{:?}",
ctx.model.id, req.left, req.right, opts.paths
);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
let list = self
.baby(&ctx)
.commit_diff(&req, &opts.into(), None)
.await
.map_err(map_baby)?
.into_iter()
.map(ChangedPathDiffDto::from)
.collect::<Vec<_>>();
Ok(list)
})
.await
}
pub async fn commit_diff_for_html(
&self,
ctx: &std::sync::Arc<crate::context::facade::GitContext>,
commit: &str,
) -> Result<(String, Vec<ChangedPathDiffDto>), GitError> {
const EMPTY_TREE: &str = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
let baby = self.baby(ctx);
let head_oid = baby.rev_parse(commit).await.map_err(map_baby)?;
let info = baby
.get_commit(&head_oid.to_string())
.await
.map_err(map_baby)?;
let base = info
.parents
.first()
.map(|p| p.to_string())
.unwrap_or_else(|| EMPTY_TREE.to_string());
let req = self.diff_request(ctx, &base, &head_oid.to_string()).await?;
let key = format!(
"repo.{}.diff_files_dto.{}.{:?}",
ctx.model.id, req.left, None::<Vec<std::path::PathBuf>>
);
let files = cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
let list = baby
.commit_diff(&req, &DiffOptions::default(), None)
.await
.map_err(map_baby)?
.into_iter()
.map(ChangedPathDiffDto::from)
.collect::<Vec<_>>();
Ok(list)
})
.await?;
Ok((base, files))
}
pub async fn diff_for_commit(
&self,
ctx: &GitContext,
commit: String,
) -> Result<Vec<ChangedPath>, GitError> {
let oid = parse_oid(&commit)?;
let key = format!("repo.{}.diff_for_commit.{}", ctx.model.id, oid);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
self.baby(&ctx)
.find_changed_paths(vec![ChangedPathRequest::Commit(oid)], None, None, false)
.await
.map_err(map_baby)
})
.await
}
pub async fn diff_tree(
&self,
ctx: &GitContext,
base: String,
head: String,
use_merge_base: bool,
) -> Result<Vec<DiffTreeRecordDto>, GitError> {
let baby = self.baby(&ctx);
let left = if use_merge_base {
let mb = baby.merge_base(&base, &head).await.map_err(map_baby)?;
mb.to_string()
} else {
baby.rev_parse(&base).await.map_err(map_baby)?.to_string()
};
let right = baby.rev_parse(&head).await.map_err(map_baby)?.to_string();
let key = format!(
"repo.{}.diff_tree.{}.{}:{}",
ctx.model.id, left, right, use_merge_base
);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
let rows = baby
.diff_trees(
&left,
&right,
std::path::Path::new(""),
crate::rpcgit::tree::types::DiffTreesOptions {
recurse: true,
detect_renames: true,
..Default::default()
},
)
.await
.map_err(map_baby)?;
Ok(rows
.into_iter()
.map(|d| DiffTreeRecordDto {
status: format!("{:?}", d.status).to_lowercase(),
score: 0,
head_path: d.new_path.map(|p| p.to_string_lossy().into_owned()),
base_path: d.old_path.map(|p| p.to_string_lossy().into_owned()),
head_mode: d.new_mode.map(|m| m as u32),
base_mode: d.old_mode.map(|m| m as u32),
head_blob_id: d.new_oid.map(|o| o.to_string()),
base_blob_id: d.old_oid.map(|o| o.to_string()),
})
.collect())
})
.await
}
pub async fn range_diff(
&self,
ctx: &GitContext,
spec: RangeDiffSpec,
) -> Result<String, GitError> {
self.baby(&ctx)
.range_diff(&spec, &DiffOptions::default(), None)
.await
.map_err(map_baby)
}
}