feat(gateway): implement remote service forwarding for distributed git operations

- Add remote client functions for archive, blame, and branch services
- Implement fallback logic to forward requests to remote storage nodes
- Add logging for forwarding operations with route details
- Update Cargo.lock with new dependencies including ractor cluster libraries
- Extend .gitignore with IDE and build system files
- Remove outdated comments from bare repository implementation
This commit is contained in:
zhenyi
2026-06-08 01:21:20 +08:00
parent 5b740eecd7
commit 5c99b27421
22 changed files with 2015 additions and 98 deletions
+42 -2
View File
@@ -1,7 +1,27 @@
use crate::pb::*;
use crate::pb::archive_service_client::ArchiveServiceClient;
use super::{GitksService, cache, into_status};
async fn remote_archive_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
) -> 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).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))
}
#[tonic::async_trait]
impl archive_service_server::ArchiveService for GitksService {
type GetArchiveStream =
@@ -15,7 +35,18 @@ impl archive_service_server::ArchiveService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("archive.get_archive", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
let resp = client.get_archive(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
}
return Err(err);
}
Err(err) => return Err(err),
};
let stream = gb.get_archive_stream(inner)?;
tracing::info!(%repo, "archive streaming started");
Ok(tonic::Response::new(stream))
@@ -29,7 +60,16 @@ impl archive_service_server::ArchiveService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("archive.list_archive_entries", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.list_archive_entries(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.treeish) {
cache::cached_response("archive.list_archive_entries", &inner, || {
gb.list_archive_entries(inner.clone()).map_err(into_status)
+42 -2
View File
@@ -1,7 +1,27 @@
use crate::pb::*;
use crate::pb::blame_service_client::BlameServiceClient;
use super::{GitksService, cache, into_status, into_stream};
async fn remote_blame_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
) -> 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).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))
}
#[tonic::async_trait]
impl blame_service_server::BlameService for GitksService {
type StreamBlameStream =
@@ -16,7 +36,16 @@ impl blame_service_server::BlameService for GitksService {
let path = inner.path.clone();
let span = tracing::info_span!("blame.blame", %repo, %path);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.blame(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("blame.blame", &inner, || {
gb.blame(inner.clone()).map_err(into_status)
@@ -37,7 +66,18 @@ impl blame_service_server::BlameService for GitksService {
let path = inner.path.clone();
let span = tracing::info_span!("blame.stream_blame", %repo, %path);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
let resp = client.stream_blame(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("blame.blame", &inner, || {
gb.blame(inner.clone()).map_err(into_status)
+100 -8
View File
@@ -1,7 +1,27 @@
use crate::pb::*;
use crate::pb::branch_service_client::BranchServiceClient;
use super::{GitksService, into_status};
async fn remote_branch_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
) -> 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).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))
}
#[tonic::async_trait]
impl branch_service_server::BranchService for GitksService {
async fn list_branches(
@@ -12,7 +32,16 @@ impl branch_service_server::BranchService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("branch.list_branches", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.list_branches(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.list_branches(inner).map_err(into_status)?;
tracing::info!(%repo, count = resp.branches.len(), "list_branches done");
Ok(tonic::Response::new(resp))
@@ -27,7 +56,16 @@ impl branch_service_server::BranchService for GitksService {
let name = inner.name.clone();
let span = tracing::info_span!("branch.get_branch", %repo, %name);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_branch(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.get_branch(inner).map_err(into_status)?;
Ok(tonic::Response::new(resp))
}
@@ -41,7 +79,16 @@ impl branch_service_server::BranchService for GitksService {
let name = inner.name.clone();
let span = tracing::info_span!("branch.create_branch", %repo, %name);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.create_branch(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.create_branch(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "branch created");
Ok(tonic::Response::new(resp))
@@ -56,7 +103,16 @@ impl branch_service_server::BranchService for GitksService {
let name = inner.name.clone();
let span = tracing::info_span!("branch.delete_branch", %repo, %name);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.delete_branch(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
gb.delete_branch(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "branch deleted");
Ok(tonic::Response::new(()))
@@ -72,7 +128,16 @@ impl branch_service_server::BranchService for GitksService {
let new = inner.new_name.clone();
let span = tracing::info_span!("branch.rename_branch", %repo, %old, %new);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.rename_branch(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.rename_branch(inner).map_err(into_status)?;
tracing::info!(%repo, old = %old, new = %new, "branch renamed");
Ok(tonic::Response::new(resp))
@@ -87,7 +152,16 @@ impl branch_service_server::BranchService for GitksService {
let name = inner.name.clone();
let span = tracing::info_span!("branch.update_branch_target", %repo, %name);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.update_branch_target(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.update_branch_target(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "branch target updated");
Ok(tonic::Response::new(resp))
@@ -102,7 +176,16 @@ impl branch_service_server::BranchService for GitksService {
let name = inner.name.clone();
let span = tracing::info_span!("branch.set_branch_upstream", %repo, %name);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.set_branch_upstream(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.set_branch_upstream(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "branch upstream set");
Ok(tonic::Response::new(resp))
@@ -118,7 +201,16 @@ impl branch_service_server::BranchService for GitksService {
let target = inner.target_branch.clone();
let span = tracing::info_span!("branch.compare_branch", %repo, %source, %target);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.compare_branch(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.compare_branch(inner).map_err(into_status)?;
tracing::info!(%repo, %source, %target, ahead = resp.ahead_by, behind = resp.behind_by, "branch compared");
Ok(tonic::Response::new(resp))
+90 -7
View File
@@ -1,7 +1,27 @@
use crate::pb::*;
use crate::pb::commit_service_client::CommitServiceClient;
use super::{GitksService, cache, into_status};
async fn remote_commit_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
) -> 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).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))
}
#[tonic::async_trait]
impl commit_service_server::CommitService for GitksService {
async fn list_commits(
@@ -12,7 +32,16 @@ impl commit_service_server::CommitService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("commit.list_commits", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.list_commits(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if !inner.all && cache::selector_is_oid(&inner.revision) {
cache::cached_response("commit.list_commits", &inner, || {
gb.list_commits(inner.clone()).map_err(into_status)
@@ -32,7 +61,16 @@ impl commit_service_server::CommitService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("commit.get_commit", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_commit(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("commit.get_commit", &inner, || {
gb.get_commit(inner.clone()).map_err(into_status)
@@ -51,7 +89,16 @@ impl commit_service_server::CommitService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("commit.get_commit_ancestors", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_commit_ancestors(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("commit.get_commit_ancestors", &inner, || {
gb.get_commit_ancestors(inner.clone()).map_err(into_status)
@@ -72,7 +119,16 @@ impl commit_service_server::CommitService for GitksService {
let branch = inner.branch.clone();
let span = tracing::info_span!("commit.create_commit", %repo, %branch);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.create_commit(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.create_commit(inner).map_err(into_status)?;
let commit_hex = resp.commit.as_ref()
.and_then(|c| c.oid.as_ref().map(|o| o.hex.as_str()).or(Some("?")))
@@ -90,7 +146,16 @@ impl commit_service_server::CommitService for GitksService {
let branch = inner.branch.clone();
let span = tracing::info_span!("commit.revert_commit", %repo, %branch);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.revert_commit(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.revert_commit(inner).map_err(into_status)?;
tracing::info!(%repo, %branch, "commit reverted");
Ok(tonic::Response::new(resp))
@@ -105,7 +170,16 @@ impl commit_service_server::CommitService for GitksService {
let branch = inner.branch.clone();
let span = tracing::info_span!("commit.cherry_pick_commit", %repo, %branch);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.cherry_pick_commit(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.cherry_pick_commit(inner).map_err(into_status)?;
tracing::info!(%repo, %branch, "commit cherry-picked");
Ok(tonic::Response::new(resp))
@@ -119,7 +193,16 @@ impl commit_service_server::CommitService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("commit.compare_commits", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.compare_commits(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selectors_are_oid(&inner.base, &inner.head) {
cache::cached_response("commit.compare_commits", &inner, || {
gb.compare_commits(inner.clone()).map_err(into_status)
+62 -4
View File
@@ -1,7 +1,27 @@
use crate::pb::*;
use crate::pb::diff_service_client::DiffServiceClient;
use super::{GitksService, cache, into_status, into_stream};
async fn remote_diff_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
) -> 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).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))
}
#[tonic::async_trait]
impl diff_service_server::DiffService for GitksService {
type GetPatchStream =
@@ -15,7 +35,16 @@ impl diff_service_server::DiffService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("diff.get_diff", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_diff(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selectors_are_oid(&inner.base, &inner.head) {
cache::cached_response("diff.get_diff", &inner, || {
gb.get_diff(inner.clone()).map_err(into_status)
@@ -35,7 +64,16 @@ impl diff_service_server::DiffService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("diff.get_commit_diff", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_commit_diff(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.commit) {
cache::cached_response("diff.get_commit_diff", &inner, || {
gb.get_commit_diff(inner.clone()).map_err(into_status)
@@ -55,7 +93,18 @@ impl diff_service_server::DiffService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("diff.get_patch", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
let resp = client.get_patch(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
}
return Err(err);
}
Err(err) => return Err(err),
};
let items = if cache::selectors_are_oid(&inner.base, &inner.head) {
cache::cached_vec_response("diff.get_patch", &inner, || {
gb.get_patch(inner.clone()).map_err(into_status)
@@ -74,7 +123,16 @@ impl diff_service_server::DiffService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("diff.get_diff_stats", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_diff_stats(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selectors_are_oid(&inner.base, &inner.head) {
cache::cached_response("diff.get_diff_stats", &inner, || {
gb.get_diff_stats(inner.clone()).map_err(into_status)
+70 -5
View File
@@ -1,7 +1,27 @@
use crate::pb::*;
use crate::pb::merge_service_client::MergeServiceClient;
use super::{GitksService, into_status};
async fn remote_merge_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
) -> 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).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))
}
#[tonic::async_trait]
impl merge_service_server::MergeService for GitksService {
async fn check_merge(
@@ -12,7 +32,16 @@ impl merge_service_server::MergeService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("merge.check_merge", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.check_merge(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.check_merge(inner).map_err(into_status)?;
tracing::info!(%repo, status = resp.status, "check_merge done");
Ok(tonic::Response::new(resp))
@@ -27,7 +56,16 @@ impl merge_service_server::MergeService for GitksService {
let target = inner.target_branch.clone();
let span = tracing::info_span!("merge.merge", %repo, %target);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.merge(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.merge(inner).map_err(into_status)?;
tracing::info!(%repo, %target, status = resp.status, "merge done");
Ok(tonic::Response::new(resp))
@@ -41,7 +79,16 @@ impl merge_service_server::MergeService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("merge.list_merge_conflicts", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.list_merge_conflicts(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.list_merge_conflicts(inner).map_err(into_status)?;
tracing::info!(%repo, conflicts = resp.conflicts.len(), "list_merge_conflicts done");
Ok(tonic::Response::new(resp))
@@ -56,7 +103,16 @@ impl merge_service_server::MergeService for GitksService {
let target = inner.target_branch.clone();
let span = tracing::info_span!("merge.resolve_merge_conflicts", %repo, %target);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.resolve_merge_conflicts(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.resolve_merge_conflicts(inner).map_err(into_status)?;
tracing::info!(%repo, %target, status = resp.status, "merge conflicts resolved");
Ok(tonic::Response::new(resp))
@@ -71,7 +127,16 @@ impl merge_service_server::MergeService for GitksService {
let branch = inner.branch.clone();
let span = tracing::info_span!("merge.rebase", %repo, %branch);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.rebase(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.rebase(inner).map_err(into_status)?;
tracing::info!(%repo, %branch, status = resp.status, "rebase done");
Ok(tonic::Response::new(resp))
+112 -5
View File
@@ -11,11 +11,14 @@ mod repository_maint;
mod tag;
mod tree;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use gix::discover::is_git;
use ractor::{ActorCell, ActorRef};
use tokio_stream::wrappers::ReceiverStream;
use crate::actor::message::{GitNodeMessage, RouteDecision};
use crate::bare::GitBare;
use crate::error::GitError;
use crate::error::{GitError, GitResult};
use crate::pb::{
archive_service_server, blame_service_server, branch_service_server, commit_service_server,
diff_service_server, merge_service_server, pack_service_server, repository_service_server,
@@ -24,11 +27,52 @@ use crate::pb::{
#[derive(Clone)]
pub struct GitksService {
/// Root prefix path for all repositories
pub repo_prefix: PathBuf,
pub node_actor: Option<ActorRef<GitNodeMessage>>,
}
impl GitksService {
pub fn new(repo_prefix: PathBuf) -> Self {
Self { repo_prefix, node_actor: None }
}
pub fn with_actor(mut self, node_actor: ActorRef<GitNodeMessage>) -> Self {
self.node_actor = Some(node_actor);
self
}
pub fn scan_all_repo(&self) -> GitResult<Vec<String>> {
let root = self.repo_prefix.as_ref();
let mut repos = Vec::new();
if is_bare_git_repo(root) {
repos.push(root.to_path_buf());
} else {
scan_bare_repos_recursively(root, &mut repos)?;
}
Ok(repos
.into_iter()
.filter_map(|path| path.to_str().map(str::to_owned))
.collect())
}
pub async fn route_repository(
&self,
header: &crate::pb::RepositoryHeader,
) -> Result<Option<RouteDecision>, tonic::Status> {
let members = ractor::pg::get_members(&"gitks_nodes".to_string());
let local = self.node_actor.as_ref().map(|actor| actor.get_cell());
for member in members {
if local.as_ref().is_some_and(|actor| actor == &member) {
continue;
}
if let Some(decision) = query_route(member, header.clone()).await? {
if decision.found && !decision.grpc_addr.is_empty() {
return Ok(Some(decision));
}
}
}
Ok(None)
}
fn repo_label(&self, header: Option<&crate::pb::RepositoryHeader>) -> String {
header
.and_then(|h| {
@@ -93,6 +137,70 @@ impl GitksService {
}
}
pub(super) 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()))
}
pub(super) fn bridge_server_stream<T: Send + 'static>(
mut remote: tonic::Streaming<T>,
) -> tokio_stream::wrappers::ReceiverStream<Result<T, tonic::Status>> {
let (tx, rx) = tokio::sync::mpsc::channel(16);
tokio::spawn(async move {
use tokio_stream::StreamExt;
while let Some(item) = remote.next().await {
if tx.send(item).await.is_err() {
break;
}
}
});
tokio_stream::wrappers::ReceiverStream::new(rx)
}
async fn query_route(
member: ActorCell,
header: crate::pb::RepositoryHeader,
) -> Result<Option<RouteDecision>, tonic::Status> {
let actor_ref: ActorRef<GitNodeMessage> = member.into();
match ractor::call_t!(actor_ref, GitNodeMessage::RouteRepository, 500, header) {
Ok(decision) => Ok(Some(decision)),
Err(err) => {
tracing::warn!(error = %err, "repository route query failed");
Ok(None)
}
}
}
fn scan_bare_repos_recursively(dir: &Path, repos: &mut Vec<PathBuf>) -> GitResult<()> {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if is_bare_git_repo(&path) {
repos.push(path);
continue;
}
if path.is_dir() {
scan_bare_repos_recursively(&path, repos)?;
}
}
Ok(())
}
fn is_bare_git_repo(path: &Path) -> bool {
match is_git(path) {
Ok(repo) => repo.is_bare(),
Err(_) => false,
}
}
pub(crate) fn into_status(e: GitError) -> tonic::Status {
match &e {
GitError::NotFound(_)
@@ -160,11 +268,10 @@ pub(crate) fn git_cmd(gb: &GitBare, args: &[&str]) -> Result<std::process::Outpu
pub async fn serve(
addr: std::net::SocketAddr,
repo_prefix: PathBuf,
svc: GitksService,
) -> Result<(), tonic::transport::Error> {
let span = tracing::info_span!("gitks.server", %addr);
let _enter = span.enter();
let svc = GitksService { repo_prefix };
tracing::info!("registering gRPC services");
let server = tonic::transport::Server::builder()
.add_service(repository_service_server::RepositoryServiceServer::new(
+118 -7
View File
@@ -2,9 +2,29 @@ use tokio_stream::StreamExt;
use tokio_stream::wrappers::ReceiverStream;
use crate::pb::*;
use crate::pb::pack_service_client::PackServiceClient;
use super::{GitksService, into_status};
async fn remote_pack_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
) -> 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).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))
}
#[tonic::async_trait]
impl pack_service_server::PackService for GitksService {
type UploadPackStream = ReceiverStream<Result<UploadPackResponse, tonic::Status>>;
@@ -19,7 +39,16 @@ impl pack_service_server::PackService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("pack.advertise_refs", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.advertise_refs(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.advertise_refs(inner).map_err(into_status)?;
tracing::info!(%repo, refs = resp.references.len(), "advertise_refs done");
Ok(tonic::Response::new(resp))
@@ -37,8 +66,30 @@ impl pack_service_server::PackService for GitksService {
let repo = self.repo_label(first.repository.as_ref());
let span = tracing::info_span!("pack.upload_pack", %repo);
let _enter = span.enter();
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()).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; } }
Err(_) => break,
}
}
});
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));
}
return Err(err);
}
Err(err) => return Err(err),
};
tracing::info!(%repo, "upload-pack streaming started");
let gb = self.resolve(first.repository.as_ref())?;
let (tx, rx) = tokio::sync::mpsc::channel(16);
tx.send(Ok(first))
@@ -68,8 +119,30 @@ impl pack_service_server::PackService for GitksService {
let repo = self.repo_label(first.repository.as_ref());
let span = tracing::info_span!("pack.receive_pack", %repo);
let _enter = span.enter();
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()).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; } }
Err(_) => break,
}
}
});
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));
}
return Err(err);
}
Err(err) => return Err(err),
};
tracing::info!(%repo, "receive-pack streaming started");
let gb = self.resolve(first.repository.as_ref())?;
let (tx, rx) = tokio::sync::mpsc::channel(16);
tx.send(Ok(first))
@@ -95,7 +168,18 @@ impl pack_service_server::PackService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("pack.pack_objects", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
let resp = client.pack_objects(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
}
return Err(err);
}
Err(err) => return Err(err),
};
let stream = gb.pack_objects(inner).await?;
tracing::info!(%repo, "pack-objects streaming started");
Ok(tonic::Response::new(stream))
@@ -113,7 +197,16 @@ impl pack_service_server::PackService for GitksService {
let repo = self.repo_label(inputs.first().and_then(|r| r.repository.as_ref()));
let span = tracing::info_span!("pack.index_pack", %repo);
let _enter = span.enter();
let gb = self.resolve(inputs.first().and_then(|r| r.repository.as_ref()))?;
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())).await? {
return client.index_pack(tokio_stream::iter(inputs)).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.index_pack(inputs).map_err(into_status)?;
tracing::info!(%repo, objects = resp.object_count, "index_pack done");
Ok(tonic::Response::new(resp))
@@ -127,7 +220,16 @@ impl pack_service_server::PackService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("pack.list_packfiles", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.list_packfiles(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.list_packfiles(inner).map_err(into_status)?;
tracing::info!(%repo, count = resp.packfiles.len(), "list_packfiles done");
Ok(tonic::Response::new(resp))
@@ -141,7 +243,16 @@ impl pack_service_server::PackService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("pack.fsck", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.fsck(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.fsck(inner).map_err(into_status)?;
tracing::info!(%repo, ok = resp.ok, errors = resp.errors.len(), warnings = resp.warnings.len(), "fsck done");
Ok(tonic::Response::new(resp))
+141 -12
View File
@@ -1,6 +1,26 @@
use crate::pb::*;
use crate::pb::repository_service_client::RepositoryServiceClient;
use super::{GitksService, git_cmd, into_status, repository_maint};
use super::{GitksService, git_cmd, into_status, repository_maint, remote_endpoint};
async fn remote_repository_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
) -> 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).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))
}
fn default_branch_name(gb: &crate::bare::GitBare) -> String {
git_cmd(gb, &["symbolic-ref", "HEAD"])
@@ -24,7 +44,16 @@ impl repository_service_server::RepositoryService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.get_repository", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_repository(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let bare = gb.bare_dir.join("HEAD").exists();
let object_format = gb.object_format();
Ok(tonic::Response::new(Repository {
@@ -64,6 +93,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()).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()))?;
tracing::info!(%repo, "repository deleted");
@@ -80,6 +114,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()).await? {
return client.repository_exists(inner).await;
}
}
Ok(tonic::Response::new(RepositoryExistsResponse { exists }))
}
@@ -91,7 +130,16 @@ impl repository_service_server::RepositoryService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.get_object_format", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_object_format(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
Ok(tonic::Response::new(RepositoryObjectFormatResponse {
object_format: gb.object_format() as i32,
}))
@@ -105,7 +153,16 @@ impl repository_service_server::RepositoryService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.get_default_branch", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_default_branch(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
Ok(tonic::Response::new(GetDefaultBranchResponse {
name: default_branch_name(&gb),
}))
@@ -120,7 +177,16 @@ impl repository_service_server::RepositoryService for GitksService {
let name = inner.name.clone();
let span = tracing::info_span!("repo.set_default_branch", %repo, %name);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.set_default_branch(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let refname = format!("refs/heads/{}", inner.name);
let out = git_cmd(&gb, &["symbolic-ref", "HEAD", &refname])?;
if !out.status.success() {
@@ -140,7 +206,16 @@ impl repository_service_server::RepositoryService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.get_repository_config", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_repository_config(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let mut entries = Vec::new();
if inner.keys.is_empty() {
let out = git_cmd(&gb, &["config", "--list"])?;
@@ -188,7 +263,16 @@ impl repository_service_server::RepositoryService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.set_repository_config", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.set_repository_config(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
for entry in &inner.entries {
if entry.values.is_empty() {
git_cmd(&gb, &["config", "--unset-all", &entry.key])?;
@@ -213,7 +297,16 @@ impl repository_service_server::RepositoryService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.get_repository_statistics", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_repository_statistics(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
Ok(tonic::Response::new(repository_maint::get_statistics(&gb)))
}
@@ -225,7 +318,16 @@ impl repository_service_server::RepositoryService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.check_repository_health", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.check_repository_health(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = repository_maint::check_health(&gb, inner.connectivity_only)?;
tracing::info!(%repo, ok = resp.ok, errors = resp.errors.len(), warnings = resp.warnings.len(), "health check done");
Ok(tonic::Response::new(resp))
@@ -239,7 +341,16 @@ impl repository_service_server::RepositoryService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.garbage_collect", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.garbage_collect(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = repository_maint::run_gc(&gb, inner.prune, inner.aggressive)?;
tracing::info!(%repo, ok = resp.ok, "gc done");
Ok(tonic::Response::new(resp))
@@ -253,7 +364,16 @@ impl repository_service_server::RepositoryService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.repack", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.repack(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = repository_maint::run_repack(
&gb,
inner.full,
@@ -272,7 +392,16 @@ impl repository_service_server::RepositoryService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("repo.write_commit_graph", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.write_commit_graph(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = repository_maint::run_commit_graph_write(&gb, inner.split, inner.replace)?;
tracing::info!(%repo, ok = resp.ok, "commit-graph write done");
Ok(tonic::Response::new(resp))
+70 -5
View File
@@ -1,7 +1,27 @@
use crate::pb::*;
use crate::pb::tag_service_client::TagServiceClient;
use super::{GitksService, into_status};
async fn remote_tag_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
) -> 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).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))
}
#[tonic::async_trait]
impl tag_service_server::TagService for GitksService {
async fn list_tags(
@@ -12,7 +32,16 @@ impl tag_service_server::TagService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("tag.list_tags", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.list_tags(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.list_tags(inner).map_err(into_status)?;
tracing::info!(%repo, count = resp.tags.len(), "list_tags done");
Ok(tonic::Response::new(resp))
@@ -27,7 +56,16 @@ impl tag_service_server::TagService for GitksService {
let name = inner.name.clone();
let span = tracing::info_span!("tag.get_tag", %repo, %name);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_tag(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.get_tag(inner).map_err(into_status)?;
Ok(tonic::Response::new(resp))
}
@@ -41,7 +79,16 @@ impl tag_service_server::TagService for GitksService {
let name = inner.name.clone();
let span = tracing::info_span!("tag.create_tag", %repo, %name);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.create_tag(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.create_tag(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "tag created");
Ok(tonic::Response::new(resp))
@@ -56,7 +103,16 @@ impl tag_service_server::TagService for GitksService {
let name = inner.name.clone();
let span = tracing::info_span!("tag.delete_tag", %repo, %name);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.delete_tag(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
gb.delete_tag(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "tag deleted");
Ok(tonic::Response::new(()))
@@ -71,7 +127,16 @@ impl tag_service_server::TagService for GitksService {
let name = inner.name.clone();
let span = tracing::info_span!("tag.verify_tag", %repo, %name);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.verify_tag(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = gb.verify_tag(inner).map_err(into_status)?;
tracing::info!(%repo, %name, verified = resp.verified, "tag verified");
Ok(tonic::Response::new(resp))
+82 -6
View File
@@ -1,7 +1,27 @@
use crate::pb::*;
use crate::pb::tree_service_client::TreeServiceClient;
use super::{GitksService, cache, into_status, into_stream};
async fn remote_tree_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
) -> 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).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))
}
#[tonic::async_trait]
impl tree_service_server::TreeService for GitksService {
type GetRawBlobStream =
@@ -15,7 +35,16 @@ impl tree_service_server::TreeService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("tree.list_tree", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.list_tree(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("tree.list_tree", &inner, || {
gb.list_tree(inner.clone()).map_err(into_status)
@@ -35,7 +64,16 @@ impl tree_service_server::TreeService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("tree.get_tree", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_tree(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("tree.get_tree", &inner, || {
gb.get_tree(inner.clone()).map_err(into_status)
@@ -55,7 +93,16 @@ impl tree_service_server::TreeService for GitksService {
let path = inner.path.clone();
let span = tracing::info_span!("tree.get_blob", %repo, %path);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_blob(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("tree.get_blob", &inner, || {
gb.get_blob(inner.clone()).map_err(into_status)
@@ -74,7 +121,18 @@ impl tree_service_server::TreeService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("tree.get_raw_blob", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
let resp = client.get_raw_blob(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
}
return Err(err);
}
Err(err) => return Err(err),
};
let items = if inner.oid.is_some() {
cache::cached_vec_response("tree.get_raw_blob", &inner, || {
gb.get_raw_blob(inner.clone()).map_err(into_status)
@@ -97,7 +155,16 @@ impl tree_service_server::TreeService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("tree.get_file_metadata", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.get_file_metadata(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("tree.get_file_metadata", &inner, || {
gb.get_file_metadata(inner.clone()).map_err(into_status)
@@ -116,7 +183,16 @@ impl tree_service_server::TreeService for GitksService {
let repo = self.repo_label(inner.repository.as_ref());
let span = tracing::info_span!("tree.find_files", %repo);
let _enter = span.enter();
let gb = self.resolve(inner.repository.as_ref())?;
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()).await? {
return client.find_files(inner).await;
}
return Err(err);
}
Err(err) => return Err(err),
};
let resp = if cache::selector_is_oid(&inner.revision) {
cache::cached_response("tree.find_files", &inner, || {
gb.find_files(inner.clone()).map_err(into_status)