use gix::bstr::ByteSlice; use crate::{ bare::GitBare, cmd::{ diff::{ DiffOptions, DiffResult, DiffStats, diff_tree_to_tree::{ HunkCollector, change_to_delta, matches_pathspec, }, }, oid::ObjectId, }, errors::GitResult, }; impl GitBare { pub fn diff_index_to_tree( &self, tree: ObjectId, opts: Option, ) -> GitResult { let options = opts.unwrap_or_default(); let repo = self.gix_repo()?; let gix_id: gix::hash::ObjectId = (&tree).try_into()?; let tree_obj = if let Ok(tree) = repo.find_tree(gix_id) { tree } else { let commit = repo .find_commit(gix_id) .map_err(|e| crate::errors::GitError::Gix(e.to_string()))?; let tree_id = commit .tree_id() .map_err(|e| crate::errors::GitError::Gix(e.to_string()))? .detach(); repo.find_tree(tree_id) .map_err(|e| crate::errors::GitError::Gix(e.to_string()))? }; let head_id = repo .head_id() .map_err(|e| crate::errors::GitError::Gix(e.to_string()))?; let head_commit = repo .find_commit(head_id.detach()) .map_err(|e| crate::errors::GitError::Gix(e.to_string()))?; let head_tree_id = head_commit .tree_id() .map_err(|e| crate::errors::GitError::Gix(e.to_string()))? .detach(); let head_tree = repo .find_tree(head_tree_id) .map_err(|e| crate::errors::GitError::Gix(e.to_string()))?; let tree_oid = tree_obj.id().detach(); let head_tree_oid = head_tree.id().detach(); if tree_oid == head_tree_oid { return Ok(DiffResult { stats: DiffStats { files_changed: 0, insertions: 0, deletions: 0, }, deltas: Vec::new(), }); } let mut diff_opts = gix::diff::Options::default(); diff_opts.track_path(); let changes = repo .diff_tree_to_tree(&tree_obj, &head_tree, Some(diff_opts)) .map_err(|e| crate::errors::GitError::Gix(e.to_string()))?; let mut resource_cache = repo .diff_resource_cache_for_tree_diff() .map_err(|e| crate::errors::GitError::Gix(e.to_string()))?; let mut deltas = Vec::new(); let mut stats = DiffStats { files_changed: 0, insertions: 0, deletions: 0, }; for change in &changes { let location = change.location().to_str().unwrap_or(""); if !matches_pathspec(&options.pathspec, location) { continue; } let mut delta = change_to_delta(change); resource_cache .set_resource_by_change(change.to_ref(), &repo.objects) .map_err(|e| crate::errors::GitError::Gix(e.to_string()))?; let is_binary = { use gix::diff::blob::platform::prepare_diff::Operation; let prep = resource_cache .prepare_diff() .map_err(|e| crate::errors::GitError::Gix(e.to_string()))?; match prep.operation { Operation::InternalDiff { algorithm } => { let input = prep.interned_input(); let diff = gix::diff::blob::diff_with_slider_heuristics( algorithm, &input, ); stats.files_changed += 1; stats.insertions += diff.count_additions() as usize; stats.deletions += diff.count_removals() as usize; if options.context_lines > 0 { let ctx = gix::diff::blob::unified_diff::ContextSize::symmetrical( options.context_lines.max(3), ); let collector = HunkCollector::new(); let unified = gix::diff::blob::UnifiedDiff::new( &diff, &input, collector, ctx, ); let (hunks, lines) = unified.consume().map_err(|e| { crate::errors::GitError::Gix(e.to_string()) })?; delta.hunks = hunks; delta.lines = lines; } false } Operation::SourceOrDestinationIsBinary => { stats.files_changed += 1; true } Operation::ExternalCommand { .. } => { stats.files_changed += 1; false } } }; if is_binary { delta.old_file.is_binary = true; delta.new_file.is_binary = true; } resource_cache.clear_resource_cache_keep_allocation(); deltas.push(delta); } Ok(DiffResult { stats, deltas }) } }