Login
4 branches 0 tags
Ben (Desktop/Arch) Code cleanup ada8ea6 11 days ago 251 Commits
rubhub / crates / state / src / repo_service.rs
//! Repository service
//!
//! Provides a RepoService struct that wraps rubhub-repo functions,
//! handling event emission for repository changes.

use std::path::PathBuf;
use std::sync::Arc;

use anyhow::Result;
use time::OffsetDateTime;
use tokio::sync::broadcast;

use crate::{RepoEvent, RepoEventInfo};
use rubhub_auth_store::AuthStore;

// Re-export types from rubhub-repo for convenience
pub use rubhub_repo::{CommitParams, EntryKind, GitRefInfo, GitSummary, GitTreeEntry};

/// Newtype wrapper to implement AuthorResolver for AuthStore
struct AuthStoreResolver<'a>(&'a AuthStore);

impl rubhub_repo::AuthorResolver for AuthStoreResolver<'_> {
    fn resolve_email(&self, email: &str) -> Option<String> {
        self.0.get_user_slug_by_email(email)
    }
}

/// Repository service that handles git operations with event emission
#[derive(Debug, Clone)]
pub struct RepoService {
    git_root: PathBuf,
    event_tx: broadcast::Sender<RepoEvent>,
    auth: Arc<AuthStore>,
}

impl RepoService {
    /// Create a new RepoService
    pub fn new(
        git_root: PathBuf,
        event_tx: broadcast::Sender<RepoEvent>,
        auth: Arc<AuthStore>,
    ) -> Self {
        Self {
            git_root,
            event_tx,
            auth,
        }
    }

    /// Emit a repository event to all SSE listeners
    fn emit_event(&self, event: RepoEvent) {
        let _ = self.event_tx.send(event);
    }

    /// Emit events for repository changes
    fn emit_changes(&self, before: &GitSummary, after: &GitSummary) {
        let timestamp = OffsetDateTime::now_utc();
        let (branch_changes, tag_changes) = before.diff(after);

        for (branch, commit_hash) in branch_changes {
            self.emit_event(RepoEvent::BranchUpdated {
                info: RepoEventInfo {
                    owner: after.owner().to_string(),
                    project: after.project().to_string(),
                    commit_hash,
                    timestamp,
                },
                branch,
            });
        }

        for (tag, commit_hash) in tag_changes {
            self.emit_event(RepoEvent::TagUpdated {
                info: RepoEventInfo {
                    owner: after.owner().to_string(),
                    project: after.project().to_string(),
                    commit_hash,
                    timestamp,
                },
                tag,
            });
        }
    }

    /// Execute an operation with event emission for repository changes
    async fn with_event_emission<F, Fut>(
        &self,
        user_name: &str,
        project_slug: &str,
        operation: F,
    ) -> Result<()>
    where
        F: FnOnce() -> Fut,
        Fut: std::future::Future<Output = Result<()>>,
    {
        let before = rubhub_repo::capture_git_summary(&self.git_root, user_name, project_slug);
        operation().await?;
        let after = rubhub_repo::capture_git_summary(&self.git_root, user_name, project_slug);
        self.emit_changes(&before, &after);
        Ok(())
    }

    /// Create a new bare git repository
    pub async fn create_bare_repo(&self, user: &str, project: &str) -> Result<(), std::io::Error> {
        rubhub_repo::create_bare_repo(&self.git_root, user, project)
            .await
            .map_err(|e| std::io::Error::other(e.to_string()))
    }

    /// Set the HEAD reference for a repository
    pub async fn set_git_head(
        &self,
        user_name: &str,
        project_slug: &str,
        head_branch: &str,
    ) -> Result<()> {
        rubhub_repo::set_git_head(&self.git_root, user_name, project_slug, head_branch).await
    }

    /// Get a summary of a repository's branches and tags
    pub async fn get_git_summary(&self, user_name: &str, project_slug: &str) -> Option<GitSummary> {
        rubhub_repo::get_git_summary(&self.git_root, user_name, project_slug).await
    }

    /// Get information about a git reference (branch/tag) with commits
    pub async fn get_git_info(
        &self,
        user_name: &str,
        project_slug: &str,
        branch: &str,
        max_commits: i32,
        offset: i32,
    ) -> Option<GitRefInfo> {
        rubhub_repo::get_git_info(
            &self.git_root,
            user_name,
            project_slug,
            branch,
            max_commits,
            offset,
            &AuthStoreResolver(self.auth.as_ref()),
        )
        .await
    }

    /// Get the contents of a file from a repository
    pub async fn get_git_file(
        &self,
        user_name: &str,
        project_slug: &str,
        branch: &str,
        path: &str,
    ) -> Result<Vec<u8>> {
        rubhub_repo::get_git_file(&self.git_root, user_name, project_slug, branch, path).await
    }

    /// Get the entries in a directory from a repository
    pub async fn get_git_tree(
        &self,
        user_name: &str,
        project_slug: &str,
        branch: &str,
        path: &str,
    ) -> Result<Vec<GitTreeEntry>> {
        rubhub_repo::get_git_tree(&self.git_root, user_name, project_slug, branch, path).await
    }

    /// Check if a branch exists in a repository
    pub async fn branch_exists(&self, user_name: &str, project_slug: &str, branch: &str) -> bool {
        rubhub_repo::branch_exists(&self.git_root, user_name, project_slug, branch).await
    }

    /// Capture git summary for use with emit_repo_changes
    pub fn capture_summary(&self, owner: &str, project: &str) -> GitSummary {
        rubhub_repo::capture_git_summary(&self.git_root, owner, project)
    }

    /// Emit repository change events by comparing before/after states
    pub fn emit_repo_changes(&self, before: &GitSummary, after: &GitSummary) {
        self.emit_changes(before, after);
    }

    /// Create an orphan branch with an initial commit containing one file
    pub async fn create_orphan_branch(&self, params: CommitParams<'_>) -> Result<()> {
        let user_name = params.user_name;
        let project_slug = params.project_slug;
        let git_root = self.git_root.clone();
        self.with_event_emission(user_name, project_slug, || async {
            rubhub_repo::create_orphan_branch(&git_root, params).await
        })
        .await
    }

    /// Add a file to a branch and create a commit
    pub async fn add_file_to_branch(&self, params: CommitParams<'_>) -> Result<()> {
        let user_name = params.user_name;
        let project_slug = params.project_slug;
        let git_root = self.git_root.clone();
        self.with_event_emission(user_name, project_slug, || async {
            rubhub_repo::add_file_to_branch(&git_root, params).await
        })
        .await
    }
}