8923bc0
use std::path::PathBuf;
use crate::rpcgit::commit::types::{TreeEntry, TreeEntryMode};
use crate::rpcgit::tree::types::WalkTreeOptions;
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};
use crate::share::validate;
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct PathLastUpdated {
pub path: String,
pub object_type: i8,
pub last_updated: i64,
pub commit_id: String,
}
impl GitService {
pub async fn tree_read(
&self,
ctx: &GitContext,
revision: String,
path: PathBuf,
) -> Result<Vec<TreeEntry>, GitError> {
validate::validate_revision(&revision)?;
let baby = self.baby(&ctx);
let rev_oid = baby.rev_parse(&revision).await.map_err(map_baby)?;
let key = format!(
"repo.{}.tree_read.{}.{}",
ctx.model.id,
rev_oid,
path.display()
);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
baby.get_tree_entries(&revision, &path)
.await
.map_err(map_baby)
})
.await
}
pub async fn tree_resolve_path(
&self,
ctx: &GitContext,
revision: String,
path: String,
) -> Result<TreeEntry, GitError> {
validate::validate_revision(&revision)?;
let baby = self.baby(&ctx);
let name = path.trim().trim_start_matches("./").to_string();
let rev_oid = baby.rev_parse(&revision).await.map_err(map_baby)?;
let key = format!("repo.{}.tree_resolve.{}.{}", ctx.model.id, rev_oid, name);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
baby.tree_entry_at(&revision, std::path::Path::new(&name))
.await
.map_err(map_baby)?
.ok_or(GitError::NotFound)
})
.await
}
pub async fn tree_walk(
&self,
ctx: &GitContext,
revision: String,
recurse: bool,
show_trees: bool,
) -> Result<Vec<TreeEntry>, GitError> {
validate::validate_revision(&revision)?;
let baby = self.baby(&ctx);
let rev_oid = baby.rev_parse(&revision).await.map_err(map_baby)?;
let key = format!(
"repo.{}.tree_walk.{}:{}:{}",
ctx.model.id, rev_oid, recurse, show_trees
);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
baby.walk_tree(
&revision,
std::path::Path::new(""),
WalkTreeOptions {
recurse,
show_trees,
},
)
.await
.map_err(map_baby)
})
.await
}
pub async fn tree_follow(
&self,
ctx: &GitContext,
revision: String,
path: String,
) -> Result<(TreeEntry, Option<String>, Option<TreeEntry>), GitError> {
validate::validate_revision(&revision)?;
let entry = self.tree_resolve_path(ctx, revision.clone(), path).await?;
if entry.mode != TreeEntryMode::Symlink {
return Ok((entry, None, None));
}
let baby = self.baby(&ctx);
let blob = baby
.get_blob(
entry.oid.clone(),
crate::rpcgit::blob::types::GetBlobOptions { limit: Some(8192) },
)
.await
.map_err(map_baby)?;
let target = String::from_utf8_lossy(&blob.data).trim().to_string();
let mut target_path = PathBuf::from(&target);
if target_path.is_relative() {
let parent = std::path::Path::new(&entry.name)
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_default();
target_path = parent.join(target_path);
}
let target_entry = baby
.tree_entry_at(&revision, &target_path)
.await
.ok()
.flatten()
.filter(|_| !target.is_empty());
Ok((entry, Some(target), target_entry))
}
pub async fn tree_last_updated(
&self,
ctx: &GitContext,
revision: String,
path: Option<String>,
) -> Result<Vec<PathLastUpdated>, GitError> {
validate::validate_revision(&revision)?;
let baby = self.baby(&ctx);
let rev_oid = baby.rev_parse(&revision).await.map_err(map_baby)?;
let path_str = path.as_deref().unwrap_or("");
let key = format!(
"repo.{}.tree_last_updated.{}.{}",
ctx.model.id, rev_oid, path_str
);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
let rows = baby
.tree_entries_with_latest(&revision, path.as_deref())
.await
.map_err(map_baby)?;
Ok(rows
.into_iter()
.map(|row| PathLastUpdated {
path: {
let entry = &row.entry;
let mut s = entry.name.clone();
if entry.mode == TreeEntryMode::Tree {
s.push('/');
}
s
},
object_type: {
let mode = row.entry.mode;
match mode {
TreeEntryMode::Blob => 0,
TreeEntryMode::Exec => 1,
TreeEntryMode::Symlink => 2,
TreeEntryMode::Tree => 3,
TreeEntryMode::Submodule => 4,
TreeEntryMode::Commit => 5,
}
},
last_updated: row.latest.author.time.unix_timestamp(),
commit_id: row.latest.id.to_string(),
})
.collect())
})
.await
}
pub async fn tree_with_commits_html(
&self,
ctx: &std::sync::Arc<crate::context::facade::GitContext>,
revision: &str,
path: &str,
) -> Result<Vec<crate::rpcgit::tree::types::TreeEntryWithLatest>, GitError> {
validate::validate_revision(revision)?;
if path.contains("..")
|| path.contains('\\')
|| path.starts_with('/')
|| path.contains('\0')
{
return Err(GitError::InvalidArgument("invalid path".into()));
}
if ctx.repo_uninitialized() {
return Ok(Vec::new());
}
let baby = self.baby(ctx);
let rev_oid = baby.rev_parse(revision).await.map_err(map_baby)?;
let key = format!(
"repo.{}.tree_dir_entries_html.{}.{}",
ctx.model.id, rev_oid, path
);
cache::cached_fetch(&ctx.cache, &key, IMMUTABLE_TTL, || async {
let p = if path.is_empty() { None } else { Some(path) };
baby.tree_dir_entries_with_latest(revision, p)
.await
.map_err(map_baby)
})
.await
}
}