From d695ad8f887ee66bfc483aa691c092bbc203e89a Mon Sep 17 00:00:00 2001 From: blinxen Date: Mon, 27 Apr 2026 22:59:19 +0200 Subject: [PATCH] Add more tests for git diff --- src/diff.rs | 123 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 108 insertions(+), 15 deletions(-) diff --git a/src/diff.rs b/src/diff.rs index f498baf1..4258e2d9 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -2,14 +2,12 @@ use gix::diff::blob::pipeline::{Mode, WorktreeRoots}; use gix::diff::blob::{Algorithm, HunkIter, ResourceKind}; -use gix::index::hash::Kind; use gix::object::tree::EntryKind; -use gix::{self, ObjectId}; use path_abs::PathInfo; use std::collections::HashMap; use std::path::Path; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum LineChange { Added, RemovedAbove, @@ -44,9 +42,13 @@ fn collect_changes_from_hunks(hunks: HunkIter) -> Option { pub fn get_git_diff(filename: &Path) -> Option { let filepath_absolute = filename.canonicalize().ok()?; - let repository = gix::discover(filepath_absolute.parent().ok()?).unwrap(); + let repository = gix::discover(filepath_absolute.parent().ok()?).ok()?; let repo_path_absolute = repository.workdir()?.canonicalize().ok()?; - let filepath_relative_to_repo = filepath_absolute.strip_prefix(&repo_path_absolute).ok()?; + let filepath_relative_to_repo = gix::path::to_unix_separators_on_windows( + gix::path::into_bstr(filepath_absolute.strip_prefix(&repo_path_absolute).ok()?) + ); + let index = repository.index_or_load_from_head_or_empty().ok()?; + let index_entry = index.entry_by_path(filepath_relative_to_repo.as_ref())?; let mut cache = repository .diff_resource_cache( Mode::ToGit, @@ -58,23 +60,18 @@ pub fn get_git_diff(filename: &Path) -> Option { .ok()?; cache .set_resource( - repository - .head_tree() - .ok()? - .lookup_entry_by_path(filepath_relative_to_repo.to_str()?) - .ok()?? - .object_id(), - EntryKind::Blob, - filepath_relative_to_repo.to_str()?.into(), + index_entry.id, + index_entry.mode.to_tree_entry_mode()?.kind(), + filepath_relative_to_repo.as_ref(), ResourceKind::OldOrSource, &repository, ) .ok()?; cache .set_resource( - ObjectId::null(Kind::Sha1), + repository.object_hash().null(), EntryKind::Blob, - filepath_relative_to_repo.to_str()?.into(), + filepath_relative_to_repo.as_ref(), ResourceKind::NewOrDestination, &repository, ) @@ -86,3 +83,99 @@ pub fn get_git_diff(filename: &Path) -> Option { collect_changes_from_hunks(diff.hunks()) } + +#[cfg(test)] +mod tests { + use super::{get_git_diff, LineChange}; + use std::path::{Path, PathBuf}; + use std::process::Command; + + fn git(repo: &Path, args: &[&str]) { + let output = Command::new("git") + .args(args) + .current_dir(repo) + .output() + .expect("git command can run"); + assert!( + output.status.success(), + "git {args:?} failed\nstdout: {}\nstderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } + + fn setup_repo() -> tempfile::TempDir { + let dir = tempfile::tempdir().expect("can create temporary directory"); + let repo = dir.path(); + git(repo, &["init"]); + git(repo, &["config", "user.email", "test@test.test"]); + git(repo, &["config", "user.name", "Test"]); + dir + } + + fn create_and_track_file(repo: &tempfile::TempDir, filename: &str) -> PathBuf { + let filepath = repo.path().join(filename); + std::fs::write(&filepath, "file\n").expect("can write file"); + git(repo.path(), &["add", &filename]); + git(repo.path(), &["commit", "-m", "initial"]); + + filepath.to_owned() + } + + #[test] + fn not_a_git_repository() { + let dir = tempfile::tempdir().expect("can create temporary directory"); + let file = dir.path().join("file.txt"); + std::fs::write(&file, "line 1\n").expect("can write file"); + + assert_eq!(get_git_diff(&file), None); + } + + #[test] + fn diff_is_calculated_against_index() { + let repo = setup_repo(); + let file = create_and_track_file(&repo, "file.txt"); + + std::fs::write(&file, "line 1\nline 2 modified\n").expect("can write file"); + // Add modified file to index -> should find 0 changes but not none + git(repo.path(), &["add", "file.txt"]); + + assert_eq!(get_git_diff(&file).expect("empty map").len(), 0); + } + + #[test] + fn diff_is_calculated_against_index_2() { + let repo = setup_repo(); + let file = create_and_track_file(&repo, "file.txt"); + + std::fs::write(&file, "line 1\nline 2 modified\n").expect("can write file"); + git(repo.path(), &["add", "file.txt"]); + // modify the second line again which should show a single change + std::fs::write(&file, "line 1\nline 2 modified again\n").expect("can write file"); + + assert_eq!(get_git_diff(&file).expect("one change").len(), 1); + } + + #[test] + fn diff_is_calculated_correctly() { + let repo = setup_repo(); + create_and_track_file(&repo, "committed.txt"); + let filename = "committed2.txt"; + let file = create_and_track_file(&repo, filename); + + std::fs::write(&file, "file\nline 2 added\n").expect("can write file"); + let mut changes = get_git_diff(&file).expect("multiple changes"); + assert_eq!(changes.get(&2), Some(&LineChange::Added)); + git(repo.path(), &["add", filename]); + + std::fs::write(&file, "file\nline 2 modified\n").expect("can write file"); + changes = get_git_diff(&file).expect("multiple changes"); + assert_eq!(changes.get(&2), Some(&LineChange::Modified)); + git(repo.path(), &["add", filename]); + + std::fs::write(&file, "line 2 modified\nline 3 added").expect("can write file"); + changes = get_git_diff(&file).expect("multiple changes"); + assert_eq!(changes.get(&1), Some(&LineChange::RemovedAbove)); + assert_eq!(changes.get(&2), Some(&LineChange::Added)); + } +}