gitdataai/lib/service/git
2026-05-30 01:38:40 +08:00
..
archive.rs feat: 1.0 2026-05-30 01:38:40 +08:00
blame.rs feat: 1.0 2026-05-30 01:38:40 +08:00
blob.rs feat: 1.0 2026-05-30 01:38:40 +08:00
branch.rs feat: 1.0 2026-05-30 01:38:40 +08:00
commit_status.rs feat: 1.0 2026-05-30 01:38:40 +08:00
commit.rs feat: 1.0 2026-05-30 01:38:40 +08:00
compare.rs feat: 1.0 2026-05-30 01:38:40 +08:00
contents.rs feat: 1.0 2026-05-30 01:38:40 +08:00
contributor.rs feat: 1.0 2026-05-30 01:38:40 +08:00
diff.rs feat: 1.0 2026-05-30 01:38:40 +08:00
fork.rs feat: 1.0 2026-05-30 01:38:40 +08:00
init.rs feat: 1.0 2026-05-30 01:38:40 +08:00
language.rs feat: 1.0 2026-05-30 01:38:40 +08:00
mod.rs feat: 1.0 2026-05-30 01:38:40 +08:00
protect.rs feat: 1.0 2026-05-30 01:38:40 +08:00
readme.rs feat: 1.0 2026-05-30 01:38:40 +08:00
refs.rs feat: 1.0 2026-05-30 01:38:40 +08:00
release.rs feat: 1.0 2026-05-30 01:38:40 +08:00
repo.rs feat: 1.0 2026-05-30 01:38:40 +08:00
star.rs feat: 1.0 2026-05-30 01:38:40 +08:00
tag.rs feat: 1.0 2026-05-30 01:38:40 +08:00
tree.rs feat: 1.0 2026-05-30 01:38:40 +08:00
watch.rs feat: 1.0 2026-05-30 01:38:40 +08:00
webhook.rs feat: 1.0 2026-05-30 01:38:40 +08:00

use serde::{Deserialize, Serialize};
use session::Session;
use utoipa::ToSchema;

use crate::{AppService, error::AppError};

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ReadmeDto {
    pub content: String,
    pub html: String,
}

impl AppService {
    pub async fn git_repo_readme(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
    ) -> Result<Option<ReadmeDto>, AppError> {
        let repo = self.git_require_member(ctx, wk_name, repo_name).await?;

        let readme_names = [
            "README.md",
            "README.markdown",
            "README.txt",
            "README",
            "Readme.md",
            "readme.md",
        ];

        for name in &readme_names {
            match self
                .git_tree_entry_by_path_from_commit_for_readme(
                    &repo.id,
                    name,
                )
                .await?
            {
                Some((content, oid)) => {
                    return self
                        .git_blob_load_for_readme(
                            &repo, &content, &oid,
                        )
                        .await;
                }
                None => continue,
            }
        }

        Ok(None)
    }
}
impl AppService {
    async fn git_tree_entry_by_path_from_commit_for_readme(
        &self,
        repo_id: &uuid::Uuid,
        readme_name: &str,
    ) -> Result<Option<(String, String)>, AppError> {
        use git::rpc::proto as p;
        use git::rpc::proto::tree_service_client::TreeServiceClient;
        use crate::git::rpc_err;

        let mut client = TreeServiceClient::new(self.git.clone());
        let mut commit_client =
            git::rpc::proto::commit_service_client::CommitServiceClient::new(self.git.clone());
        let summary_resp = commit_client
            .commit_summary(tonic::Request::new(p::CommitSummaryRequest {
                repo_id: repo_id.to_string(),
            }))
            .await
            .map_err(rpc_err)?
            .into_inner();

        let head_commit = match summary_resp.summary.and_then(|s| s.head) {
            Some(c) => c,
            None => return Ok(None),
        };

        let tree_id = match head_commit.tree_id {
            Some(id) => id.value,
            None => return Ok(None),
        };

        let resp = client
            .tree_entry_by_path(tonic::Request::new(p::TreeEntryByPathRequest {
                repo_id: repo_id.to_string(),
                tree_oid: Some(p::ObjectId { value: tree_id.clone() }),
                path: readme_name.to_string(),
            }))
            .await
            .map_err(rpc_err)?
            .into_inner();

        match resp.entry {
            Some(entry) => {
                let oid = entry.oid.map(|o| o.value).unwrap_or_default();
                if oid.is_empty() {
                    Ok(None)
                } else {
                    Ok(Some((readme_name.to_string(), oid)))
                }
            }
            None => Ok(None),
        }
    }
    async fn git_blob_load_for_readme(
        &self,
        repo: &model::repos::RepoModel,
        _path: &str,
        oid: &str,
    ) -> Result<Option<super::readme::ReadmeDto>, AppError> {
        use git::rpc::proto as p;
        use git::rpc::proto::blob_service_client::BlobServiceClient;
        use crate::git::rpc_err;

        let mut client = BlobServiceClient::new(self.git.clone());
        let resp = client
            .blob_load(tonic::Request::new(p::BlobLoadRequest {
                repo_id: repo.id.to_string(),
                id: Some(p::ObjectId { value: oid.to_string() }),
                path: String::new(),
            }))
            .await
            .map_err(rpc_err)?
            .into_inner();

        let content = String::from_utf8_lossy(&resp.blob).to_string();
        if content.is_empty() {
            return Ok(None);
        }

        let html = comrak::markdown_to_html(&content, &comrak::ComrakOptions::default());

        Ok(Some(super::readme::ReadmeDto {
            content,
            html,
        }))
    }
}