use std::path::PathBuf;
use super::{Repo, RepoError};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChangeKind {
Added,
Modified,
Deleted,
Renamed {
from: PathBuf,
},
}
#[derive(Debug, Clone)]
pub struct FileChange {
pub path: PathBuf,
pub kind: ChangeKind,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct Diff {
pub changes: Vec<FileChange>,
}
impl Repo {
pub async fn diff(&self, a: &str, b: &str) -> Result<Diff, RepoError> {
let inner = self.thread_safe();
let a = a.to_string();
let b = b.to_string();
tokio::task::spawn_blocking(move || compute_diff(&inner, &a, &b)).await.map_err(|join| {
RepoError::DiffFailed { cause: format!("spawn_blocking join: {join}") }
})?
}
}
fn compute_diff(inner: &gix::ThreadSafeRepository, a: &str, b: &str) -> Result<Diff, RepoError> {
let repo = inner.to_thread_local();
let a_id = parse_single(&repo, a)?;
let b_id = parse_single(&repo, b)?;
let a_commit = repo
.find_commit(a_id)
.map_err(|_| RepoError::RevspecNotFound { revspec: a.to_string() })?;
let b_commit = repo
.find_commit(b_id)
.map_err(|_| RepoError::RevspecNotFound { revspec: b.to_string() })?;
let a_tree = a_commit
.tree()
.map_err(|e| RepoError::DiffFailed { cause: format!("source tree: {e}") })?;
let b_tree = b_commit
.tree()
.map_err(|e| RepoError::DiffFailed { cause: format!("destination tree: {e}") })?;
let changes = repo
.diff_tree_to_tree(Some(&a_tree), Some(&b_tree), None)
.map_err(|e| RepoError::DiffFailed { cause: format!("tree diff: {e}") })?;
let mut diff = Diff::default();
for change in changes {
use gix::diff::tree_with_rewrites::Change as C;
let file = match change {
C::Addition { location, .. } => {
FileChange { path: gix::path::from_bstring(location), kind: ChangeKind::Added }
}
C::Deletion { location, .. } => {
FileChange { path: gix::path::from_bstring(location), kind: ChangeKind::Deleted }
}
C::Modification { location, .. } => {
FileChange { path: gix::path::from_bstring(location), kind: ChangeKind::Modified }
}
C::Rewrite { source_location, location, .. } => FileChange {
path: gix::path::from_bstring(location),
kind: ChangeKind::Renamed { from: gix::path::from_bstring(source_location) },
},
};
diff.changes.push(file);
}
Ok(diff)
}
fn parse_single(repo: &gix::Repository, revspec: &str) -> Result<gix::ObjectId, RepoError> {
let id = repo
.rev_parse_single(revspec)
.map_err(|_| RepoError::RevspecNotFound { revspec: revspec.to_string() })?;
Ok(id.detach())
}