Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
* crash after stashing untracked files when the current directory is one of them ([#2651](https://github.com/gitui-org/gitui/issues/2651))

## [0.28.1] - 2026-03-21

Expand Down
17 changes: 17 additions & 0 deletions asyncgit/src/sync/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,23 @@ impl RepoPath {
Self::Workdir { workdir, .. } => Some(workdir.as_path()),
}
}

/// resolve to the absolute repo root so later operations survive
/// the deletion of the process cwd (see #2651)
pub fn resolve_root(&self) -> Result<Self> {
match self {
Self::Path(_) => {
let repo = repo(self)?;
// bare repos have no workdir
let root = repo
.workdir()
.unwrap_or_else(|| repo.path())
.to_path_buf();
Ok(Self::Path(root))
}
Self::Workdir { .. } => Ok(self.clone()),
}
}
}

impl From<PathBuf> for RepoPath {
Expand Down
23 changes: 22 additions & 1 deletion asyncgit/src/sync/stash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,28 @@ mod tests {
},
utils::{repo_read_file, repo_write_file},
};
use std::{fs::File, io::Write, path::Path};
use std::{fs, fs::File, io::Write, path::Path};

#[test]
fn test_stash_untracked_removes_stored_repo_path() -> Result<()> {
let (_td, repo) = repo_init()?;
let root = repo.path().parent().unwrap();

let sub_dir = root.join("untracked_sub");
fs::create_dir(&sub_dir)?;
File::create(sub_dir.join("foo.txt"))?.write_all(b"foo")?;

// gitui stores the startup path and reopens from it on every call, so a
// path inside the untracked dir is the same trap as a relative "." there
let repo_path = RepoPath::Path(sub_dir).resolve_root()?;

stash_save(&repo_path, None, true, false)?;

// stashing deleted that dir, further operations must still work
assert_eq!(get_stashes(&repo_path)?.len(), 1);

Ok(())
}

#[test]
fn test_smoke() {
Expand Down
4 changes: 3 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,12 @@ macro_rules! log_eprintln {
fn main() -> Result<()> {
let app_start = Instant::now();

let cliargs = process_cmdline()?;
let mut cliargs = process_cmdline()?;

asyncgit::register_tracing_logging();
ensure_valid_path(&cliargs.repo_path)?;
// pin to the repo root so operations survive deletion of the cwd
cliargs.repo_path = cliargs.repo_path.resolve_root()?;

let key_config = KeyConfig::init(
cliargs.key_bindings_path.as_ref(),
Expand Down