Skip to content
Clone
126b767
1785 lines 61.3 KB Rust
use std::path::{Path, PathBuf};
use proto::client::Intercepted;
use proto::kenshell::common::v1::PageRequest;
use proto::kenshell::v1 as pb;
use proto::kenshell::v1::commit_service_client::CommitServiceClient;
use proto::kenshell::v1::diff_service_client::DiffServiceClient;
use proto::kenshell::v1::ref_service_client::RefServiceClient;
use proto::kenshell::v1::tree_service_client::TreeServiceClient;
use crate::rpcgit::GitBaby;
use crate::rpcgit::archive::types::{
ArchiveFormat, BundleRequest, CreateArchiveRequest, GetArchiveRequest,
};
use crate::rpcgit::blame::types::{BlameHunk, BlameOptions};
use crate::rpcgit::blob::types::{
BlobContent, BlobInfo, GetBlobOptions, LFSPointer, ListLFSPointersOptions,
};
use crate::rpcgit::branch::types::{BranchInfo, ListBranchesOptions};
use crate::rpcgit::commit::types::{
CommitInfo, CommitShortStat, CommitStats, ListCommitsOptions, Signature, TreeEntry,
TreeEntryMode,
};
use crate::rpcgit::conflict::types::ListConflictOptions;
use crate::rpcgit::diff::types::{
ChangedPath, ChangedPathDiff, ChangedPathRequest, DiffOptions, DiffRequest, DiffStatus,
NumStat, RangeDiffSpec, ShortStat,
};
use crate::rpcgit::error::BabyError;
use crate::rpcgit::oid::ObjectId;
use crate::rpcgit::refs::types::{ListRefsOptions, RefInfo, RefType};
use crate::rpcgit::setup::types::ObjectFormat;
use crate::rpcgit::tags::types::{
CreateTagOptions, ListTagsOptions, TagInfo, TagType, TagVerifyResult,
};
use crate::rpcgit::tree::types::{
DiffTreesOptions, TreeDiff, TreeDiffStatus, TreeEntryWithLatest, WalkTreeOptions,
};
fn unsupported(what: &str) -> BabyError {
BabyError::Custom(format!("{what} is not provided by KenShell"))
}
fn oid(text: &str) -> Result<ObjectId, BabyError> {
ObjectId::parse(text).map_err(|_| BabyError::RevisionInvalid(text.to_string()))
}
fn oid_opt(text: &str) -> Option<ObjectId> {
if text.trim().is_empty() {
None
} else {
ObjectId::parse(text).ok()
}
}
fn signature(person: Option<pb::CommitPerson>) -> Signature {
let person = person.unwrap_or_default();
Signature {
name: person.name,
email: person.email,
time: person
.at
.and_then(|at| time::OffsetDateTime::from_unix_timestamp(at.seconds).ok())
.unwrap_or(time::OffsetDateTime::UNIX_EPOCH),
}
}
/// 仓库概览,字段全部来自 KenShell。
#[derive(Debug, Clone, Default)]
pub struct RepositoryOverviewInfo {
pub default_branch: String,
pub primary_language: Option<String>,
pub object_format: String,
pub size_bytes: i64,
pub branch_count: i32,
pub tag_count: i32,
}
fn mode_of(text: &str) -> TreeEntryMode {
TreeEntryMode::from_octal_str(text).unwrap_or(TreeEntryMode::Blob)
}
/// `google.protobuf.Timestamp` → `OffsetDateTime`;非法值当作缺失。
fn at(value: Option<prost_types::Timestamp>) -> Option<time::OffsetDateTime> {
value.and_then(|at| time::OffsetDateTime::from_unix_timestamp(at.seconds).ok())
}
fn entry_from_view(view: pb::TreeEntryView) -> TreeEntry {
let name = if view.name_bytes.is_empty() {
view.name
} else {
String::from_utf8_lossy(&view.name_bytes).into_owned()
};
let mode =
match pb::TreeEntryType::try_from(view.r#type).unwrap_or(pb::TreeEntryType::Unspecified) {
pb::TreeEntryType::Submodule => TreeEntryMode::Submodule,
pb::TreeEntryType::Symlink => TreeEntryMode::Symlink,
_ => mode_of(&view.mode),
};
TreeEntry {
oid: oid(&view.oid).unwrap_or_else(|_| {
oid_opt("4b825dc642cb6eb9a060e54bf8d69288fbee4904").expect("empty tree")
}),
name,
mode,
size: Some(view.size_bytes),
}
}
fn entry_from_view_path(view: pb::TreeEntryView) -> TreeEntry {
let mut entry = entry_from_view(view.clone());
if !view.path.is_empty() {
entry.name = view.path;
}
entry
}
fn commit_from_view(view: pb::CommitView) -> CommitInfo {
CommitInfo {
id: oid(&view.id)
.unwrap_or_else(|_| oid_opt("0000000000000000000000000000000000000000").expect("zero")),
tree_id: oid(&view.tree_id).unwrap_or_else(|_| {
oid_opt("4b825dc642cb6eb9a060e54bf8d69288fbee4904").expect("empty tree")
}),
parents: view
.parent_ids
.iter()
.filter_map(|p| ObjectId::parse(p).ok())
.collect(),
author: signature(view.author),
committer: signature(view.committer),
message: view.message,
gpg_signature: None,
}
}
fn ref_type(value: i32) -> RefType {
match pb::RefType::try_from(value).unwrap_or(pb::RefType::Unspecified) {
pb::RefType::Branch => RefType::Branch,
pb::RefType::Tag => RefType::Tag,
pb::RefType::RemoteBranch => RefType::Remote,
_ => RefType::Other,
}
}
fn path_or_empty(path: Option<PathBuf>) -> String {
path.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_default()
}
fn page_request(page_size: u32, token: String) -> PageRequest {
PageRequest {
page_size,
page_token: token,
}
}
fn changed_path(view: pb::ChangedPathView) -> ChangedPath {
ChangedPath {
old_path: if view.old_path.is_empty() {
None
} else {
Some(PathBuf::from(view.old_path))
},
new_path: PathBuf::from(view.new_path),
old_mode: view.old_mode,
new_mode: view.new_mode,
old_oid: oid_opt(&view.old_oid),
new_oid: oid_opt(&view.new_oid),
status: {
let __inline = |text: &str| -> DiffStatus {
match text {
"A" | "added" | "Added" => DiffStatus::Added,
"D" | "deleted" | "Deleted" => DiffStatus::Deleted,
"T" | "type_changed" | "TypeChanged" => DiffStatus::TypeChanged,
"R" | "renamed" | "Renamed" => DiffStatus::Renamed,
"C" | "copied" | "Copied" => DiffStatus::Copied,
_ => DiffStatus::Modified,
}
};
__inline(&view.status)
},
}
}
impl GitBaby {
fn common_ref(&self) -> proto::kenshell::common::v1::RepositoryRef {
self.context().repository_ref()
}
fn refs_client(&self) -> RefServiceClient<Intercepted> {
self.context().ks.refs()
}
fn commit_client(&self) -> CommitServiceClient<Intercepted> {
self.context().ks.commit()
}
fn tree_client(&self) -> TreeServiceClient<Intercepted> {
self.context().ks.tree()
}
fn diff_client(&self) -> DiffServiceClient<Intercepted> {
self.context().ks.diff()
}
fn repository_ref_some(&self) -> Option<proto::kenshell::common::v1::RepositoryRef> {
Some(self.common_ref())
}
pub async fn rev_parse(&self, revision: &str) -> Result<ObjectId, BabyError> {
let mut client = self.commit_client();
let response = client
.rev_parse(pb::RevParseRequest {
repository: self.repository_ref_some(),
revision: revision.to_string(),
})
.await
.map_err(BabyError::from)?
.into_inner();
oid(&response.oid)
}
pub async fn resolve_revision(&self, revision: &str) -> Result<ObjectId, BabyError> {
self.rev_parse(revision).await
}
pub async fn is_ancestor(&self, ancestor: &str, descendant: &str) -> Result<bool, BabyError> {
let mut client = self.commit_client();
let response = client
.is_ancestor(pb::IsAncestorRequest {
repository: self.repository_ref_some(),
ancestor: ancestor.to_string(),
descendant: descendant.to_string(),
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response.is_ancestor)
}
pub async fn get_commit(&self, revision: &str) -> Result<CommitInfo, BabyError> {
let mut client = self.commit_client();
let response = client
.get_commit(pb::GetCommitRequest {
repository: self.repository_ref_some(),
commit_id: revision.to_string(),
})
.await
.map_err(BabyError::from)?
.into_inner();
response
.commit
.map(commit_from_view)
.ok_or_else(|| BabyError::RevisionNotFound(revision.to_string()))
}
pub async fn list_commits(
&self,
opts: ListCommitsOptions,
) -> Result<Vec<CommitInfo>, BabyError> {
let limit = opts.max_count.unwrap_or(50).max(1);
let skip = opts.skip.unwrap_or(0);
let mut out = Vec::new();
let mut token = String::new();
let mut seen = 0u32;
loop {
let mut client = self.commit_client();
let response = client
.list_commits(pb::ListCommitsRequest {
repository: self.repository_ref_some(),
revision: opts.revision.clone(),
path: path_or_empty(opts.path.clone()),
query: String::new(),
first_parent: opts.first_parent,
page: Some(page_request(limit.min(200), token.clone())),
since: opts.since.clone().unwrap_or_default(),
until: opts.until.clone().unwrap_or_default(),
})
.await
.map_err(BabyError::from)?
.into_inner();
for commit in response.commits {
if seen >= skip {
out.push(commit_from_view(commit));
}
seen += 1;
}
let next = response
.page
.as_ref()
.map(|p| p.next_page_token.clone())
.unwrap_or_default();
if out.len() >= limit as usize || next.is_empty() {
break;
}
token = next;
}
out.truncate(limit as usize);
Ok(out)
}
pub async fn count_commits(&self, opts: ListCommitsOptions) -> Result<u64, BabyError> {
let mut client = self.commit_client();
let response = client
.list_commits(pb::ListCommitsRequest {
repository: self.repository_ref_some(),
revision: opts.revision,
path: path_or_empty(opts.path),
query: String::new(),
first_parent: opts.first_parent,
page: Some(page_request(1, String::new())),
since: opts.since.unwrap_or_default(),
until: opts.until.unwrap_or_default(),
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response
.total
.or_else(|| response.page.as_ref().and_then(|p| p.total_size))
.unwrap_or(0))
}
pub async fn commit_stats(
&self,
revision: &str,
path: Option<&Path>,
) -> Result<CommitStats, BabyError> {
let mut client = self.commit_client();
let response = client
.get_commit_stats(pb::GetCommitStatsRequest {
repository: self.repository_ref_some(),
commit_id: revision.to_string(),
path: path
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_default(),
})
.await
.map_err(BabyError::from)?
.into_inner();
let stats = response.stats.unwrap_or_default();
Ok(CommitStats {
additions: stats.additions as u64,
deletions: stats.deletions as u64,
})
}
pub async fn commit_short_stats(
&self,
revision: &str,
) -> Result<Vec<CommitShortStat>, BabyError> {
let commits = self
.list_commits(ListCommitsOptions {
revision: revision.to_string(),
max_count: Some(200),
..Default::default()
})
.await?;
let mut out = Vec::with_capacity(commits.len());
for commit in commits {
let stats = self
.commit_stats(&commit.id.to_string(), None)
.await
.unwrap_or_default();
out.push(CommitShortStat {
oid: commit.id,
author_name: commit.author.name,
author_email: commit.author.email,
additions: stats.additions,
deletions: stats.deletions,
});
}
Ok(out)
}
pub async fn blame_all(&self, opts: BlameOptions) -> Result<Vec<BlameHunk>, BabyError> {
let mut client = self.commit_client();
let mut token = String::new();
let mut hunks: Vec<BlameHunk> = Vec::new();
loop {
let response = client
.list_blame(pb::ListBlameRequest {
repository: self.repository_ref_some(),
revision: opts.revision.clone(),
path: opts.path.to_string_lossy().into_owned(),
page: Some(page_request(500, token.clone())),
})
.await
.map_err(BabyError::from)?
.into_inner();
for line in response.lines {
let sha = ObjectId::parse(&line.commit_id).unwrap_or_else(|_| {
ObjectId::parse("0000000000000000000000000000000000000000").expect("zero")
});
match hunks.last_mut() {
Some(last) if last.sha == sha => {
last.lines.push(line.content);
last.final_line_count += 1;
}
_ => hunks.push(BlameHunk {
sha,
previous_sha: None,
previous_path: None,
lines: vec![line.content],
final_start_line: line.line_number,
final_line_count: 1,
orig_start_line: 0,
}),
}
}
let next = response
.page
.as_ref()
.map(|p| p.next_page_token.clone())
.unwrap_or_default();
if next.is_empty() {
break;
}
token = next;
}
Ok(hunks)
}
pub async fn get_tree_entries(
&self,
revision: &str,
path: &Path,
) -> Result<Vec<TreeEntry>, BabyError> {
let mut client = self.tree_client();
let response = client
.get_tree(pb::GetTreeRequest {
repository: self.repository_ref_some(),
revision: revision.to_string(),
path: path.to_string_lossy().into_owned(),
page: None,
path_bytes: Vec::new(),
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response.entries.into_iter().map(entry_from_view).collect())
}
pub async fn tree_entry_at(
&self,
revision: &str,
path: &Path,
) -> Result<Option<TreeEntry>, BabyError> {
let mut client = self.tree_client();
let response = client
.resolve_tree_path(pb::ResolveTreePathRequest {
repository: self.repository_ref_some(),
revision: revision.to_string(),
path: path.to_string_lossy().into_owned(),
path_bytes: Vec::new(),
})
.await;
match response {
Ok(response) => Ok(response.into_inner().entry.map(entry_from_view)),
Err(status) if status.code() == tonic::Code::NotFound => Ok(None),
Err(status) => Err(BabyError::from(status)),
}
}
pub async fn walk_tree(
&self,
revision: &str,
path: &Path,
opts: WalkTreeOptions,
) -> Result<Vec<TreeEntry>, BabyError> {
let mut client = self.tree_client();
let mut token = String::new();
let mut out = Vec::new();
loop {
let response = client
.list_files(pb::ListFilesRequest {
repository: self.repository_ref_some(),
revision: revision.to_string(),
path: path.to_string_lossy().into_owned(),
recursive: opts.recurse,
page: Some(page_request(200, token.clone())),
include_directories: opts.show_trees,
path_bytes: Vec::new(),
})
.await
.map_err(BabyError::from)?
.into_inner();
out.extend(response.files.into_iter().map(entry_from_view_path));
let next = response
.page
.as_ref()
.map(|p| p.next_page_token.clone())
.unwrap_or_default();
if next.is_empty() {
break;
}
token = next;
}
Ok(out)
}
pub async fn list_dir(
&self,
revision: &str,
path: Option<&str>,
) -> Result<Vec<TreeEntry>, BabyError> {
let mut client = self.tree_client();
let response = client
.get_tree(pb::GetTreeRequest {
repository: self.repository_ref_some(),
revision: revision.to_string(),
path: path.unwrap_or("").to_string(),
page: None,
path_bytes: Vec::new(),
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response.entries.into_iter().map(entry_from_view).collect())
}
async fn entries_with_latest(
&self,
revision: &str,
path: Option<&str>,
recursive: bool,
) -> Result<Vec<TreeEntryWithLatest>, BabyError> {
let mut client = self.tree_client();
let mut token = String::new();
let mut out = Vec::new();
loop {
let response = client
.get_tree(pb::GetTreeRequest {
repository: self.repository_ref_some(),
revision: revision.to_string(),
path: path.unwrap_or("").to_string(),
page: Some(page_request(200, token.clone())),
path_bytes: Vec::new(),
})
.await
.map_err(BabyError::from)?
.into_inner();
for entry in response.entries {
let latest = entry
.latest_commit
.clone()
.map(commit_from_view)
.unwrap_or_else(|| commit_from_view(pb::CommitView::default()));
out.push(TreeEntryWithLatest {
entry: entry_from_view(entry),
latest,
});
}
let next = response
.page
.as_ref()
.map(|p| p.next_page_token.clone())
.unwrap_or_default();
if next.is_empty() {
break;
}
token = next;
}
if recursive {
let mut full = Vec::new();
for item in out {
if item.entry.mode == TreeEntryMode::Tree {
continue;
}
full.push(item);
}
return Ok(full);
}
Ok(out)
}
pub async fn tree_entries_with_latest(
&self,
revision: &str,
path: Option<&str>,
) -> Result<Vec<TreeEntryWithLatest>, BabyError> {
self.entries_with_latest(revision, path, false).await
}
pub async fn tree_dir_entries_with_latest(
&self,
revision: &str,
path: Option<&str>,
) -> Result<Vec<TreeEntryWithLatest>, BabyError> {
self.entries_with_latest(revision, path, false).await
}
pub async fn get_blob(
&self,
oid_value: ObjectId,
opts: GetBlobOptions,
) -> Result<BlobContent, BabyError> {
let mut client = self.context().ks.blob();
let response = client
.get_blob_content(pb::GetBlobContentRequest {
repository: self.repository_ref_some(),
oid: oid_value.to_string(),
max_bytes: opts.limit.unwrap_or(0).max(0) as u64,
})
.await
.map_err(BabyError::from)?
.into_inner();
let content = response.content.unwrap_or_default();
let size = content.size;
Ok(BlobContent {
info: BlobInfo {
oid: oid_opt(&content.oid).unwrap_or(oid_value),
size,
data: Some(content.data.clone()),
},
data: content.data,
})
}
pub async fn get_blob_size(&self, oid_value: ObjectId) -> Result<i64, BabyError> {
let mut client = self.context().ks.blob();
let response = client
.get_blob_metadata(pb::GetBlobMetadataRequest {
repository: self.repository_ref_some(),
oid: oid_value.to_string(),
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response.metadata.map(|m| m.size).unwrap_or(0))
}
pub async fn list_lfs_pointers(
&self,
opts: ListLFSPointersOptions,
) -> Result<Vec<LFSPointer>, BabyError> {
let mut client = self.context().ks.blob();
let response = client
.list_lfs_pointers(pb::ListLfsPointersRequest {
repository: self.repository_ref_some(),
revision: opts.revisions.first().cloned().unwrap_or_default(),
limit: opts.limit.unwrap_or(100),
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response
.pointers
.into_iter()
.map(|p| LFSPointer {
oid: oid_opt(&p.oid).unwrap_or_else(|| {
oid_opt("0000000000000000000000000000000000000000").expect("zero")
}),
size: p.size,
file_oid: oid_opt("0000000000000000000000000000000000000000").expect("zero"),
file_size: p.size,
content: Vec::new(),
})
.collect())
}
pub async fn list_refs(&self, opts: ListRefsOptions) -> Result<Vec<RefInfo>, BabyError> {
let mut client = self.refs_client();
let mut out = Vec::new();
let mut token = String::new();
let prefix = opts.patterns.first().cloned().unwrap_or_default();
let limit = opts.limit.unwrap_or(500);
loop {
let response = client
.list_refs(pb::ListRefsRequest {
repository: self.repository_ref_some(),
prefix: prefix.clone(),
page: Some(page_request(limit.min(200), token.clone())),
})
.await
.map_err(BabyError::from)?
.into_inner();
for item in response.refs {
out.push(RefInfo {
name: item.name,
ref_type: ref_type(item.r#type),
sha: oid(&item.sha).unwrap_or_else(|_| {
oid_opt("0000000000000000000000000000000000000000").expect("zero")
}),
peeled: None,
});
}
let next = response
.page
.as_ref()
.map(|p| p.next_page_token.clone())
.unwrap_or_default();
if next.is_empty() || out.len() >= limit as usize {
break;
}
token = next;
}
if !opts.patterns.is_empty() {
out.retain(|r| opts.patterns.iter().any(|p| r.name.contains(p.as_str())));
}
if !opts.exclude_patterns.is_empty() {
out.retain(|r| {
!opts
.exclude_patterns
.iter()
.any(|p| r.name.contains(p.as_str()))
});
}
Ok(out)
}
pub async fn find_refs_by_oid(&self, target: ObjectId) -> Result<Vec<RefInfo>, BabyError> {
let refs = self.list_refs(ListRefsOptions::default()).await?;
Ok(refs.into_iter().filter(|r| r.sha == target).collect())
}
pub async fn update_ref(
&self,
name: &str,
new_sha: ObjectId,
old_sha: Option<ObjectId>,
) -> Result<(), BabyError> {
let branch = name
.strip_prefix("refs/heads/")
.ok_or_else(|| unsupported("updating non-branch refs"))?;
let mut client = self.context().ks.command();
client
.update_branch(pb::UpdateBranchRequest {
repository: self.repository_ref_some(),
name: branch.to_string(),
new_sha: new_sha.to_string(),
expected_old_sha: old_sha.map(|o| o.to_string()).unwrap_or_default(),
actor: Some(self.context().actor()),
options: Some(self.context().write_options()),
})
.await
.map_err(BabyError::from)?;
Ok(())
}
pub async fn delete_ref(&self, name: &str) -> Result<(), BabyError> {
let branch = name
.strip_prefix("refs/heads/")
.ok_or_else(|| unsupported("deleting non-branch refs"))?;
let mut client = self.context().ks.command();
client
.delete_branch(pb::DeleteBranchRequest {
repository: self.repository_ref_some(),
name: branch.to_string(),
expected_sha: String::new(),
actor: Some(self.context().actor()),
options: Some(self.context().write_options()),
force: true,
})
.await
.map_err(BabyError::from)?;
Ok(())
}
pub async fn set_default_branch(&self, name: &str) -> Result<(), BabyError> {
let mut client = self.context().ks.command();
client
.set_default_branch(pb::SetDefaultBranchRequest {
repository: self.repository_ref_some(),
name: name.to_string(),
actor: Some(self.context().actor()),
options: Some(self.context().write_options()),
})
.await
.map_err(BabyError::from)?;
Ok(())
}
pub async fn get_default_branch(&self) -> Result<String, BabyError> {
let mut client = self.refs_client();
let response = client
.get_default_branch(pb::GetDefaultBranchRequest {
repository: self.repository_ref_some(),
})
.await
.map_err(BabyError::from)?
.into_inner();
if response.name.is_empty() {
Err(BabyError::RevisionNotFound("HEAD".into()))
} else {
Ok(response.name)
}
}
pub async fn list_branches(
&self,
opts: ListBranchesOptions,
) -> Result<Vec<BranchInfo>, BabyError> {
let mut client = self.refs_client();
let mut token = String::new();
let mut out = Vec::new();
loop {
let response = client
.list_branches(pb::ListBranchesRequest {
repository: self.repository_ref_some(),
query: opts.patterns.first().cloned().unwrap_or_default(),
sort: pb::BranchSort::NameAsc as i32,
page: Some(page_request(200, token.clone())),
})
.await
.map_err(BabyError::from)?
.into_inner();
for branch in response.branches {
out.push(BranchInfo {
name: branch.name,
sha: oid(&branch.sha).unwrap_or_else(|_| {
oid_opt("0000000000000000000000000000000000000000").expect("zero")
}),
is_protected: branch.is_protected,
is_deleted: branch.is_deleted,
deleted_at: at(branch.deleted_at),
created_at: at(branch.created_at),
updated_at: at(branch.updated_at),
});
}
let next = response
.page
.as_ref()
.map(|p| p.next_page_token.clone())
.unwrap_or_default();
if next.is_empty() {
break;
}
token = next;
}
Ok(out)
}
pub async fn find_branch(&self, name: &str) -> Result<BranchInfo, BabyError> {
let mut client = self.refs_client();
let response = client
.get_branch(pb::GetBranchRequest {
repository: self.repository_ref_some(),
name: name.to_string(),
})
.await
.map_err(BabyError::from)?
.into_inner();
let branch = response
.branch
.ok_or_else(|| BabyError::BranchNotFound(name.to_string()))?;
Ok(BranchInfo {
name: branch.name,
sha: oid(&branch.sha).unwrap_or_else(|_| {
oid_opt("0000000000000000000000000000000000000000").expect("zero")
}),
is_protected: branch.is_protected,
is_deleted: branch.is_deleted,
deleted_at: at(branch.deleted_at),
created_at: at(branch.created_at),
updated_at: at(branch.updated_at),
})
}
pub async fn create_branch(
&self,
name: &str,
start_point: Option<&str>,
) -> Result<(), BabyError> {
let mut client = self.context().ks.command();
client
.create_branch(pb::CreateBranchRequest {
repository: self.repository_ref_some(),
name: name.to_string(),
start_point: start_point.unwrap_or("").to_string(),
actor: Some(self.context().actor()),
options: Some(self.context().write_options()),
})
.await
.map_err(BabyError::from)?;
Ok(())
}
pub async fn delete_branch(&self, name: &str, force: bool) -> Result<(), BabyError> {
let mut client = self.context().ks.command();
client
.delete_branch(pb::DeleteBranchRequest {
repository: self.repository_ref_some(),
name: name.to_string(),
expected_sha: String::new(),
actor: Some(self.context().actor()),
options: Some(self.context().write_options()),
force,
})
.await
.map_err(BabyError::from)?;
Ok(())
}
pub async fn rename_branch(&self, from: &str, to: &str) -> Result<(), BabyError> {
let mut client = self.context().ks.command();
client
.rename_branch(pb::RenameBranchRequest {
repository: self.repository_ref_some(),
name: from.to_string(),
new_name: to.to_string(),
actor: Some(self.context().actor()),
options: Some(self.context().write_options()),
})
.await
.map_err(BabyError::from)?;
Ok(())
}
pub async fn list_tags(&self, opts: ListTagsOptions) -> Result<Vec<TagInfo>, BabyError> {
let mut client = self.refs_client();
let mut token = String::new();
let mut out = Vec::new();
loop {
let response = client
.list_tags(pb::ListTagsRequest {
repository: self.repository_ref_some(),
query: opts.patterns.first().cloned().unwrap_or_default(),
sort: pb::TagSort::NameAsc as i32,
page: Some(page_request(200, token.clone())),
})
.await
.map_err(BabyError::from)?
.into_inner();
for tag in response.tags {
out.push(tag_from_view(tag));
}
let next = response
.page
.as_ref()
.map(|p| p.next_page_token.clone())
.unwrap_or_default();
if next.is_empty() {
break;
}
token = next;
}
Ok(out)
}
pub async fn find_tag(&self, name: &str) -> Result<TagInfo, BabyError> {
let mut client = self.refs_client();
let response = client
.get_tag(pb::GetTagRequest {
repository: self.repository_ref_some(),
name: name.to_string(),
})
.await
.map_err(BabyError::from)?
.into_inner();
response
.tag
.map(tag_from_view)
.ok_or_else(|| BabyError::TagNotFound(name.to_string()))
}
pub async fn create_lightweight_tag(&self, opts: CreateTagOptions) -> Result<(), BabyError> {
self.create_tag(opts, false).await
}
pub async fn create_annotated_tag(&self, opts: CreateTagOptions) -> Result<(), BabyError> {
self.create_tag(opts, true).await
}
async fn create_tag(&self, opts: CreateTagOptions, annotated: bool) -> Result<(), BabyError> {
let mut client = self.context().ks.command();
client
.create_tag(pb::CreateTagRequest {
repository: self.repository_ref_some(),
name: opts.name,
target_sha: opts.target,
message: opts.message.unwrap_or_default(),
actor: Some(self.context().actor()),
options: Some(self.context().write_options()),
annotated,
})
.await
.map_err(BabyError::from)?;
Ok(())
}
pub async fn delete_tag(&self, name: &str) -> Result<(), BabyError> {
let mut client = self.context().ks.command();
client
.delete_tag(pb::DeleteTagRequest {
repository: self.repository_ref_some(),
name: name.to_string(),
expected_sha: String::new(),
actor: Some(self.context().actor()),
options: Some(self.context().write_options()),
})
.await
.map_err(BabyError::from)?;
Ok(())
}
pub async fn raw_diff(
&self,
req: &DiffRequest,
_opts: Option<&DiffOptions>,
) -> Result<String, BabyError> {
let mut client = self.diff_client();
let mut stream = client
.get_raw_diff(pb::GetRawDiffRequest {
repository: self.repository_ref_some(),
base_revision: req.left.to_string(),
head_revision: req.right.to_string(),
})
.await
.map_err(BabyError::from)?
.into_inner();
let mut out = Vec::new();
while let Some(chunk) = stream.message().await.map_err(BabyError::from)? {
out.extend_from_slice(&chunk.data);
}
Ok(String::from_utf8_lossy(&out).into_owned())
}
pub async fn range_diff(
&self,
spec: &RangeDiffSpec,
_opts: &DiffOptions,
_hex_len: Option<u8>,
) -> Result<String, BabyError> {
let (from, to, base) = match spec {
RangeDiffSpec::Range(a, b) => (a.clone(), b.clone(), String::new()),
RangeDiffSpec::RevisionRange(a, b) => (a.clone(), b.clone(), String::new()),
RangeDiffSpec::BaseWithRevisions(base, a, b) => (a.clone(), b.clone(), base.clone()),
};
let mut client = self.diff_client();
let mut stream = client
.get_range_diff(pb::GetRangeDiffRequest {
repository: self.repository_ref_some(),
from,
to,
base,
})
.await
.map_err(BabyError::from)?
.into_inner();
let mut out = Vec::new();
while let Some(chunk) = stream.message().await.map_err(BabyError::from)? {
out.extend_from_slice(&chunk.data);
}
Ok(String::from_utf8_lossy(&out).into_owned())
}
pub async fn shortstat(
&self,
req: &DiffRequest,
_opts: &DiffOptions,
) -> Result<ShortStat, BabyError> {
let mut client = self.diff_client();
let response = client
.get_diff_short_stat(pb::GetDiffShortStatRequest {
repository: self.repository_ref_some(),
base_revision: req.left.to_string(),
head_revision: req.right.to_string(),
})
.await
.map_err(BabyError::from)?
.into_inner();
let stats = response.stats.unwrap_or_default();
Ok(ShortStat {
files_changed: stats.files_changed,
additions: stats.additions,
deletions: stats.deletions,
})
}
pub async fn diff_stats(
&self,
req: &DiffRequest,
opts: &DiffOptions,
) -> Result<Vec<NumStat>, BabyError> {
let mut client = self.diff_client();
let response = client
.list_diff_num_stats(pb::ListDiffNumStatsRequest {
repository: self.repository_ref_some(),
base_revision: req.left.to_string(),
head_revision: req.right.to_string(),
paths: opts
.paths
.as_ref()
.map(|paths| {
paths
.iter()
.map(|p| p.to_string_lossy().into_owned())
.collect()
})
.unwrap_or_default(),
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response
.stats
.into_iter()
.map(|stat| NumStat {
additions: stat.additions,
deletions: stat.deletions,
path: PathBuf::from(stat.path),
old_path: None,
})
.collect())
}
pub async fn find_changed_paths(
&self,
requests: Vec<ChangedPathRequest>,
_filter_status: Option<char>,
_detect_renames: Option<u8>,
_merge_parents: bool,
) -> Result<Vec<ChangedPath>, BabyError> {
let mut client = self.diff_client();
let (base, head, commit) = match requests.first() {
Some(ChangedPathRequest::Commit(oid_value)) => {
(String::new(), String::new(), oid_value.to_string())
}
Some(ChangedPathRequest::TreePair(left, right)) => {
(left.to_string(), right.to_string(), String::new())
}
None => return Ok(Vec::new()),
};
let response = client
.list_changed_paths(pb::ListChangedPathsRequest {
repository: self.repository_ref_some(),
base_revision: base,
head_revision: head,
commit_id: commit,
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response.paths.into_iter().map(changed_path).collect())
}
pub async fn commit_diff(
&self,
req: &DiffRequest,
_opts: &DiffOptions,
_hex_len: Option<u8>,
) -> Result<Vec<ChangedPathDiff>, BabyError> {
let mut client = self.diff_client();
let response = client
.compare_commits(pb::CompareCommitsRequest {
repository: self.repository_ref_some(),
base_revision: req.left.to_string(),
head_revision: req.right.to_string(),
merge_base: false,
path: String::new(),
page: None,
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response.files.into_iter().map(changed_path_diff).collect())
}
pub async fn diff_trees(
&self,
base: &str,
head: &str,
_path: &Path,
_opts: DiffTreesOptions,
) -> Result<Vec<TreeDiff>, BabyError> {
let mut client = self.diff_client();
let response = client
.list_diff_tree(pb::ListDiffTreeRequest {
repository: self.repository_ref_some(),
base_revision: base.to_string(),
head_revision: head.to_string(),
use_merge_base: false,
paths: Vec::new(),
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response
.records
.into_iter()
.map(|record| TreeDiff {
old_path: if record.base_path.is_empty() {
None
} else {
Some(PathBuf::from(record.base_path))
},
new_path: if record.head_path.is_empty() {
None
} else {
Some(PathBuf::from(record.head_path))
},
old_mode: Some(mode_of(&format!("{:o}", record.base_mode))),
new_mode: Some(mode_of(&format!("{:o}", record.head_mode))),
old_oid: oid_opt(&record.base_blob_id),
new_oid: oid_opt(&record.head_blob_id),
status: {
let __inline = |text: &str| -> TreeDiffStatus {
match text {
"A" | "added" | "Added" => TreeDiffStatus::Added,
"D" | "deleted" | "Deleted" => TreeDiffStatus::Deleted,
"T" | "type_changed" | "TypeChanged" => TreeDiffStatus::TypeChanged,
"R" | "renamed" | "Renamed" => TreeDiffStatus::Renamed,
"C" | "copied" | "Copied" => TreeDiffStatus::Copied,
_ => TreeDiffStatus::Modified,
}
};
__inline(&record.status)
},
})
.collect())
}
pub async fn list_conflict_files(
&self,
base: &str,
head: &str,
opts: &ListConflictOptions,
) -> Result<Vec<crate::rpcgit::conflict::types::ConflictFile>, BabyError> {
let mut client = self.context().ks.conflict();
let response = client
.check_conflicts(pb::CheckConflictsRequest {
repository: self.repository_ref_some(),
base_revision: base.to_string(),
head_revision: head.to_string(),
ancestor_revision: String::new(),
})
.await
.map_err(BabyError::from)?
.into_inner();
let our_commit_oid = oid(base)
.unwrap_or_else(|_| oid_opt("0000000000000000000000000000000000000000").expect("zero"));
let mut out = Vec::new();
for path in response
.status
.map(|status| status.paths)
.unwrap_or_default()
{
let content = if opts.skip_content {
None
} else {
let mut client = self.context().ks.conflict();
client
.get_conflict_detail(pb::GetConflictDetailRequest {
repository: self.repository_ref_some(),
base_revision: base.to_string(),
head_revision: head.to_string(),
path: path.clone(),
})
.await
.ok()
.map(|response| response.into_inner().content)
};
out.push(crate::rpcgit::conflict::types::ConflictFile {
header: crate::rpcgit::conflict::types::ConflictFileHeader {
our_commit_oid: our_commit_oid.clone(),
their_path: Some(PathBuf::from(&path)),
our_path: Some(PathBuf::from(&path)),
ancestor_path: None,
our_mode: 0,
},
content,
});
}
Ok(out)
}
pub async fn commit_ids_between_reverse(
&self,
start: &str,
end: &str,
_limit: Option<u32>,
max_count: Option<u32>,
) -> Result<Vec<ObjectId>, BabyError> {
let commits = self
.list_commits(ListCommitsOptions {
revision: format!("{start}..{end}"),
max_count,
..Default::default()
})
.await?;
let mut ids: Vec<ObjectId> = commits.into_iter().map(|commit| commit.id).collect();
ids.reverse();
Ok(ids)
}
pub async fn conflict_detail(
&self,
base: &str,
head: &str,
path: &str,
) -> Result<String, BabyError> {
let mut client = self.context().ks.conflict();
let response = client
.get_conflict_detail(pb::GetConflictDetailRequest {
repository: self.repository_ref_some(),
base_revision: base.to_string(),
head_revision: head.to_string(),
path: path.to_string(),
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response.content)
}
pub async fn list_remotes(&self) -> Result<Vec<(String, String)>, BabyError> {
let mut client = self.context().ks.remote();
let response = client
.list_remotes(pb::ListRemotesRequest {
repository: self.repository_ref_some(),
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response
.remotes
.into_iter()
.map(|remote| (remote.name, remote.fetch_url))
.collect())
}
pub async fn remove_remote(&self, name: &str) -> Result<(), BabyError> {
let mut client = self.context().ks.remote();
client
.remove_remote(pb::RemoveRemoteRequest {
repository: self.repository_ref_some(),
name: name.to_string(),
actor: Some(self.context().actor()),
options: Some(self.context().write_options()),
})
.await
.map_err(BabyError::from)?;
Ok(())
}
pub async fn create_archive(&self, req: CreateArchiveRequest) -> Result<(), BabyError> {
let mut client = self.context().ks.archive();
client
.start_archive(pb::StartArchiveRequest {
repository: self.repository_ref_some(),
revision: req.commit,
format: archive_format(req.format) as i32,
path_filters: req.paths,
actor: Some(self.context().actor()),
idempotency_key: String::new(),
})
.await
.map_err(BabyError::from)?;
Ok(())
}
pub async fn get_archive(&self, req: GetArchiveRequest) -> Result<ArchiveArtifact, BabyError> {
let mut client = self.context().ks.archive();
let response = client
.get_archive_artifact(pb::GetArchiveArtifactRequest {
repository: self.repository_ref_some(),
revision: req.commit,
format: archive_format(req.format) as i32,
path_filters: req.exclude,
})
.await
.map_err(BabyError::from)?
.into_inner();
let artifact = response
.artifact
.ok_or_else(|| BabyError::Custom("archive artifact missing".into()))?;
Ok(ArchiveArtifact {
format: artifact.format,
size: artifact.size_bytes,
storage_key: artifact.storage_key,
download_url: artifact.download_url,
})
}
pub async fn create_bundle(&self, _req: BundleRequest) -> Result<(), BabyError> {
let mut client = self.context().ks.archive();
client
.start_archive_bundle(pb::StartArchiveBundleRequest {
repository: self.repository_ref_some(),
actor: Some(self.context().actor()),
idempotency_key: String::new(),
})
.await
.map_err(BabyError::from)?;
Ok(())
}
pub async fn init_repository(
&self,
_path: &str,
_bare: bool,
_format: Option<ObjectFormat>,
) -> Result<(), BabyError> {
let mut client = self.context().ks.command();
client
.create_repository(pb::CreateRepositoryRequest {
repository: self.repository_ref_some(),
// 默认分支由托管面决定;本地不再保存该字段。
default_branch: String::new(),
actor: Some(self.context().actor()),
options: Some(self.context().write_options()),
})
.await
.map_err(BabyError::from)?;
Ok(())
}
/// 仓库概览:默认分支、主语言、对象格式、体积与引用计数。
///
/// 这些字段的真值都在 KenShell,本地库不再保存副本。
pub async fn repository_overview(&self) -> Result<RepositoryOverviewInfo, BabyError> {
let mut client = self.context().ks.repository();
let response = client
.get_repository(pb::GetRepositoryRequest {
repository: self.repository_ref_some(),
})
.await
.map_err(BabyError::from)?
.into_inner();
let overview = response
.repository
.ok_or_else(|| BabyError::RefNotFound("repository".to_string()))?;
let summary = overview.repository.unwrap_or_default();
let branch = summary
.default_branch
.strip_prefix("refs/heads/")
.unwrap_or(&summary.default_branch)
.to_string();
Ok(RepositoryOverviewInfo {
default_branch: branch,
primary_language: Some(summary.primary_language).filter(|v| !v.is_empty()),
object_format: if overview.object_format.is_empty() {
"sha1".to_string()
} else {
overview.object_format
},
size_bytes: overview.size_bytes as i64,
branch_count: overview.branch_count as i32,
tag_count: overview.tag_count as i32,
})
}
/// 保护规则列表;`kind` 为 `branch` 或 `tag`。
pub async fn list_ref_protections(
&self,
kind: &str,
) -> Result<Vec<pb::RefProtectionView>, BabyError> {
let mut client = self.refs_client();
let response = client
.list_ref_protections(pb::ListRefProtectionsRequest {
repository: self.repository_ref_some(),
kind: if kind == "tag" {
pb::RefProtectionKind::Tag as i32
} else {
pb::RefProtectionKind::Branch as i32
},
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response.protections)
}
pub async fn create_ref_protection(
&self,
kind: &str,
pattern: &str,
rules: pb::RefProtectionRules,
whitelists: Vec<pb::RefProtectionWhitelist>,
) -> Result<pb::RefProtectionView, BabyError> {
let mut client = self.refs_client();
let response = client
.create_ref_protection(pb::CreateRefProtectionRequest {
repository: self.repository_ref_some(),
kind: if kind == "tag" {
pb::RefProtectionKind::Tag as i32
} else {
pb::RefProtectionKind::Branch as i32
},
pattern: pattern.to_string(),
rules: Some(rules),
whitelists,
})
.await
.map_err(BabyError::from)?
.into_inner();
response
.protection
.ok_or_else(|| BabyError::RefNotFound(pattern.to_string()))
}
pub async fn update_ref_protection(
&self,
id: &str,
pattern: &str,
rules: pb::RefProtectionRules,
whitelists: Vec<pb::RefProtectionWhitelist>,
) -> Result<pb::RefProtectionView, BabyError> {
let mut client = self.refs_client();
let response = client
.update_ref_protection(pb::UpdateRefProtectionRequest {
repository: self.repository_ref_some(),
id: id.to_string(),
pattern: pattern.to_string(),
rules: Some(rules),
whitelists,
})
.await
.map_err(BabyError::from)?
.into_inner();
response
.protection
.ok_or_else(|| BabyError::RefNotFound(id.to_string()))
}
pub async fn delete_ref_protection(&self, id: &str) -> Result<(), BabyError> {
let mut client = self.refs_client();
client
.delete_ref_protection(pb::DeleteRefProtectionRequest {
repository: self.repository_ref_some(),
id: id.to_string(),
})
.await
.map_err(BabyError::from)?;
Ok(())
}
pub async fn list_code_comments(
&self,
commit_sha: &str,
path: &str,
resolved: Option<bool>,
) -> Result<Vec<pb::CodeCommentView>, BabyError> {
let mut client = self.context().ks.comment();
let response = client
.list_code_comments(pb::ListCodeCommentsRequest {
repository: self.repository_ref_some(),
commit_sha: commit_sha.to_string(),
path: path.to_string(),
path_bytes: Vec::new(),
line_number: None,
resolved,
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response.comments)
}
pub async fn create_code_comment(
&self,
revision: &str,
path: &str,
line_number: u32,
body: &str,
user_id: i64,
parent_id: &str,
) -> Result<pb::CodeCommentView, BabyError> {
let mut client = self.context().ks.comment();
let response = client
.create_code_comment(pb::CreateCodeCommentRequest {
repository: self.repository_ref_some(),
revision: revision.to_string(),
path: path.to_string(),
line_number,
body: body.to_string(),
user_id,
path_bytes: Vec::new(),
parent_id: parent_id.to_string(),
})
.await
.map_err(BabyError::from)?
.into_inner();
response
.comment
.ok_or_else(|| BabyError::RefNotFound("code comment".to_string()))
}
pub async fn update_code_comment(
&self,
id: &str,
user_id: i64,
body: &str,
resolved: Option<bool>,
) -> Result<pb::CodeCommentView, BabyError> {
let mut client = self.context().ks.comment();
let response = client
.update_code_comment(pb::UpdateCodeCommentRequest {
repository: self.repository_ref_some(),
id: id.to_string(),
user_id,
body: body.to_string(),
resolved,
})
.await
.map_err(BabyError::from)?
.into_inner();
response
.comment
.ok_or_else(|| BabyError::RefNotFound("code comment".to_string()))
}
pub async fn delete_code_comment(&self, id: &str, user_id: i64) -> Result<(), BabyError> {
let mut client = self.context().ks.comment();
client
.delete_code_comment(pb::DeleteCodeCommentRequest {
repository: self.repository_ref_some(),
id: id.to_string(),
user_id,
})
.await
.map_err(BabyError::from)?;
Ok(())
}
pub async fn repo_size(&self) -> Result<u64, BabyError> {
let mut client = self.context().ks.repository();
let response = client
.get_repository_size(pb::GetRepositorySizeRequest {
repository: self.repository_ref_some(),
})
.await
.map_err(BabyError::from)?
.into_inner();
Ok(response.size_bytes)
}
pub async fn merge_base(&self, _one: &str, _two: &str) -> Result<ObjectId, BabyError> {
Err(unsupported("merge base"))
}
pub async fn merge_tree(
&self,
_ours: &str,
_theirs: &str,
_base: Option<&str>,
_force: bool,
) -> Result<crate::rpcgit::conflict::types::MergeTreeResult, BabyError> {
Err(unsupported("merge tree"))
}
pub async fn verify_tag_signature(&self, _name: &str) -> Result<TagVerifyResult, BabyError> {
Err(unsupported("tag signature verification"))
}
pub async fn list_submodules(
&self,
_commit: &str,
) -> Result<Vec<crate::rpcgit::submodule::types::SubModule>, BabyError> {
Err(unsupported("submodules"))
}
pub async fn prune(&self) -> Result<(), BabyError> {
Err(unsupported("prune"))
}
}
#[derive(Debug, Clone)]
pub struct ArchiveArtifact {
pub format: i32,
pub size: u64,
pub storage_key: String,
pub download_url: String,
}
fn tag_from_view(tag: pb::TagView) -> TagInfo {
let target = oid(&tag.target_sha)
.unwrap_or_else(|_| oid_opt("0000000000000000000000000000000000000000").expect("zero"));
TagInfo {
name: tag.name,
sha: target.clone(),
target_sha: target,
tag_type: if tag.annotated {
TagType::Annotated
} else {
TagType::Lightweight
},
tagger: if tag.tagger_name.is_empty() && tag.tagger_email.is_empty() {
None
} else {
Some(Signature {
name: tag.tagger_name,
email: tag.tagger_email,
time: tag
.created_at
.and_then(|at| time::OffsetDateTime::from_unix_timestamp(at.seconds).ok())
.unwrap_or(time::OffsetDateTime::UNIX_EPOCH),
})
},
message: if tag.message.is_empty() {
None
} else {
Some(tag.message)
},
created_at: at(tag.created_at),
}
}
fn changed_path_diff(file: pb::DiffFileView) -> ChangedPathDiff {
let mut patch = String::new();
for hunk in &file.hunks {
patch.push_str(&hunk.header);
patch.push('\n');
for line in &hunk.lines {
let prefix =
match pb::DiffLineKind::try_from(line.kind).unwrap_or(pb::DiffLineKind::Context) {
pb::DiffLineKind::Addition => "+",
pb::DiffLineKind::Deletion => "-",
pb::DiffLineKind::NoNewline => "\\",
_ => " ",
};
patch.push_str(prefix);
patch.push_str(&line.content);
patch.push('\n');
}
}
let status =
match pb::DiffFileStatus::try_from(file.status).unwrap_or(pb::DiffFileStatus::Modified) {
pb::DiffFileStatus::Added => DiffStatus::Added,
pb::DiffFileStatus::Deleted => DiffStatus::Deleted,
pb::DiffFileStatus::Renamed => DiffStatus::Renamed,
pb::DiffFileStatus::Copied => DiffStatus::Copied,
pb::DiffFileStatus::TypeChanged => DiffStatus::TypeChanged,
_ => DiffStatus::Modified,
};
let old_path = if file.old_path.is_empty() {
None
} else {
Some(PathBuf::from(file.old_path))
};
let new_path = if file.new_path.is_empty() {
old_path.clone().unwrap_or_default()
} else {
PathBuf::from(file.new_path)
};
ChangedPathDiff {
path: ChangedPath {
old_path,
new_path,
old_mode: 0,
new_mode: 0,
old_oid: oid_opt(&file.old_oid),
new_oid: oid_opt(&file.new_oid),
status,
},
lines_added: file.additions as u64,
lines_removed: file.deletions as u64,
binary: file.binary,
patch: if patch.is_empty() || file.binary {
None
} else {
Some(patch.into_bytes())
},
}
}
fn archive_format(format: ArchiveFormat) -> pb::ArchiveFormat {
match format {
ArchiveFormat::Tar => pb::ArchiveFormat::Tar,
ArchiveFormat::TarGz => pb::ArchiveFormat::TarGz,
ArchiveFormat::TarBz2 => pb::ArchiveFormat::TarBz2,
ArchiveFormat::Zip => pb::ArchiveFormat::Zip,
}
}
impl From<tonic::Status> for BabyError {
fn from(status: tonic::Status) -> Self {
match status.code() {
tonic::Code::NotFound => BabyError::RevisionNotFound(status.message().to_string()),
tonic::Code::AlreadyExists => BabyError::Custom(status.message().to_string()),
tonic::Code::Aborted | tonic::Code::FailedPrecondition => {
BabyError::RefUpdateRejected(status.message().to_string())
}
tonic::Code::InvalidArgument => {
BabyError::RevisionInvalid(status.message().to_string())
}
tonic::Code::Unauthenticated => BabyError::Custom("unauthenticated".to_string()),
tonic::Code::PermissionDenied => BabyError::Custom("permission denied".to_string()),
tonic::Code::Unimplemented => BabyError::Custom(status.message().to_string()),
_ => BabyError::Custom(format!("{}: {}", status.code(), status.message())),
}
}
}