From 0d69bfca7a80d45961a5a2bfa895434edbab9e2e Mon Sep 17 00:00:00 2001 From: ychampion Date: Thu, 9 Jul 2026 01:14:16 +0000 Subject: [PATCH] fix: resolve --file from subdirectories --- CHANGELOG.md | 1 + src/app.rs | 79 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68c2158509..3aaee51b46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * crash when opening submodule ([#2895](https://github.com/gitui-org/gitui/issues/2895)) * when staging the last file in a directory, the first item after the directory is no longer skipped [[@Tillerino](https://github.com/Tillerino)] ([#2748](https://github.com/gitui-org/gitui/issues/2748)) * index-out-of-bounds panic when unstaging lines near the end of a diff ([#2953](https://github.com/gitui-org/gitui/issues/2953)) +* make `--file` resolve paths correctly when GitUI starts from a repository subdirectory ([#2871](https://github.com/gitui-org/gitui/issues/2871)) ## [0.28.1] - 2026-03-21 diff --git a/src/app.rs b/src/app.rs index ddb157af3f..28c27f867e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -178,14 +178,11 @@ impl App { let mut select_file: Option = None; let tab = if let Some(file) = cliargs.select_file { - // convert to relative git path - if let Ok(abs) = file.canonicalize() { - if let Ok(path) = abs.strip_prefix( - env.repo.borrow().gitpath().canonicalize()?, - ) { - select_file = Some(Path::new(".").join(path)); - } - } + select_file = normalize_select_file( + &env.repo.borrow(), + &file, + &std::env::current_dir()?, + )?; 2 } else { env.options.borrow().current_tab() @@ -490,6 +487,72 @@ impl App { } } +fn normalize_select_file( + repo_path: &RepoPath, + file: &Path, + cwd: &Path, +) -> Result> { + let repo_root = + PathBuf::from(repo_work_dir(repo_path)?).canonicalize()?; + let candidates = if file.is_absolute() { + vec![file.to_path_buf()] + } else { + vec![cwd.join(file), repo_root.join(file)] + }; + + for candidate in candidates { + if let Ok(abs) = candidate.canonicalize() { + if let Ok(path) = abs.strip_prefix(&repo_root) { + return Ok(Some(Path::new(".").join(path))); + } + } + } + + Ok(None) +} + +#[cfg(test)] +mod tests { + use super::normalize_select_file; + use asyncgit::sync::RepoPath; + use git2_testing::repo_init; + use pretty_assertions::assert_eq; + use std::path::Path; + + #[test] + fn select_file_is_relative_to_repo_when_started_from_subdir() { + let (temp_dir, _repo) = repo_init(); + let root = temp_dir.path(); + let file = root.join("frontend/src/index.html"); + std::fs::create_dir_all(file.parent().unwrap()).unwrap(); + std::fs::write(&file, "
\n").unwrap(); + + let repo_path = RepoPath::Path(root.to_path_buf()); + let cwd = root.join("frontend"); + let expected = + Some(Path::new(".").join("frontend/src/index.html")); + + assert_eq!( + normalize_select_file( + &repo_path, + Path::new("src/index.html"), + &cwd, + ) + .unwrap(), + expected + ); + assert_eq!( + normalize_select_file( + &repo_path, + Path::new("frontend/src/index.html"), + &cwd, + ) + .unwrap(), + expected + ); + } +} + // private impls impl App { accessors!(