huskies: merge 1199 story Orphaned build-dir GC: reclaim dead worktree targets without touching warm caches
This commit is contained in:
@@ -7,6 +7,11 @@ use super::git::{branch_name, remove_worktree_sync, resolve_base_branch};
|
||||
use super::{WorktreeInfo, worktree_path};
|
||||
|
||||
/// Remove a git worktree and its branch.
|
||||
///
|
||||
/// Story 1199: measures the worktree's `target/` size before removal (once
|
||||
/// removed, there's nothing left to measure) and logs bytes reclaimed on
|
||||
/// success, so terminal-story worktree cleanup reports its disk impact the
|
||||
/// same way the on-demand `gc` pass does.
|
||||
pub async fn remove_worktree(
|
||||
project_root: &Path,
|
||||
info: &WorktreeInfo,
|
||||
@@ -14,13 +19,30 @@ pub async fn remove_worktree(
|
||||
) -> Result<(), String> {
|
||||
run_teardown_commands(&info.path, config).await?;
|
||||
|
||||
let target_path = info.path.join("target");
|
||||
let target_bytes = tokio::task::spawn_blocking(move || {
|
||||
crate::service::disk_watch::io::dir_size_bytes(&target_path)
|
||||
})
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
|
||||
let root = project_root.to_path_buf();
|
||||
let wt_path = info.path.clone();
|
||||
let branch = info.branch.clone();
|
||||
|
||||
tokio::task::spawn_blocking(move || remove_worktree_sync(&root, &wt_path, &branch))
|
||||
.await
|
||||
.map_err(|e| format!("spawn_blocking: {e}"))?
|
||||
let result =
|
||||
tokio::task::spawn_blocking(move || remove_worktree_sync(&root, &wt_path, &branch))
|
||||
.await
|
||||
.map_err(|e| format!("spawn_blocking: {e}"))?;
|
||||
|
||||
if result.is_ok() && target_bytes > 0 {
|
||||
crate::slog!(
|
||||
"[worktree-remove] Reclaimed {target_bytes} bytes from '{}' target/ on removal",
|
||||
info.path.display()
|
||||
);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Remove a git worktree by story ID, deriving the path and branch deterministically.
|
||||
@@ -91,6 +113,7 @@ mod tests {
|
||||
gateway_project: None,
|
||||
status_push_enabled: true,
|
||||
merge_failure_block_threshold: 3,
|
||||
gc_min_free_gb: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,4 +228,35 @@ mod tests {
|
||||
.unwrap();
|
||||
assert!(!path.exists());
|
||||
}
|
||||
|
||||
/// Story 1199 AC4: removal reclaims a populated `target/` dir along with
|
||||
/// the rest of the worktree.
|
||||
#[tokio::test]
|
||||
async fn remove_worktree_reclaims_populated_target_dir() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let project_root = tmp.path().join("my-project");
|
||||
fs::create_dir_all(&project_root).unwrap();
|
||||
init_git_repo(&project_root);
|
||||
|
||||
let info = super::super::create::create_worktree(
|
||||
&project_root,
|
||||
"90_reclaim_target",
|
||||
&empty_config(),
|
||||
3001,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let target_dir = info.path.join("target");
|
||||
fs::create_dir_all(&target_dir).unwrap();
|
||||
fs::write(target_dir.join("build_artifact.bin"), vec![0u8; 1024]).unwrap();
|
||||
assert!(target_dir.exists());
|
||||
|
||||
remove_worktree(&project_root, &info, &empty_config())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(!info.path.exists(), "whole worktree must be gone");
|
||||
assert!(!target_dir.exists(), "target/ must be reclaimed");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user