refactor(server): replace custom remote clients with macro-based implementation

- Replaced manual remote client functions with remote_client! macro for archive, blame, branch, commit, and diff services
- Simplified remote client creation logic using declarative macro approach
- Maintained same functionality while reducing code duplication across services

security(bare): enhance path traversal protection with comprehensive validation

- Added early relative_path validation to prevent path traversal attacks
- Implemented unified path validation to avoid TOCTOU race conditions
- Enhanced canonicalization checks for both existing and non-existent paths
- Added detailed logging for path traversal detection attempts

feat(cache): migrate from CLruCache to Moka with TTL and invalidation support

- Replaced clru dependency with moka for improved caching capabilities
- Added 300-second time-to-live for cache entries
- Implemented repository-specific cache invalidation mechanism
- Enhanced cache operations with thread-safe async support

refactor(commit): improve security validation for commit operations

- Added ref name validation to prevent command injection in cherry_pick_commit
- Implemented revision validation for commit selectors
- Added comprehensive input validation for create_commit parameters
- Enhanced file path validation to prevent traversal
This commit is contained in:
zhenyi
2026-06-08 09:43:57 +08:00
parent 8c95eb230d
commit d243dce027
60 changed files with 1746 additions and 561 deletions
+8 -22
View File
@@ -1,27 +1,9 @@
use crate::pb::*;
use crate::pb::archive_service_client::ArchiveServiceClient;
use crate::pb::*;
use super::{GitksService, cache, into_status};
async fn remote_archive_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<ArchiveServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding archive rpc");
let endpoint = super::remote_endpoint(&route.grpc_addr).await?;
let client = ArchiveServiceClient::connect(endpoint)
.await
.map_err(|e| tonic::Status::unavailable(e.to_string()))?;
Ok(Some(client))
}
remote_client!(remote_archive_client, ArchiveServiceClient<tonic::transport::Channel>, "archive");
#[tonic::async_trait]
impl archive_service_server::ArchiveService for GitksService {
@@ -39,7 +21,9 @@ impl archive_service_server::ArchiveService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_archive_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_archive_client(self, inner.repository.as_ref(), false).await?
{
let resp = client.get_archive(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
@@ -64,7 +48,9 @@ impl archive_service_server::ArchiveService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_archive_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_archive_client(self, inner.repository.as_ref(), false).await?
{
return client.list_archive_entries(inner).await;
}
return Err(err);
+8 -22
View File
@@ -1,27 +1,9 @@
use crate::pb::*;
use crate::pb::blame_service_client::BlameServiceClient;
use crate::pb::*;
use super::{GitksService, cache, into_status, into_stream};
async fn remote_blame_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<BlameServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding blame rpc");
let endpoint = super::remote_endpoint(&route.grpc_addr).await?;
let client = BlameServiceClient::connect(endpoint)
.await
.map_err(|e| tonic::Status::unavailable(e.to_string()))?;
Ok(Some(client))
}
remote_client!(remote_blame_client, BlameServiceClient<tonic::transport::Channel>, "blame");
#[tonic::async_trait]
impl blame_service_server::BlameService for GitksService {
@@ -40,7 +22,9 @@ impl blame_service_server::BlameService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_blame_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_blame_client(self, inner.repository.as_ref(), false).await?
{
return client.blame(inner).await;
}
return Err(err);
@@ -70,7 +54,9 @@ impl blame_service_server::BlameService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_blame_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_blame_client(self, inner.repository.as_ref(), false).await?
{
let resp = client.stream_blame(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
+26 -28
View File
@@ -1,27 +1,9 @@
use crate::pb::*;
use crate::pb::branch_service_client::BranchServiceClient;
use crate::pb::*;
use super::{GitksService, into_status};
async fn remote_branch_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<BranchServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding branch rpc");
let endpoint = super::remote_endpoint(&route.grpc_addr).await?;
let client = BranchServiceClient::connect(endpoint)
.await
.map_err(|e| tonic::Status::unavailable(e.to_string()))?;
Ok(Some(client))
}
remote_client!(remote_branch_client, BranchServiceClient<tonic::transport::Channel>, "branch");
#[tonic::async_trait]
impl branch_service_server::BranchService for GitksService {
@@ -36,7 +18,9 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), false).await?
{
return client.list_branches(inner).await;
}
return Err(err);
@@ -60,7 +44,9 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), false).await?
{
return client.get_branch(inner).await;
}
return Err(err);
@@ -83,7 +69,9 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), true).await?
{
return client.create_branch(inner).await;
}
return Err(err);
@@ -108,7 +96,9 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), true).await?
{
return client.delete_branch(inner).await;
}
return Err(err);
@@ -134,7 +124,9 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), true).await?
{
return client.rename_branch(inner).await;
}
return Err(err);
@@ -159,7 +151,9 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), true).await?
{
return client.update_branch_target(inner).await;
}
return Err(err);
@@ -184,7 +178,9 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), true).await?
{
return client.set_branch_upstream(inner).await;
}
return Err(err);
@@ -210,7 +206,9 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_branch_client(self, inner.repository.as_ref(), false).await?
{
return client.compare_branch(inner).await;
}
return Err(err);
+80 -30
View File
@@ -1,22 +1,22 @@
use std::num::NonZeroUsize;
use std::sync::{Mutex, OnceLock};
use std::sync::OnceLock;
use std::time::Duration;
use clru::CLruCache;
use moka::sync::Cache;
use prost::Message;
use crate::pb::{ObjectSelector, object_selector};
const GLOBAL_CACHE_MAX: usize = 65_545;
const GLOBAL_CACHE_MAX: u64 = 65_536;
const CACHE_TTL: Duration = Duration::from_secs(300);
type Cache = CLruCache<Vec<u8>, Vec<u8>>;
static GLOBAL_CACHE: OnceLock<Cache<Vec<u8>, Vec<u8>>> = OnceLock::new();
static GLOBAL_CACHE: OnceLock<Mutex<Cache>> = OnceLock::new();
fn cache() -> &'static Mutex<Cache> {
fn cache() -> &'static Cache<Vec<u8>, Vec<u8>> {
GLOBAL_CACHE.get_or_init(|| {
let capacity =
NonZeroUsize::new(GLOBAL_CACHE_MAX).expect("cache capacity must be non-zero");
Mutex::new(CLruCache::new(capacity))
Cache::builder()
.max_capacity(GLOBAL_CACHE_MAX)
.time_to_live(CACHE_TTL)
.build()
})
}
@@ -45,11 +45,7 @@ where
{
let key = cache_key(namespace, request);
if let Some(bytes) = cache()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&key)
.cloned()
if let Some(bytes) = cache().get(&key)
&& let Ok(response) = Res::decode(bytes.as_slice())
{
tracing::debug!(
@@ -70,10 +66,7 @@ where
response
.encode(&mut bytes)
.expect("encoding a prost message into Vec cannot fail");
cache()
.lock()
.unwrap_or_else(|e| e.into_inner())
.put(key, bytes);
cache().insert(key, bytes);
Ok(response)
}
@@ -89,12 +82,7 @@ where
{
let key = cache_key(namespace, request);
if let Some(bytes) = cache()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&key)
.cloned()
{
if let Some(bytes) = cache().get(&key) {
let mut remaining = bytes.as_slice();
let mut items = Vec::new();
let mut valid = true;
@@ -133,13 +121,75 @@ where
item.encode_length_delimited(&mut bytes)
.expect("encoding a prost message into Vec cannot fail");
}
cache()
.lock()
.unwrap_or_else(|e| e.into_inner())
.put(key, bytes);
cache().insert(key, bytes);
Ok(response)
}
/// Invalidate all cache entries related to a specific repository.
/// Called when refs are updated (create branch, create commit, etc.)
/// so that stale data is not served.
pub(crate) fn invalidate_repo(relative_path: &str) {
let c = cache();
// Encode the relative_path to match how it appears in cache keys
let target_path_bytes = relative_path.as_bytes();
// Remove all keys that reference this repository
// Cache keys are: namespace\0 + prost-encoded request
let keys_to_remove: Vec<std::sync::Arc<Vec<u8>>> = c
.iter()
.filter_map(|(key, _)| {
// Find the null byte separator
if let Some(null_pos) = key.iter().position(|&b| b == 0) {
let encoded_request = &key[null_pos + 1..];
// Check if this encoded request contains the repository path
// We use a sliding window to find the path bytes in the encoded protobuf
// This is conservative but correct: we may invalidate slightly more than
// necessary, but we won't miss any entries for this repository.
//
// The encoded protobuf format embeds string fields as length-prefixed data,
// so the relative_path bytes should appear verbatim somewhere in the message.
if contains_subslice(encoded_request, target_path_bytes) {
return Some(key);
}
} else {
// Malformed key without separator, remove it to be safe
tracing::warn!("found cache key without null separator, removing");
return Some(key);
}
None
})
.collect();
let removed = keys_to_remove.len();
for key in keys_to_remove {
c.invalidate(key.as_ref());
}
if removed > 0 {
tracing::debug!(
relative_path = %relative_path,
entries_removed = removed,
"cache invalidated for repository"
);
}
}
/// Check if a byte slice contains a subslice
fn contains_subslice(haystack: &[u8], needle: &[u8]) -> bool {
if needle.is_empty() {
return true;
}
if needle.len() > haystack.len() {
return false;
}
haystack
.windows(needle.len())
.any(|window| window == needle)
}
pub(crate) fn selector_is_oid(selector: &Option<ObjectSelector>) -> bool {
matches!(
selector.as_ref().and_then(|s| s.selector.as_ref()),
+26 -28
View File
@@ -1,27 +1,9 @@
use crate::pb::*;
use crate::pb::commit_service_client::CommitServiceClient;
use crate::pb::*;
use super::{GitksService, cache, into_status};
async fn remote_commit_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<CommitServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding commit rpc");
let endpoint = super::remote_endpoint(&route.grpc_addr).await?;
let client = CommitServiceClient::connect(endpoint)
.await
.map_err(|e| tonic::Status::unavailable(e.to_string()))?;
Ok(Some(client))
}
remote_client!(remote_commit_client, CommitServiceClient<tonic::transport::Channel>, "commit");
#[tonic::async_trait]
impl commit_service_server::CommitService for GitksService {
@@ -36,7 +18,9 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), false).await?
{
return client.list_commits(inner).await;
}
return Err(err);
@@ -65,7 +49,9 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), false).await?
{
return client.get_commit(inner).await;
}
return Err(err);
@@ -93,7 +79,9 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), false).await?
{
return client.get_commit_ancestors(inner).await;
}
return Err(err);
@@ -123,7 +111,9 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), true).await?
{
return client.create_commit(inner).await;
}
return Err(err);
@@ -131,7 +121,9 @@ impl commit_service_server::CommitService for GitksService {
Err(err) => return Err(err),
};
let resp = gb.create_commit(inner).map_err(into_status)?;
let commit_hex = resp.commit.as_ref()
let commit_hex = resp
.commit
.as_ref()
.and_then(|c| c.oid.as_ref().map(|o| o.hex.as_str()).or(Some("?")))
.unwrap_or("?");
tracing::info!(%repo, %branch, %commit_hex, "commit created");
@@ -151,7 +143,9 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), true).await?
{
return client.revert_commit(inner).await;
}
return Err(err);
@@ -176,7 +170,9 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), true).await?
{
return client.cherry_pick_commit(inner).await;
}
return Err(err);
@@ -200,7 +196,9 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_commit_client(self, inner.repository.as_ref(), false).await?
{
return client.compare_commits(inner).await;
}
return Err(err);
+14 -24
View File
@@ -1,27 +1,9 @@
use crate::pb::*;
use crate::pb::diff_service_client::DiffServiceClient;
use crate::pb::*;
use super::{GitksService, cache, into_status, into_stream};
async fn remote_diff_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<DiffServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding diff rpc");
let endpoint = super::remote_endpoint(&route.grpc_addr).await?;
let client = DiffServiceClient::connect(endpoint)
.await
.map_err(|e| tonic::Status::unavailable(e.to_string()))?;
Ok(Some(client))
}
remote_client!(remote_diff_client, DiffServiceClient<tonic::transport::Channel>, "diff");
#[tonic::async_trait]
impl diff_service_server::DiffService for GitksService {
@@ -39,7 +21,9 @@ impl diff_service_server::DiffService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_diff_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_diff_client(self, inner.repository.as_ref(), false).await?
{
return client.get_diff(inner).await;
}
return Err(err);
@@ -68,7 +52,9 @@ impl diff_service_server::DiffService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_diff_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_diff_client(self, inner.repository.as_ref(), false).await?
{
return client.get_commit_diff(inner).await;
}
return Err(err);
@@ -97,7 +83,9 @@ impl diff_service_server::DiffService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_diff_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_diff_client(self, inner.repository.as_ref(), false).await?
{
let resp = client.get_patch(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
@@ -127,7 +115,9 @@ impl diff_service_server::DiffService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_diff_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_diff_client(self, inner.repository.as_ref(), false).await?
{
return client.get_diff_stats(inner).await;
}
return Err(err);
+17 -25
View File
@@ -1,27 +1,9 @@
use crate::pb::*;
use crate::pb::merge_service_client::MergeServiceClient;
use crate::pb::*;
use super::{GitksService, into_status};
async fn remote_merge_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<MergeServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding merge rpc");
let endpoint = super::remote_endpoint(&route.grpc_addr).await?;
let client = MergeServiceClient::connect(endpoint)
.await
.map_err(|e| tonic::Status::unavailable(e.to_string()))?;
Ok(Some(client))
}
remote_client!(remote_merge_client, MergeServiceClient<tonic::transport::Channel>, "merge");
#[tonic::async_trait]
impl merge_service_server::MergeService for GitksService {
@@ -36,7 +18,9 @@ impl merge_service_server::MergeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_merge_client(self, inner.repository.as_ref(), false).await?
{
return client.check_merge(inner).await;
}
return Err(err);
@@ -60,7 +44,9 @@ impl merge_service_server::MergeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_merge_client(self, inner.repository.as_ref(), true).await?
{
return client.merge(inner).await;
}
return Err(err);
@@ -84,7 +70,9 @@ impl merge_service_server::MergeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_merge_client(self, inner.repository.as_ref(), false).await?
{
return client.list_merge_conflicts(inner).await;
}
return Err(err);
@@ -108,7 +96,9 @@ impl merge_service_server::MergeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_merge_client(self, inner.repository.as_ref(), true).await?
{
return client.resolve_merge_conflicts(inner).await;
}
return Err(err);
@@ -133,7 +123,9 @@ impl merge_service_server::MergeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_merge_client(self, inner.repository.as_ref(), true).await?
{
return client.rebase(inner).await;
}
return Err(err);
+97 -22
View File
@@ -1,3 +1,35 @@
/// Generate a `remote_<service>_client` helper function that resolves a repository
/// route and returns a connected gRPC client for the given service.
macro_rules! remote_client {
($fn_name:ident, $client:ty, $svc_label:literal) => {
async fn $fn_name(
svc: &super::GitksService,
header: Option<&crate::pb::RepositoryHeader>,
is_write: bool,
) -> Result<Option<$client>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(
storage_name = %route.storage_name,
relative_path = %route.relative_path,
actor_name = %route.actor_name,
grpc_addr = %route.grpc_addr,
concat!("forwarding ", $svc_label, " rpc")
);
let endpoint = super::remote_endpoint(&route.grpc_addr).await?;
let client = <$client>::connect(endpoint)
.await
.map_err(|e| tonic::Status::unavailable(e.to_string()))?;
Ok(Some(client))
}
};
}
mod archive;
mod blame;
mod branch;
@@ -11,9 +43,9 @@ mod repository_maint;
mod tag;
mod tree;
use std::path::{Path, PathBuf};
use gix::discover::is_git;
use ractor::{ActorCell, ActorRef};
use std::path::{Path, PathBuf};
use tokio_stream::wrappers::ReceiverStream;
use crate::actor::message::{GitNodeMessage, RouteDecision};
@@ -34,7 +66,11 @@ pub struct GitksService {
impl GitksService {
pub fn new(repo_prefix: PathBuf) -> Self {
Self { repo_prefix, node_actor: None, grpc_addr: String::new() }
Self {
repo_prefix,
node_actor: None,
grpc_addr: String::new(),
}
}
pub fn with_actor(mut self, node_actor: ActorRef<GitNodeMessage>) -> Self {
@@ -74,20 +110,23 @@ impl GitksService {
if local.as_ref().is_some_and(|actor| actor == &member) {
continue;
}
if let Some(decision) = query_find_primary(member.clone(), header.clone()).await? {
if decision.found && !decision.grpc_addr.is_empty() {
primary = Some(decision);
if is_write {
return Ok(primary);
}
if let Some(decision) = query_find_primary(member.clone(), header.clone()).await?
&& decision.found
&& !decision.grpc_addr.is_empty()
{
primary = Some(decision);
if is_write {
return Ok(primary);
}
}
if !is_write && replica.is_none() {
if let Some(decision) = query_find_replica(member.clone(), header.clone()).await? {
if decision.found && !decision.grpc_addr.is_empty() && decision.role == ROLE_REPLICA {
replica = Some(decision);
}
}
if !is_write
&& replica.is_none()
&& let Some(decision) = query_find_replica(member.clone(), header.clone()).await?
&& decision.found
&& !decision.grpc_addr.is_empty()
&& decision.role == ROLE_REPLICA
{
replica = Some(decision);
}
}
if let Some(p) = primary {
@@ -142,20 +181,56 @@ impl GitksService {
if relative_path.is_empty() {
return Err(tonic::Status::invalid_argument("relative_path is required"));
}
// Validate early to reject '..' and other traversal patterns
crate::sanitize::validate_relative_path(relative_path)
.map_err(|e| tonic::Status::invalid_argument(e.to_string()))?;
let candidate = self.repo_prefix.join(relative_path);
// Path traversal check
let canonical = candidate
.canonicalize()
.unwrap_or_else(|_| candidate.clone());
// Canonicalize repo_prefix (which should exist) for a reliable check
let prefix_canon = self
.repo_prefix
.canonicalize()
.unwrap_or_else(|_| self.repo_prefix.clone());
// Unified path validation to avoid TOCTOU
let canonical = match candidate.canonicalize() {
Ok(canon) => {
// Path exists and was canonicalized
canon
}
Err(_) => {
// Path doesn't exist yet — validate via parent
let parent = candidate.parent().unwrap_or(&self.repo_prefix);
let filename = candidate.file_name().ok_or_else(|| {
tonic::Status::invalid_argument("invalid path: missing filename")
})?;
let parent_canon = parent
.canonicalize()
.unwrap_or_else(|_| parent.to_path_buf());
let constructed = parent_canon.join(filename);
// String-level verification for non-existent paths
let constructed_str = constructed.to_string_lossy();
let prefix_str = prefix_canon.to_string_lossy();
if !constructed_str.starts_with(&*prefix_str) {
return Err(tonic::Status::invalid_argument(
"path traversal detected: relative_path escapes repo prefix",
));
}
constructed
}
};
// Final check: canonical must be under prefix
if !canonical.starts_with(&prefix_canon) {
return Err(tonic::Status::invalid_argument(
"path traversal detected: relative_path escapes repo prefix",
));
}
Ok(canonical)
}
@@ -166,6 +241,9 @@ impl GitksService {
old_oid: &str,
new_oid: &str,
) {
// Invalidate caches that depend on this repository
crate::server::cache::invalidate_repo(relative_path);
if let Some(ref actor) = self.node_actor {
let event = crate::actor::message::RefUpdateEvent {
relative_path: relative_path.to_string(),
@@ -189,14 +267,11 @@ impl GitksService {
}
}
pub async fn remote_endpoint(addr: &str) -> Result<tonic::transport::Endpoint, tonic::Status> {
let uri: tonic::codegen::http::Uri = addr
.parse()
.map_err(|e| tonic::Status::invalid_argument(format!("invalid URI: {e}")))?;
tonic::transport::Endpoint::new(uri)
.map_err(|e| tonic::Status::internal(e.to_string()))
tonic::transport::Endpoint::new(uri).map_err(|e| tonic::Status::internal(e.to_string()))
}
pub(super) fn bridge_server_stream<T: Send + 'static>(
+43 -31
View File
@@ -1,30 +1,12 @@
use tokio_stream::StreamExt;
use tokio_stream::wrappers::ReceiverStream;
use crate::pb::*;
use crate::pb::pack_service_client::PackServiceClient;
use crate::pb::*;
use super::{GitksService, into_status};
async fn remote_pack_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<PackServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding pack rpc");
let endpoint = super::remote_endpoint(&route.grpc_addr).await?;
let client = PackServiceClient::connect(endpoint)
.await
.map_err(|e| tonic::Status::unavailable(e.to_string()))?;
Ok(Some(client))
}
remote_client!(remote_pack_client, PackServiceClient<tonic::transport::Channel>, "pack");
#[tonic::async_trait]
impl pack_service_server::PackService for GitksService {
@@ -43,7 +25,9 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_pack_client(self, inner.repository.as_ref(), false).await?
{
return client.advertise_refs(inner).await;
}
return Err(err);
@@ -70,19 +54,27 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(first.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, first.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_pack_client(self, first.repository.as_ref(), false).await?
{
let (tx, rx) = tokio::sync::mpsc::channel(16);
let _ = tx.send(first).await;
tokio::spawn(async move {
use tokio_stream::StreamExt;
while let Some(msg) = stream.next().await {
match msg {
Ok(m) => { if tx.send(m).await.is_err() { break; } }
Ok(m) => {
if tx.send(m).await.is_err() {
break;
}
}
Err(_) => break,
}
}
});
let resp = client.upload_pack(tokio_stream::wrappers::ReceiverStream::new(rx)).await?;
let resp = client
.upload_pack(tokio_stream::wrappers::ReceiverStream::new(rx))
.await?;
let out = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(out));
}
@@ -123,19 +115,27 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(first.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, first.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_pack_client(self, first.repository.as_ref(), false).await?
{
let (tx, rx) = tokio::sync::mpsc::channel(16);
let _ = tx.send(first).await;
tokio::spawn(async move {
use tokio_stream::StreamExt;
while let Some(msg) = stream.next().await {
match msg {
Ok(m) => { if tx.send(m).await.is_err() { break; } }
Ok(m) => {
if tx.send(m).await.is_err() {
break;
}
}
Err(_) => break,
}
}
});
let resp = client.receive_pack(tokio_stream::wrappers::ReceiverStream::new(rx)).await?;
let resp = client
.receive_pack(tokio_stream::wrappers::ReceiverStream::new(rx))
.await?;
let out = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(out));
}
@@ -172,7 +172,9 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_pack_client(self, inner.repository.as_ref(), false).await?
{
let resp = client.pack_objects(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
@@ -201,7 +203,13 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(inputs.first().and_then(|r| r.repository.as_ref())) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, inputs.first().and_then(|r| r.repository.as_ref()), false).await? {
if let Some(mut client) = remote_pack_client(
self,
inputs.first().and_then(|r| r.repository.as_ref()),
false,
)
.await?
{
return client.index_pack(tokio_stream::iter(inputs)).await;
}
return Err(err);
@@ -224,7 +232,9 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_pack_client(self, inner.repository.as_ref(), false).await?
{
return client.list_packfiles(inner).await;
}
return Err(err);
@@ -247,7 +257,9 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_pack_client(self, inner.repository.as_ref(), false).await?
{
return client.fsck(inner).await;
}
return Err(err);
+50 -40
View File
@@ -1,27 +1,9 @@
use crate::pb::*;
use crate::pb::repository_service_client::RepositoryServiceClient;
use crate::pb::*;
use super::{GitksService, git_cmd, into_status, repository_maint, remote_endpoint};
use super::{GitksService, git_cmd, into_status, repository_maint};
async fn remote_repository_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<RepositoryServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding repository rpc");
let endpoint = remote_endpoint(&route.grpc_addr).await?;
let client = RepositoryServiceClient::connect(endpoint)
.await
.map_err(|e| tonic::Status::unavailable(e.to_string()))?;
Ok(Some(client))
}
remote_client!(remote_repository_client, RepositoryServiceClient<tonic::transport::Channel>, "repository");
fn default_branch_name(gb: &crate::bare::GitBare) -> String {
git_cmd(gb, &["symbolic-ref", "HEAD"])
@@ -48,7 +30,9 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), false).await?
{
return client.get_repository(inner).await;
}
return Err(err);
@@ -95,10 +79,11 @@ impl repository_service_server::RepositoryService for GitksService {
let span = tracing::info_span!("repo.delete_repository", %repo);
let _enter = span.enter();
let bare_dir = self.resolve_for_init(inner.repository.as_ref())?;
if !bare_dir.exists() {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), true).await? {
return client.delete_repository(inner).await;
}
if !bare_dir.exists()
&& let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), true).await?
{
return client.delete_repository(inner).await;
}
tracing::warn!(%repo, path = %bare_dir.display(), "deleting repository");
std::fs::remove_dir_all(&bare_dir).map_err(|e| tonic::Status::internal(e.to_string()))?;
@@ -117,10 +102,11 @@ impl repository_service_server::RepositoryService for GitksService {
let _enter = span.enter();
let bare_dir = self.resolve_for_init(inner.repository.as_ref())?;
let exists = bare_dir.exists() && bare_dir.is_dir() && bare_dir.join("HEAD").exists();
if !exists {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
return client.repository_exists(inner).await;
}
if !exists
&& let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), false).await?
{
return client.repository_exists(inner).await;
}
Ok(tonic::Response::new(RepositoryExistsResponse { exists }))
}
@@ -136,7 +122,9 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), false).await?
{
return client.get_object_format(inner).await;
}
return Err(err);
@@ -159,7 +147,9 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), false).await?
{
return client.get_default_branch(inner).await;
}
return Err(err);
@@ -183,7 +173,9 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), true).await?
{
return client.set_default_branch(inner).await;
}
return Err(err);
@@ -213,7 +205,9 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), false).await?
{
return client.get_repository_config(inner).await;
}
return Err(err);
@@ -238,6 +232,8 @@ impl repository_service_server::RepositoryService for GitksService {
}
} else {
for key in &inner.keys {
crate::sanitize::validate_config_key(key)
.map_err(|e| tonic::Status::invalid_argument(e.to_string()))?;
let out = git_cmd(&gb, &["config", "--get-all", key])?;
if out.status.success() {
let vals: Vec<String> = String::from_utf8_lossy(&out.stdout)
@@ -270,7 +266,9 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), true).await?
{
return client.set_repository_config(inner).await;
}
return Err(err);
@@ -278,6 +276,8 @@ impl repository_service_server::RepositoryService for GitksService {
Err(err) => return Err(err),
};
for entry in &inner.entries {
crate::sanitize::validate_config_key(&entry.key)
.map_err(|e| tonic::Status::invalid_argument(e.to_string()))?;
if entry.values.is_empty() {
git_cmd(&gb, &["config", "--unset-all", &entry.key])?;
} else {
@@ -305,7 +305,9 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), false).await?
{
return client.get_repository_statistics(inner).await;
}
return Err(err);
@@ -326,7 +328,9 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), false).await?
{
return client.check_repository_health(inner).await;
}
return Err(err);
@@ -349,7 +353,9 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), true).await?
{
return client.garbage_collect(inner).await;
}
return Err(err);
@@ -372,7 +378,9 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), true).await?
{
return client.repack(inner).await;
}
return Err(err);
@@ -400,7 +408,9 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_repository_client(self, inner.repository.as_ref(), true).await?
{
return client.write_commit_graph(inner).await;
}
return Err(err);
+8 -11
View File
@@ -39,17 +39,14 @@ fn dir_size(gb: &crate::bare::GitBare) -> u64 {
}
fn count_refs(gb: &crate::bare::GitBare) -> u64 {
let out = git_cmd(gb, &["for-each-ref", "--format=%(refname)"]).unwrap_or_else(|_| {
std::process::Output {
status: Default::default(),
stdout: Vec::new(),
stderr: Vec::new(),
}
});
String::from_utf8_lossy(&out.stdout)
.lines()
.filter(|l| !l.is_empty())
.count() as u64
let out = git_cmd(gb, &["for-each-ref", "--format=%(refname)"]).ok();
out.map(|o| {
String::from_utf8_lossy(&o.stdout)
.lines()
.filter(|l| !l.is_empty())
.count() as u64
})
.unwrap_or(0)
}
fn file_len(path: &std::path::Path) -> u64 {
+17 -25
View File
@@ -1,27 +1,9 @@
use crate::pb::*;
use crate::pb::tag_service_client::TagServiceClient;
use crate::pb::*;
use super::{GitksService, into_status};
async fn remote_tag_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<TagServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding tag rpc");
let endpoint = super::remote_endpoint(&route.grpc_addr).await?;
let client = TagServiceClient::connect(endpoint)
.await
.map_err(|e| tonic::Status::unavailable(e.to_string()))?;
Ok(Some(client))
}
remote_client!(remote_tag_client, TagServiceClient<tonic::transport::Channel>, "tag");
#[tonic::async_trait]
impl tag_service_server::TagService for GitksService {
@@ -36,7 +18,9 @@ impl tag_service_server::TagService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_tag_client(self, inner.repository.as_ref(), false).await?
{
return client.list_tags(inner).await;
}
return Err(err);
@@ -60,7 +44,9 @@ impl tag_service_server::TagService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_tag_client(self, inner.repository.as_ref(), false).await?
{
return client.get_tag(inner).await;
}
return Err(err);
@@ -83,7 +69,9 @@ impl tag_service_server::TagService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_tag_client(self, inner.repository.as_ref(), true).await?
{
return client.create_tag(inner).await;
}
return Err(err);
@@ -108,7 +96,9 @@ impl tag_service_server::TagService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref(), true).await? {
if let Some(mut client) =
remote_tag_client(self, inner.repository.as_ref(), true).await?
{
return client.delete_tag(inner).await;
}
return Err(err);
@@ -133,7 +123,9 @@ impl tag_service_server::TagService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_tag_client(self, inner.repository.as_ref(), false).await?
{
return client.verify_tag(inner).await;
}
return Err(err);
+20 -26
View File
@@ -1,27 +1,9 @@
use crate::pb::*;
use crate::pb::tree_service_client::TreeServiceClient;
use crate::pb::*;
use super::{GitksService, cache, into_status, into_stream};
async fn remote_tree_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<TreeServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding tree rpc");
let endpoint = super::remote_endpoint(&route.grpc_addr).await?;
let client = TreeServiceClient::connect(endpoint)
.await
.map_err(|e| tonic::Status::unavailable(e.to_string()))?;
Ok(Some(client))
}
remote_client!(remote_tree_client, TreeServiceClient<tonic::transport::Channel>, "tree");
#[tonic::async_trait]
impl tree_service_server::TreeService for GitksService {
@@ -39,7 +21,9 @@ impl tree_service_server::TreeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_tree_client(self, inner.repository.as_ref(), false).await?
{
return client.list_tree(inner).await;
}
return Err(err);
@@ -68,7 +52,9 @@ impl tree_service_server::TreeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_tree_client(self, inner.repository.as_ref(), false).await?
{
return client.get_tree(inner).await;
}
return Err(err);
@@ -97,7 +83,9 @@ impl tree_service_server::TreeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_tree_client(self, inner.repository.as_ref(), false).await?
{
return client.get_blob(inner).await;
}
return Err(err);
@@ -125,7 +113,9 @@ impl tree_service_server::TreeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_tree_client(self, inner.repository.as_ref(), false).await?
{
let resp = client.get_raw_blob(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
@@ -159,7 +149,9 @@ impl tree_service_server::TreeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_tree_client(self, inner.repository.as_ref(), false).await?
{
return client.get_file_metadata(inner).await;
}
return Err(err);
@@ -187,7 +179,9 @@ impl tree_service_server::TreeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref(), false).await? {
if let Some(mut client) =
remote_tree_client(self, inner.repository.as_ref(), false).await?
{
return client.find_files(inner).await;
}
return Err(err);