Skip to main content
Glama
by 8b-is
git_relay.rs14.4 kB
//! 🔄 GiT Relay - Smart Git CLI Integration with Compression //! //! This module provides a compressed, intelligent interface to Git CLI //! operations without requiring API keys or vendor lock-in. It leverages //! our quantum compression and context awareness for maximum efficiency. use super::context::ContextAnalyzer; use super::{SmartResponse, TaskContext, TokenSavings}; use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; use std::path::Path; use std::process::{Command, Output}; /// 🔄 Git relay with smart compression and context awareness pub struct GitRelay { #[allow(dead_code)] context_analyzer: ContextAnalyzer, } /// 📊 Git operation result with compression #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GitResult { /// Operation type pub operation: GitOperation, /// Compressed output pub output: String, /// Exit code pub exit_code: i32, /// Context-aware summary pub summary: String, /// Suggested next actions pub suggestions: Vec<String>, } /// 🏷️ Git operation types #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum GitOperation { Status, Log, Diff, Branch, Remote, Commit, Push, Pull, Clone, Add, Reset, Stash, Tag, Merge, Rebase, Custom(String), } /// 📈 Git relay response with smart compression pub type GitRelayResponse = SmartResponse<GitResult>; impl GitRelay { /// Create new Git relay pub fn new() -> Self { Self { context_analyzer: ContextAnalyzer::new(), } } /// 🔄 Execute git command with smart compression pub fn execute( &self, repo_path: &Path, operation: GitOperation, args: &[String], context: Option<&TaskContext>, ) -> Result<GitRelayResponse> { // Build git command let mut cmd = Command::new("git"); cmd.current_dir(repo_path); // Add operation-specific arguments match &operation { GitOperation::Status => { cmd.args(["status", "--porcelain", "--branch"]); } GitOperation::Log => { cmd.args(["log", "--oneline", "--graph", "--decorate", "-10"]); } GitOperation::Diff => { cmd.args(["diff", "--stat", "--color=never"]); } GitOperation::Branch => { cmd.args(["branch", "-v", "-a"]); } GitOperation::Remote => { cmd.args(["remote", "-v"]); } GitOperation::Custom(op) => { cmd.arg(op); } _ => { return Err(anyhow!("Operation {:?} not yet implemented", operation)); } } // Add user-provided arguments cmd.args(args); // Execute command let output = cmd.output()?; // Process and compress output let git_result = self.process_output(operation, output, context)?; // Calculate token savings let original_tokens = git_result.output.len() / 4; // Rough estimation let compressed_tokens = git_result.summary.len() / 4; let token_savings = TokenSavings::new(original_tokens, compressed_tokens, "git-relay"); // Create response let response = GitRelayResponse { primary: vec![git_result.clone()], secondary: vec![], context_summary: format!( "Git {} operation completed", self.operation_name(&git_result.operation) ), token_savings, suggestions: git_result.suggestions.clone(), }; Ok(response) } /// 📊 Smart git status with context awareness pub fn smart_status( &self, repo_path: &Path, context: Option<&TaskContext>, ) -> Result<GitRelayResponse> { self.execute(repo_path, GitOperation::Status, &[], context) } /// 📜 Smart git log with relevance filtering pub fn smart_log( &self, repo_path: &Path, limit: Option<usize>, context: Option<&TaskContext>, ) -> Result<GitRelayResponse> { let limit_str = limit.unwrap_or(10).to_string(); let args = vec![format!("-{}", limit_str)]; self.execute(repo_path, GitOperation::Log, &args, context) } /// 🔍 Smart git diff with context filtering pub fn smart_diff( &self, repo_path: &Path, target: Option<&str>, context: Option<&TaskContext>, ) -> Result<GitRelayResponse> { let args = if let Some(t) = target { vec![t.to_string()] } else { vec![] }; self.execute(repo_path, GitOperation::Diff, &args, context) } /// 🌿 Smart branch information pub fn smart_branches( &self, repo_path: &Path, context: Option<&TaskContext>, ) -> Result<GitRelayResponse> { self.execute(repo_path, GitOperation::Branch, &[], context) } /// 🔗 Smart remote information pub fn smart_remotes( &self, repo_path: &Path, context: Option<&TaskContext>, ) -> Result<GitRelayResponse> { self.execute(repo_path, GitOperation::Remote, &[], context) } /// 🎯 Execute custom git command with compression pub fn custom_command( &self, repo_path: &Path, command: &str, args: &[String], context: Option<&TaskContext>, ) -> Result<GitRelayResponse> { self.execute( repo_path, GitOperation::Custom(command.to_string()), args, context, ) } /// Process git command output with smart compression fn process_output( &self, operation: GitOperation, output: Output, context: Option<&TaskContext>, ) -> Result<GitResult> { let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); // Combine stdout and stderr let full_output = if stderr.is_empty() { stdout.to_string() } else { format!("{}\nERROR: {}", stdout, stderr) }; // Generate context-aware summary let summary = self.generate_summary(&operation, &full_output, context); // Generate suggestions let suggestions = self.generate_suggestions(&operation, &full_output, output.status.code().unwrap_or(-1)); Ok(GitResult { operation, output: full_output, exit_code: output.status.code().unwrap_or(-1), summary, suggestions, }) } /// Generate context-aware summary fn generate_summary( &self, operation: &GitOperation, output: &str, _context: Option<&TaskContext>, ) -> String { match operation { GitOperation::Status => self.summarize_status(output), GitOperation::Log => self.summarize_log(output), GitOperation::Diff => self.summarize_diff(output), GitOperation::Branch => self.summarize_branches(output), GitOperation::Remote => self.summarize_remotes(output), _ => { format!( "Git {} completed with {} characters of output", self.operation_name(operation), output.len() ) } } } /// Summarize git status output fn summarize_status(&self, output: &str) -> String { let lines: Vec<&str> = output.lines().collect(); if lines.is_empty() { return "Repository is clean - no changes detected".to_string(); } let mut modified = 0; let mut added = 0; let mut deleted = 0; let mut untracked = 0; let mut branch_info = String::new(); for line in lines { if line.starts_with("##") { branch_info = line.trim_start_matches("## ").to_string(); } else if line.starts_with(" M") || line.starts_with("M ") { modified += 1; } else if line.starts_with("A ") || line.starts_with(" A") { added += 1; } else if line.starts_with(" D") || line.starts_with("D ") { deleted += 1; } else if line.starts_with("??") { untracked += 1; } } let mut summary = format!("Branch: {}", branch_info); if modified > 0 { summary.push_str(&format!(", {} modified", modified)); } if added > 0 { summary.push_str(&format!(", {} added", added)); } if deleted > 0 { summary.push_str(&format!(", {} deleted", deleted)); } if untracked > 0 { summary.push_str(&format!(", {} untracked", untracked)); } summary } /// Summarize git log output fn summarize_log(&self, output: &str) -> String { let lines: Vec<&str> = output.lines().collect(); let commit_count = lines.iter().filter(|line| line.contains("*")).count(); if commit_count == 0 { "No commits found".to_string() } else { format!("Last {} commits shown", commit_count) } } /// Summarize git diff output fn summarize_diff(&self, output: &str) -> String { if output.trim().is_empty() { "No differences found".to_string() } else { let lines: Vec<&str> = output.lines().collect(); let file_count = lines.iter().filter(|line| line.contains("|")).count(); format!("Changes in {} files", file_count) } } /// Summarize git branch output fn summarize_branches(&self, output: &str) -> String { let lines: Vec<&str> = output.lines().collect(); let local_branches = lines .iter() .filter(|line| !line.contains("remotes/")) .count(); let remote_branches = lines .iter() .filter(|line| line.contains("remotes/")) .count(); format!( "{} local branches, {} remote branches", local_branches, remote_branches ) } /// Summarize git remote output fn summarize_remotes(&self, output: &str) -> String { let lines: Vec<&str> = output.lines().collect(); let remote_count = lines.len() / 2; // Each remote has fetch and push URLs if remote_count == 0 { "No remotes configured".to_string() } else { format!("{} remote(s) configured", remote_count) } } /// Generate operation-specific suggestions fn generate_suggestions( &self, operation: &GitOperation, output: &str, exit_code: i32, ) -> Vec<String> { let mut suggestions = Vec::new(); if exit_code != 0 { suggestions.push("Command failed - check git status and repository state".to_string()); return suggestions; } match operation { GitOperation::Status => { if output.contains("??") { suggestions.push("Use 'git add .' to stage untracked files".to_string()); } if output.contains(" M") || output.contains("M ") { suggestions.push("Use 'git add -u' to stage modified files".to_string()); } if output.contains("ahead") { suggestions.push("Use 'git push' to push local commits".to_string()); } if output.contains("behind") { suggestions.push("Use 'git pull' to fetch remote changes".to_string()); } } GitOperation::Log => { suggestions.push("Use smart_diff to see changes in recent commits".to_string()); suggestions.push("Use smart_branches to see branch information".to_string()); } GitOperation::Diff => { if !output.trim().is_empty() { suggestions.push("Review changes before committing".to_string()); suggestions.push("Use 'git add' to stage specific changes".to_string()); } } GitOperation::Branch => { suggestions.push("Use 'git checkout <branch>' to switch branches".to_string()); suggestions .push("Use 'git branch -d <branch>' to delete merged branches".to_string()); } _ => {} } suggestions } /// Get human-readable operation name fn operation_name<'a>(&self, operation: &'a GitOperation) -> &'a str { match operation { GitOperation::Status => "status", GitOperation::Log => "log", GitOperation::Diff => "diff", GitOperation::Branch => "branch", GitOperation::Remote => "remote", GitOperation::Commit => "commit", GitOperation::Push => "push", GitOperation::Pull => "pull", GitOperation::Clone => "clone", GitOperation::Add => "add", GitOperation::Reset => "reset", GitOperation::Stash => "stash", GitOperation::Tag => "tag", GitOperation::Merge => "merge", GitOperation::Rebase => "rebase", GitOperation::Custom(op) => op, } } } impl Default for GitRelay { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; // use std::path::PathBuf; // Commented out as unused #[test] fn test_git_relay_creation() { let relay = GitRelay::new(); assert_eq!(relay.operation_name(&GitOperation::Status), "status"); } #[test] fn test_status_summary() { let relay = GitRelay::new(); let output = "## main...origin/main\n M file1.rs\n?? file2.rs\n"; let summary = relay.summarize_status(output); assert!(summary.contains("main")); assert!(summary.contains("modified")); assert!(summary.contains("untracked")); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/8b-is/smart-tree'

If you have feedback or need assistance with the MCP directory API, please join our Discord server