use gunnar_proto::refs_server::Refs;
use gunnar_proto::{
BlameRequest, BlameResponse, DiffRequest, DiffResponse, GetRefRequest, GetRefResponse,
ListRefsRequest, ListRefsResponse, LogRequest, LogResponse, ReadLfsObjectRequest,
ReadLfsObjectResponse, ReadObjectRequest, ReadObjectResponse, SearchRequest, SearchResponse,
};
use tonic::{Request, Response, Status};
use crate::auth::Access;
use crate::service::RefsService;
use crate::status::repo_path;
use gunnar_proto::GrantLevel;
const DEFAULT_LOG_PAGE: u32 = 100;
const MAX_LOG_PAGE: u32 = 1000;
#[tonic::async_trait]
impl Refs for RefsService {
async fn list(
&self,
request: Request<ListRefsRequest>,
) -> Result<Response<ListRefsResponse>, Status> {
let caller = self.ctx.caller(&request)?;
let request = request.into_inner();
let path = repo_path(request.repo, "repo")?;
self.ctx
.require(&caller, Access::repo(&path), GrantLevel::Read, "Refs.List")?;
let refs = self
.ctx
.control
.list_refs(&path, &request.prefix, request.peel)?;
Ok(Response::new(ListRefsResponse {
refs,
next_page_token: String::new(),
}))
}
async fn get(
&self,
request: Request<GetRefRequest>,
) -> Result<Response<GetRefResponse>, Status> {
let caller = self.ctx.caller(&request)?;
let request = request.into_inner();
let path = repo_path(request.repo, "repo")?;
self.ctx
.require(&caller, Access::repo(&path), GrantLevel::Read, "Refs.Get")?;
if request.ref_name.is_empty() {
return Err(Status::invalid_argument("`ref_name` is required"));
}
let ref_target = self
.ctx
.control
.get_ref(&path, &request.ref_name, request.peel)?;
Ok(Response::new(GetRefResponse {
ref_target: Some(ref_target),
}))
}
async fn log(&self, request: Request<LogRequest>) -> Result<Response<LogResponse>, Status> {
let caller = self.ctx.caller(&request)?;
let request = request.into_inner();
let path = repo_path(request.repo, "repo")?;
self.ctx
.require(&caller, Access::repo(&path), GrantLevel::Read, "Refs.Log")?;
let limit = match request.page_size {
0 => DEFAULT_LOG_PAGE,
n => n.min(MAX_LOG_PAGE),
};
let commits = if request.all_refs {
if !request.start.is_empty() {
return Err(Status::invalid_argument(
"`all_refs` walks from every ref tip; `start` must be empty with it",
));
}
self.ctx.control.log_all_refs(&path, limit, &request.path)?
} else {
self.ctx
.control
.log(&path, &request.start, limit, &request.path)?
};
Ok(Response::new(LogResponse {
commits,
next_page_token: String::new(),
}))
}
async fn read_object(
&self,
request: Request<ReadObjectRequest>,
) -> Result<Response<ReadObjectResponse>, Status> {
let caller = self.ctx.caller(&request)?;
let request = request.into_inner();
let path = repo_path(request.repo, "repo")?;
self.ctx.require(
&caller,
Access::repo(&path),
GrantLevel::Read,
"Refs.ReadObject",
)?;
if request.oid.is_empty() {
return Err(Status::invalid_argument("`oid` is required"));
}
let response = self
.ctx
.control
.read_object(&path, &request.oid, request.max_bytes)?;
Ok(Response::new(response))
}
async fn read_lfs_object(
&self,
request: Request<ReadLfsObjectRequest>,
) -> Result<Response<ReadLfsObjectResponse>, Status> {
let caller = self.ctx.caller(&request)?;
let request = request.into_inner();
let path = repo_path(request.repo, "repo")?;
self.ctx.require(
&caller,
Access::repo(&path),
GrantLevel::Read,
"Refs.ReadLfsObject",
)?;
if request.oid.is_empty() {
return Err(Status::invalid_argument("`oid` is required"));
}
let response = self.ctx.control.read_lfs_object(
&path,
&request.oid,
request.max_bytes,
(request.offset != 0 || request.length != 0)
.then_some((request.offset, request.length)),
)?;
Ok(Response::new(response))
}
async fn diff(&self, request: Request<DiffRequest>) -> Result<Response<DiffResponse>, Status> {
let caller = self.ctx.caller(&request)?;
let request = request.into_inner();
let path = repo_path(request.repo, "repo")?;
self.ctx
.require(&caller, Access::repo(&path), GrantLevel::Read, "Refs.Diff")?;
let changes =
self.ctx
.control
.diff(&path, &request.from, &request.to, &request.path_prefixes)?;
Ok(Response::new(DiffResponse { changes }))
}
async fn blame(
&self,
request: Request<BlameRequest>,
) -> Result<Response<BlameResponse>, Status> {
let caller = self.ctx.caller(&request)?;
let request = request.into_inner();
let path = repo_path(request.repo, "repo")?;
self.ctx
.require(&caller, Access::repo(&path), GrantLevel::Read, "Refs.Blame")?;
if request.path.is_empty() {
return Err(Status::invalid_argument("`path` is required"));
}
let response = self.ctx.control.blame(&path, &request.rev, &request.path)?;
Ok(Response::new(response))
}
async fn search(
&self,
request: Request<SearchRequest>,
) -> Result<Response<SearchResponse>, Status> {
let caller = self.ctx.caller(&request)?;
let request = request.into_inner();
let path = repo_path(request.repo, "repo")?;
self.ctx.require(
&caller,
Access::repo(&path),
GrantLevel::Read,
"Refs.Search",
)?;
if request.pattern.is_empty() {
return Err(Status::invalid_argument("`pattern` is required"));
}
let response = self.ctx.control.search(
&path,
&request.rev,
&request.pattern,
request.max_file_bytes,
request.max_matches,
)?;
Ok(Response::new(response))
}
}