huskies: merge 651_bug_remove_git_reset_clean_behaviour_from_bug_645_s_recovery_path_uncommitted_work_in_worktrees_is_never_junk

This commit is contained in:
dave
2026-04-26 16:42:10 +00:00
parent 365b907ba4
commit ff51a1a465
2 changed files with 303 additions and 40 deletions
+52 -13
View File
@@ -43,22 +43,47 @@ pub(crate) fn worktree_has_committed_work(wt_path: &Path) -> bool {
/// Run `cargo check` in the given worktree directory to verify committed code compiles.
///
/// Resets any dirty (uncommitted) files first via `git checkout .` so that only
/// the committed state is evaluated. Used as part of the "work survived" check
/// when an agent crashes mid-output (bug 645).
/// Stashes any dirty (uncommitted) files first so that only the committed state
/// is evaluated, then restores them afterward. Uncommitted work in worktrees is
/// never junk — it may be the next agent session's starting point (bug 651).
///
/// Used as part of the "work survived" check when an agent crashes mid-output
/// (bug 645).
pub(crate) fn cargo_check_in_worktree(wt_path: &Path) -> bool {
// Reset uncommitted changes so cargo check evaluates only committed code.
let _ = Command::new("git")
.args(["checkout", "."])
// Stash uncommitted changes (including untracked files) so cargo check
// evaluates only committed code. We restore them afterward.
let stashed = Command::new("git")
.args([
"stash",
"push",
"--include-untracked",
"-m",
"cargo-check-temp",
])
.current_dir(wt_path)
.output();
.output()
.map(|o| {
o.status.success()
&& !String::from_utf8_lossy(&o.stdout).contains("No local changes to save")
})
.unwrap_or(false);
Command::new("cargo")
let result = Command::new("cargo")
.args(["check"])
.current_dir(wt_path)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
.unwrap_or(false);
// Restore stashed uncommitted changes.
if stashed {
let _ = Command::new("git")
.args(["stash", "pop"])
.current_dir(wt_path)
.output();
}
result
}
/// Check whether the given directory has any uncommitted git changes.
@@ -457,9 +482,10 @@ mod tests {
// ── cargo_check_in_worktree tests ────────────────────────────────────────
/// Bug 645: cargo_check_in_worktree resets dirty files before checking.
/// Bug 645 + 651: cargo_check_in_worktree stashes dirty files before
/// checking committed code and restores them afterward.
#[test]
fn cargo_check_in_worktree_resets_dirty_files() {
fn cargo_check_in_worktree_stashes_and_restores_dirty_files() {
use std::fs;
let tmp = tempfile::tempdir().unwrap();
let project_root = tmp.path().join("project");
@@ -530,11 +556,24 @@ mod tests {
// Now simulate a crash leaving dirty files (broken syntax).
fs::write(wt_path.join("src/lib.rs"), "THIS IS BROKEN SYNTAX!!!\n").unwrap();
// Also add an untracked file.
fs::write(wt_path.join("crash_residue.txt"), "untracked junk").unwrap();
// cargo_check_in_worktree should reset dirty files and check committed code.
// cargo_check_in_worktree should stash dirty files, check committed code, and restore.
assert!(
cargo_check_in_worktree(&wt_path),
"cargo check should pass on committed code after resetting dirty files"
"cargo check should pass on committed code after stashing dirty files"
);
// Bug 651: dirty files must be restored after cargo check.
assert_eq!(
fs::read_to_string(wt_path.join("src/lib.rs")).unwrap(),
"THIS IS BROKEN SYNTAX!!!\n",
"modified tracked file should be restored after cargo check"
);
assert!(
wt_path.join("crash_residue.txt").exists(),
"untracked file should be restored after cargo check"
);
}
}