From 1ea9fdb21af20836b3915a429222b7187325a787 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Thu, 3 Sep 2026 04:32:31 -0700 Subject: [PATCH] fix: stage deleted files with libgit2 Fixes #638 --- src/vcs/git/staging.rs | 54 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/vcs/git/staging.rs b/src/vcs/git/staging.rs index f73e0a49..ab397d8a 100644 --- a/src/vcs/git/staging.rs +++ b/src/vcs/git/staging.rs @@ -5,7 +5,14 @@ use crate::error::Result; pub fn stage_file(repo: &Repository, path: &Path) -> Result<()> { let mut index = repo.index()?; - index.add_path(path)?; + if repo + .workdir() + .is_some_and(|workdir| workdir.join(path).exists()) + { + index.add_path(path)?; + } else { + index.remove_path(path)?; + } index.write()?; Ok(()) } @@ -15,6 +22,25 @@ mod tests { use super::*; use std::fs; + fn create_initial_commit(repo: &Repository, file_name: &str, content: &str) { + fs::write(repo.workdir().unwrap().join(file_name), content) + .expect("failed to write initial file"); + + let mut index = repo.index().expect("failed to open index"); + index + .add_path(Path::new(file_name)) + .expect("failed to add file to index"); + index.write().expect("failed to write index"); + + let tree_id = index.write_tree().expect("failed to write tree"); + let tree = repo.find_tree(tree_id).expect("failed to find tree"); + let sig = git2::Signature::now("Test User", "test@example.com") + .expect("failed to create signature"); + + repo.commit(Some("HEAD"), &sig, &sig, "initial", &tree, &[]) + .expect("failed to create commit"); + } + #[test] fn stage_file_adds_to_index() { let temp_dir = tempfile::tempdir().expect("failed to create temp dir"); @@ -28,4 +54,30 @@ mod tests { let index = repo.index().unwrap(); assert!(index.get_path(Path::new("test.txt"), 0).is_some()); } + + #[test] + fn stage_file_removes_deleted_file_from_index() { + let temp_dir = tempfile::tempdir().expect("failed to create temp dir"); + let repo = Repository::init(temp_dir.path()).expect("failed to init repo"); + create_initial_commit(&repo, "deleted.txt", "goodbye\n"); + + fs::remove_file(temp_dir.path().join("deleted.txt")).expect("failed to delete file"); + + stage_file(&repo, Path::new("deleted.txt")).unwrap(); + + let reopened_repo = Repository::open(temp_dir.path()).expect("failed to reopen repo"); + let index = reopened_repo.index().expect("failed to reopen index"); + assert!(index.get_path(Path::new("deleted.txt"), 0).is_none()); + + let head_tree = reopened_repo + .head() + .expect("failed to find HEAD") + .peel_to_tree() + .expect("failed to find HEAD tree"); + let diff = reopened_repo + .diff_tree_to_index(Some(&head_tree), Some(&index), None) + .expect("failed to diff HEAD against index"); + let delta = diff.deltas().next().expect("expected staged deletion"); + assert_eq!(delta.status(), git2::Delta::Deleted); + } }