diffscope 0.5.3

A composable code review engine with smart analysis, confidence scoring, and professional reporting
use anyhow::{Context, Result};
use git2::{Repository, DiffOptions, DiffFormat};
use std::path::Path;

pub struct GitIntegration {
    repo: Repository,
}

impl GitIntegration {
    pub fn new(repo_path: impl AsRef<Path>) -> Result<Self> {
        let repo = Repository::discover(repo_path)
            .context("Failed to find git repository")?;
        Ok(Self { repo })
    }
    
    pub fn get_uncommitted_diff(&self) -> Result<String> {
        let mut diff_options = DiffOptions::new();
        diff_options.include_untracked(true);
        
        let head = self.repo.head()?.peel_to_tree()?;
        let diff = self.repo.diff_tree_to_workdir_with_index(
            Some(&head),
            Some(&mut diff_options)
        )?;
        
        let mut diff_text = Vec::new();
        diff.print(DiffFormat::Patch, |_delta, _hunk, line| {
            diff_text.extend_from_slice(line.content());
            true
        })?;
        
        Ok(String::from_utf8_lossy(&diff_text).to_string())
    }
    
    pub fn get_staged_diff(&self) -> Result<String> {
        let head = self.repo.head()?.peel_to_tree()?;
        let mut index = self.repo.index()?;
        let oid = index.write_tree()?;
        let index_tree = self.repo.find_tree(oid)?;
        
        let diff = self.repo.diff_tree_to_tree(
            Some(&head),
            Some(&index_tree),
            None
        )?;
        
        let mut diff_text = Vec::new();
        diff.print(DiffFormat::Patch, |_delta, _hunk, line| {
            diff_text.extend_from_slice(line.content());
            true
        })?;
        
        Ok(String::from_utf8_lossy(&diff_text).to_string())
    }
    
    pub fn get_branch_diff(&self, base_branch: &str) -> Result<String> {
        let base = self.repo.revparse_single(base_branch)?
            .peel_to_commit()?;
        let head = self.repo.head()?.peel_to_commit()?;
        
        let base_tree = base.tree()?;
        let head_tree = head.tree()?;
        
        let diff = self.repo.diff_tree_to_tree(
            Some(&base_tree),
            Some(&head_tree),
            None
        )?;
        
        let mut diff_text = Vec::new();
        diff.print(DiffFormat::Patch, |_delta, _hunk, line| {
            diff_text.extend_from_slice(line.content());
            true
        })?;
        
        Ok(String::from_utf8_lossy(&diff_text).to_string())
    }
    
    pub fn get_current_branch(&self) -> Result<String> {
        let head = self.repo.head()?;
        if let Some(name) = head.shorthand() {
            Ok(name.to_string())
        } else {
            Ok("HEAD".to_string())
        }
    }
    
    pub fn get_remote_url(&self) -> Result<Option<String>> {
        let remote = self.repo.find_remote("origin")?;
        Ok(remote.url().map(|s| s.to_string()))
    }
    
    pub fn get_recent_commits(&self, count: usize) -> Result<Vec<String>> {
        let mut revwalk = self.repo.revwalk()?;
        revwalk.push_head()?;
        
        let mut commits = Vec::new();
        for (i, oid) in revwalk.enumerate() {
            if i >= count {
                break;
            }
            
            let oid = oid?;
            let commit = self.repo.find_commit(oid)?;
            let summary = commit.summary().unwrap_or("No commit message");
            commits.push(summary.to_string());
        }
        
        Ok(commits)
    }
}