diff --git a/src/command/diff/git.rs b/src/command/diff/git.rs index b7d2bae6..c6a753b3 100644 --- a/src/command/diff/git.rs +++ b/src/command/diff/git.rs @@ -102,8 +102,12 @@ pub fn get_new_content(filename: &str, refs: &DiffRefs, backend: &dyn VcsBackend .get_file_content_at_ref(to, Path::new(filename)) .unwrap_or_default(), DiffRefs::WorkingTree => { - // Read from working tree (actual filesystem) - fs::read_to_string(filename).unwrap_or_default() + // Resolve path relative to repository root + let full_path = backend + .get_repository_root() + .map(|root| root.join(filename)) + .unwrap_or_else(|_| filename.into()); + fs::read_to_string(full_path).unwrap_or_default() } } } @@ -476,4 +480,55 @@ mod tests { let _ = std::env::set_current_dir(&original); let _ = fs::remove_dir_all(&dir); } + + #[test] + fn test_load_file_diffs_from_subdirectory() { + use crate::vcs::test_utils::cwd_lock; + + let _lock = cwd_lock().lock().unwrap_or_else(|e| e.into_inner()); + let dir = make_temp_dir("git-subdir-diff"); + let original = std::env::current_dir().expect("get cwd"); + + git(&dir, &["init"]); + git(&dir, &["config", "user.email", "test@example.com"]); + git(&dir, &["config", "user.name", "Test User"]); + + // Create nested structure + fs::create_dir_all(dir.join("src")).expect("create subdir"); + fs::write(dir.join("src/file.txt"), "initial\n").expect("write file"); + git(&dir, &["add", "."]); + git(&dir, &["commit", "-m", "init"]); + + // Modify file (unstaged change) + fs::write(dir.join("src/file.txt"), "modified\n").expect("modify file"); + + // Change to subdirectory (simulating running lumen from subdir) + std::env::set_current_dir(dir.join("src")).expect("set cwd to subdir"); + + let backend = GitBackend::new(Path::new(".")) + .expect("should open repo from subdir"); + + let options = super::DiffOptions { + reference: None, + pr: None, + file: None, + watch: false, + theme: None, + stacked: false, + }; + + let diffs = load_file_diffs(&options, &backend); + + assert_eq!(diffs.len(), 1, "should have 1 changed file"); + assert_eq!(diffs[0].filename, "src/file.txt"); + assert_eq!( + diffs[0].status, + FileStatus::Modified, + "file should be modified, not deleted" + ); + assert_eq!(diffs[0].new_content, "modified\n", "should read new content"); + + let _ = std::env::set_current_dir(&original); + let _ = fs::remove_dir_all(&dir); + } } diff --git a/src/vcs/backend.rs b/src/vcs/backend.rs index 43743a90..cdb6560d 100644 --- a/src/vcs/backend.rs +++ b/src/vcs/backend.rs @@ -129,6 +129,9 @@ pub trait VcsBackend { to: &str, ) -> Result, VcsError>; + /// Get the root path of the repository working directory. + fn get_repository_root(&self) -> Result; + /// Get the name of this VCS backend ("git" or "jj"). fn name(&self) -> &'static str; } diff --git a/src/vcs/git.rs b/src/vcs/git.rs index 5d537a86..eb2697d7 100644 --- a/src/vcs/git.rs +++ b/src/vcs/git.rs @@ -783,6 +783,13 @@ impl VcsBackend for GitBackend { Ok(commits) } + fn get_repository_root(&self) -> Result { + self.repo + .workdir() + .map(|p| p.to_path_buf()) + .ok_or_else(|| VcsError::Other("bare repository has no working directory".to_string())) + } + fn name(&self) -> &'static str { "git" } @@ -1653,4 +1660,39 @@ mod tests { let _ = std::env::set_current_dir(&original); let _ = fs::remove_dir_all(&dir); } + + #[test] + fn test_get_repository_root_from_subdirectory() { + use crate::vcs::test_utils::{cwd_lock, git, make_temp_dir}; + use std::fs; + + let _lock = cwd_lock().lock().unwrap_or_else(|e| e.into_inner()); + let dir = make_temp_dir("git-repo-root"); + let original = std::env::current_dir().expect("get cwd"); + + git(&dir, &["init"]); + git(&dir, &["config", "user.email", "test@example.com"]); + git(&dir, &["config", "user.name", "Test User"]); + + fs::create_dir_all(dir.join("deep/nested")).expect("create subdirs"); + fs::write(dir.join("README.md"), "hello\n").expect("write readme"); + git(&dir, &["add", "."]); + git(&dir, &["commit", "-m", "init"]); + + // Change to nested subdirectory + std::env::set_current_dir(dir.join("deep/nested")).expect("set cwd"); + + let backend = GitBackend::new(Path::new(".")).expect("should discover repo"); + let root = backend.get_repository_root().expect("should get repo root"); + + // Root should be the original repo dir, not the subdirectory + assert_eq!( + root.canonicalize().unwrap(), + dir.canonicalize().unwrap(), + "repo root should match original dir" + ); + + let _ = std::env::set_current_dir(&original); + let _ = fs::remove_dir_all(&dir); + } } diff --git a/src/vcs/jj.rs b/src/vcs/jj.rs index 3b95af7b..d578b8e4 100644 --- a/src/vcs/jj.rs +++ b/src/vcs/jj.rs @@ -970,6 +970,10 @@ impl VcsBackend for JjBackend { }) } + fn get_repository_root(&self) -> Result { + Ok(self.workspace.workspace_root().to_path_buf()) + } + fn name(&self) -> &'static str { "jj" }