1098df8
ZhenYi
refactor: streamline context handling by replacing space and repo name parameters with context references for improved clarity and consistency
1098df8
use crate::rpcgit::conflict::types::{ConflictFile, ListConflictOptions};
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};
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MergeStatusDto {
pub mergeable: bool,
pub conflicts: Vec<String>,
pub fast_forwardable: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ConflictDetailDto {
pub path: String,
pub content: Option<String>,
}
impl GitService {
async fn resolve_pair(
&self,
ctx: &crate::context::facade::GitContext,
ours: &str,
theirs: &str,
) -> Result<(String, String), GitError> {
let baby = self.baby(ctx);
let our_oid = baby.rev_parse(ours).await.map_err(map_baby)?;
let their_oid = baby.rev_parse(theirs).await.map_err(map_baby)?;
Ok((our_oid.to_string(), their_oid.to_string()))
}
pub async fn conflict_check(
&self,
ctx: &GitContext,
base: String,
head: String,
) -> Result<MergeStatusDto, GitError> {
let (our_oid, their_oid) = self.resolve_pair(&ctx, &base, &head).await?;
let key = format!(
"repo.{}.conflict_check.{}.{}",
ctx.model.id, our_oid, their_oid
);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
let baby = self.baby(&ctx);
let fast_forwardable = baby.is_ancestor(&base, &head).await.map_err(map_baby)?;
let files = baby
.list_conflict_files(
&our_oid,
&their_oid,
&ListConflictOptions {
allow_tree_conflicts: false,
skip_content: true,
},
)
.await
.map_err(map_baby)?;
let conflicts: Vec<String> = files
.iter()
.filter_map(|f| {
f.header
.our_path
.clone()
.map(|p| p.to_string_lossy().into_owned())
.or_else(|| {
f.header
.their_path
.clone()
.map(|p| p.to_string_lossy().into_owned())
})
})
.collect();
Ok(MergeStatusDto {
mergeable: conflicts.is_empty(),
fast_forwardable,
conflicts,
})
})
.await
}
pub async fn conflict_conflicted_files(
&self,
ctx: &GitContext,
base: String,
head: String,
) -> Result<Vec<String>, GitError> {
let (our_oid, their_oid) = self.resolve_pair(&ctx, &base, &head).await?;
let key = format!(
"repo.{}.conflict_files.{}.{}",
ctx.model.id, our_oid, their_oid
);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
let baby = self.baby(&ctx);
let files = baby
.list_conflict_files(
&our_oid,
&their_oid,
&ListConflictOptions {
allow_tree_conflicts: false,
skip_content: false,
},
)
.await
.map_err(map_baby)?;
Ok(conflict_paths(&files))
})
.await
}
pub async fn conflict_detail(
&self,
ctx: &GitContext,
base: String,
head: String,
path: String,
) -> Result<ConflictDetailDto, GitError> {
let (our_oid, their_oid) = self.resolve_pair(&ctx, &base, &head).await?;
let key = format!(
"repo.{}.conflict_detail.{}.{}.{}",
ctx.model.id, our_oid, their_oid, path
);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
let baby = self.baby(&ctx);
let files = baby
.list_conflict_files(
&our_oid,
&their_oid,
&ListConflictOptions {
allow_tree_conflicts: false,
skip_content: false,
},
)
.await
.map_err(map_baby)?;
let wanted = std::path::Path::new(&path);
let file = files.into_iter().find(|f| {
f.header.our_path.as_deref() == Some(wanted)
|| f.header.their_path.as_deref() == Some(wanted)
});
match file {
Some(f) => Ok(ConflictDetailDto {
path,
content: f.content,
}),
None => Err(GitError::NotFound),
}
})
.await
}
pub async fn conflict_check_threeway(
&self,
ctx: &GitContext,
ours: String,
theirs: String,
ancestor: String,
) -> Result<MergeStatusDto, GitError> {
let (our_oid, their_oid) = self.resolve_pair(&ctx, &ours, &theirs).await?;
let ancestor_oid = self
.baby(&ctx)
.rev_parse(&ancestor)
.await
.map_err(map_baby)?
.to_string();
let key = format!(
"repo.{}.conflict_threeway.{}.{}.{}",
ctx.model.id, our_oid, their_oid, ancestor_oid
);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
let baby = self.baby(&ctx);
let merged = baby
.merge_tree(&our_oid, &their_oid, Some(&ancestor_oid), false)
.await
.map_err(map_baby)?;
let fast_forwardable = baby.is_ancestor(&ours, &theirs).await.map_err(map_baby)?;
let conflicts: Vec<String> = merged
.conflict_files
.iter()
.map(|p| p.to_string_lossy().into_owned())
.collect();
Ok(MergeStatusDto {
mergeable: !merged.has_conflicts,
conflicts,
fast_forwardable,
})
})
.await
}
}
fn conflict_paths(files: &[ConflictFile]) -> Vec<String> {
files
.iter()
.filter_map(|f| {
f.header
.our_path
.clone()
.map(|p| p.to_string_lossy().into_owned())
.or_else(|| {
f.header
.their_path
.clone()
.map(|p| p.to_string_lossy().into_owned())
})
})
.collect()
}