//! Copyright (c) 2022-2026 GitDataAi All rights reserved. use crate::bare::GitBare; use crate::error::GitResult; use crate::pb::*; impl GitBare { /// Count commits in a revision range or path. pub fn count_commits(&self, request: CountCommitsRequest) -> GitResult { let revision = if request.revision.is_empty() { "HEAD" } else { &request.revision }; crate::sanitize::validate_revision(revision)?; let mut args = vec![ "--git-dir".to_string(), self.bare_dir.to_string_lossy().into_owned(), "rev-list".to_string(), "--count".to_string(), ]; if !request.since.is_empty() { args.push(format!("--since={}", request.since)); } if !request.until.is_empty() { args.push(format!("--until={}", request.until)); } args.push(revision.to_string()); if !request.path.is_empty() { crate::sanitize::validate_file_path(&request.path)?; args.push("--".to_string()); args.push(request.path.clone()); } let output = std::process::Command::new("git") .args(&args) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .output() .map_err(|e| crate::error::GitError::CommandFailed { status_code: None, stderr: e.to_string(), })?; if !output.status.success() { return Err(crate::error::GitError::CommandFailed { status_code: output.status.code(), stderr: String::from_utf8_lossy(&output.stderr).trim().to_string(), }); } let count = String::from_utf8_lossy(&output.stdout) .trim() .parse::() .unwrap_or(0); Ok(CountCommitsResponse { count }) } /// Count diverging commits between two branches (left vs right). pub fn count_diverging_commits( &self, request: CountDivergingCommitsRequest, ) -> GitResult { crate::sanitize::validate_revision(&request.left)?; crate::sanitize::validate_revision(&request.right)?; let output = std::process::Command::new("git") .args([ "--git-dir", &self.bare_dir.to_string_lossy(), "rev-list", "--count", "--left-right", &format!("{}...{}", request.left, request.right), ]) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .output() .map_err(|e| crate::error::GitError::CommandFailed { status_code: None, stderr: e.to_string(), })?; if !output.status.success() { return Err(crate::error::GitError::CommandFailed { status_code: output.status.code(), stderr: String::from_utf8_lossy(&output.stderr).trim().to_string(), }); } let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); // Format: "\t" let parts: Vec<&str> = stdout.split('\t').collect(); let left_count = parts.first().and_then(|s| s.parse().ok()).unwrap_or(0); let right_count = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0); Ok(CountDivergingCommitsResponse { left_count, right_count, }) } }