feat: auto-prune inactive ecc2 worktrees

This commit is contained in:
Affaan Mustafa
2026-04-08 16:08:29 -07:00
parent b3f781a648
commit adfe8a8311
4 changed files with 195 additions and 2 deletions

View File

@@ -37,6 +37,10 @@ pub async fn run(db: StateStore, cfg: Config) -> Result<()> {
tracing::error!("Worktree auto-merge pass failed: {e}");
}
if let Err(e) = maybe_auto_prune_inactive_worktrees(&db).await {
tracing::error!("Worktree auto-prune pass failed: {e}");
}
time::sleep(heartbeat_interval).await;
}
}
@@ -404,6 +408,46 @@ where
Ok(merged)
}
async fn maybe_auto_prune_inactive_worktrees(db: &StateStore) -> Result<usize> {
maybe_auto_prune_inactive_worktrees_with_recorder(
|| manager::prune_inactive_worktrees(db),
|pruned, active| db.record_daemon_auto_prune_pass(pruned, active),
)
.await
}
async fn maybe_auto_prune_inactive_worktrees_with<F, Fut>(prune: F) -> Result<usize>
where
F: Fn() -> Fut,
Fut: Future<Output = Result<manager::WorktreePruneOutcome>>,
{
maybe_auto_prune_inactive_worktrees_with_recorder(prune, |_, _| Ok(())).await
}
async fn maybe_auto_prune_inactive_worktrees_with_recorder<F, Fut, R>(
prune: F,
mut record: R,
) -> Result<usize>
where
F: Fn() -> Fut,
Fut: Future<Output = Result<manager::WorktreePruneOutcome>>,
R: FnMut(usize, usize) -> Result<()>,
{
let outcome = prune().await?;
let pruned = outcome.cleaned_session_ids.len();
let active = outcome.active_with_worktree_ids.len();
record(pruned, active)?;
if pruned > 0 {
tracing::info!("Auto-pruned {pruned} inactive worktree(s)");
}
if active > 0 {
tracing::info!("Skipped {active} active worktree(s) during auto-prune");
}
Ok(pruned)
}
#[cfg(unix)]
fn pid_is_alive(pid: u32) -> bool {
if pid == 0 {
@@ -769,6 +813,9 @@ mod tests {
last_auto_merge_conflicted_skipped: 0,
last_auto_merge_dirty_skipped: 0,
last_auto_merge_failed: 0,
last_auto_prune_at: None,
last_auto_prune_pruned: 0,
last_auto_prune_active_skipped: 0,
};
let order = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let dispatch_order = order.clone();
@@ -832,6 +879,9 @@ mod tests {
last_auto_merge_conflicted_skipped: 0,
last_auto_merge_dirty_skipped: 0,
last_auto_merge_failed: 0,
last_auto_prune_at: None,
last_auto_prune_pruned: 0,
last_auto_prune_active_skipped: 0,
};
let recorded = std::sync::Arc::new(std::sync::Mutex::new(None));
let recorded_clone = recorded.clone();
@@ -887,6 +937,9 @@ mod tests {
last_auto_merge_conflicted_skipped: 0,
last_auto_merge_dirty_skipped: 0,
last_auto_merge_failed: 0,
last_auto_prune_at: None,
last_auto_prune_pruned: 0,
last_auto_prune_active_skipped: 0,
};
let calls = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let calls_clone = calls.clone();
@@ -943,6 +996,9 @@ mod tests {
last_auto_merge_conflicted_skipped: 0,
last_auto_merge_dirty_skipped: 0,
last_auto_merge_failed: 0,
last_auto_prune_at: None,
last_auto_prune_pruned: 0,
last_auto_prune_active_skipped: 0,
};
let calls = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let calls_clone = calls.clone();
@@ -999,6 +1055,9 @@ mod tests {
last_auto_merge_conflicted_skipped: 0,
last_auto_merge_dirty_skipped: 0,
last_auto_merge_failed: 0,
last_auto_prune_at: None,
last_auto_prune_pruned: 0,
last_auto_prune_active_skipped: 0,
};
let rebalance_calls = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let rebalance_calls_clone = rebalance_calls.clone();
@@ -1199,4 +1258,28 @@ mod tests {
assert_eq!(merged, 2);
Ok(())
}
#[tokio::test]
async fn maybe_auto_prune_inactive_worktrees_records_pruned_and_active_counts() -> Result<()> {
let recorded = std::sync::Arc::new(std::sync::Mutex::new(None));
let recorded_clone = recorded.clone();
let pruned = maybe_auto_prune_inactive_worktrees_with_recorder(
|| async move {
Ok(manager::WorktreePruneOutcome {
cleaned_session_ids: vec!["stopped-a".to_string(), "stopped-b".to_string()],
active_with_worktree_ids: vec!["running-a".to_string()],
})
},
move |pruned, active| {
*recorded_clone.lock().unwrap() = Some((pruned, active));
Ok(())
},
)
.await?;
assert_eq!(pruned, 2);
assert_eq!(*recorded.lock().unwrap(), Some((2, 1)));
Ok(())
}
}