use anyhow::Error; use git2::BranchType; use giterated_models::{ object::Object, repository::{ BranchStaleAfter, DefaultBranch, Repository, RepositoryBranch, RepositoryBranchFilter, RepositoryBranchRequest, RepositoryBranchesRequest, }, }; use giterated_stack::{AuthenticatedUser, GiteratedStack, OperationState, StackOperationState}; use super::{GitBackend, GitBackendError}; impl GitBackend { /// .0: List of branches filtering by passed requirements. /// .1: Total amount of branches after being filtered pub async fn handle_repository_get_branches( &mut self, requester: &Option, repository_object: &mut Object<'_, StackOperationState, Repository, GiteratedStack>, OperationState(operation_state): OperationState, request: &RepositoryBranchesRequest, ) -> Result<(Vec, usize), Error> { let repository = repository_object.object(); let git = self .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) .await?; let default_branch_name = repository_object .get::(&operation_state) .await?; let default_branch = git .find_branch(&default_branch_name.0, BranchType::Local) .map_err(|_| GitBackendError::DefaultNotFound)?; // Get the stale(after X seconds) setting let stale_after = repository_object .get::(&operation_state) .await .unwrap_or_default() .0; // Could be done better with the RepositoryBranchFilter::None check done beforehand. let mut filtered_branches = git .branches(None)? .filter_map(|branch| { let branch = branch.ok()?.0; let Some(name) = branch.name().ok().flatten() else { return None; }; // TODO: Non UTF-8? let Some(commit) = GitBackend::get_last_commit_in_rev( &git, branch.get().name().unwrap(), &default_branch_name, ) .ok() else { return None; }; let stale = chrono::Utc::now() .signed_duration_since(commit.time) .num_seconds() > stale_after.into(); // Filter based on if the branch is stale or not if request.filter != RepositoryBranchFilter::None { #[allow(clippy::if_same_then_else)] if stale && request.filter == RepositoryBranchFilter::Active { return None; } else if !stale && request.filter == RepositoryBranchFilter::Stale { return None; } } Some((name.to_string(), branch, stale, commit)) }) .collect::>(); // Get the total amount of filtered branches let branch_count = filtered_branches.len(); if let Some(search) = &request.search { // TODO: Caching // Search by sorting using a simple fuzzy search algorithm filtered_branches.sort_by(|(n1, _, _, _), (n2, _, _, _)| { strsim::damerau_levenshtein(search, n1) .cmp(&strsim::damerau_levenshtein(search, n2)) }); } else { // Sort the branches by commit date filtered_branches.sort_by(|(_, _, _, c1), (_, _, _, c2)| c2.time.cmp(&c1.time)); } // Go to the requested position let mut filtered_branches = filtered_branches.iter().skip(request.range.0); let mut branches = vec![]; // Iterate through the filtered branches using the passed range for _ in request.range.0..request.range.1 { let Some((name, branch, stale, commit)) = filtered_branches.next() else { break; }; // Get how many commits are ahead of and behind of the head let ahead_behind_default = if default_branch.get().target().is_some() && branch.get().target().is_some() { git.graph_ahead_behind( branch.get().target().unwrap(), default_branch.get().target().unwrap(), ) .ok() } else { None }; branches.push(RepositoryBranch { name: name.to_string(), stale: *stale, last_commit: Some(commit.clone()), ahead_behind_default, }) } Ok((branches, branch_count)) } pub async fn handle_repository_get_branch( &mut self, requester: &Option, repository_object: &mut Object<'_, StackOperationState, Repository, GiteratedStack>, OperationState(operation_state): OperationState, request: &RepositoryBranchRequest, ) -> Result { let repository = repository_object.object(); let git = self .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) .await?; // TODO: Don't duplicate search when the default branch and the requested one are the same // Get the default branch to compare against let default_branch_name = repository_object .get::(&operation_state) .await?; let default_branch = git .find_branch(&default_branch_name.0, BranchType::Local) .map_err(|_| GitBackendError::DefaultNotFound)?; // Find the requested branch let branch = git .find_branch(&request.name, BranchType::Local) .map_err(|_| GitBackendError::BranchNotFound(request.name.clone()))?; // Get the stale(after X seconds) setting let stale_after = repository_object .get::(&operation_state) .await .unwrap_or_default() .0; // TODO: Non UTF-8? let last_commit = GitBackend::get_last_commit_in_rev( &git, branch.get().name().unwrap(), &default_branch_name, ) .ok(); let stale = if let Some(ref last_commit) = last_commit { chrono::Utc::now() .signed_duration_since(last_commit.time) .num_seconds() > stale_after.into() } else { // TODO: Make sure it's acceptable to return false here false }; // Get how many commits are ahead of and behind of the head let ahead_behind_default = if default_branch.get().target().is_some() && branch.get().target().is_some() { git.graph_ahead_behind( branch.get().target().unwrap(), default_branch.get().target().unwrap(), ) .ok() } else { None }; Ok(RepositoryBranch { name: request.name.clone(), stale, last_commit, ahead_behind_default, }) } }