huskies: merge 1244 refactor Deduplicate the worktree module cluster (cleanup/remove/sweep/create/lifecycle share ~60-line clones)

This commit is contained in:
Huskies Agent
2026-07-21 20:19:29 +00:00
parent c58e204e8b
commit c3cad6b3d8
8 changed files with 95 additions and 324 deletions
+1 -29
View File
@@ -184,39 +184,11 @@ pub(crate) async fn on_terminal_transition(project_root: &Path, story_id: &str)
#[cfg(test)]
mod tests {
use super::*;
use crate::worktree::test_support::init_git_repo;
use std::fs;
use std::process::Command;
use tempfile::TempDir;
fn init_git_repo(dir: &Path) {
use crate::git_test_support::git_ok;
git_ok(
Command::new("git").args(["init"]).current_dir(dir).output(),
"git init",
);
git_ok(
Command::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(dir)
.output(),
"git config user.email",
);
git_ok(
Command::new("git")
.args(["config", "user.name", "Test"])
.current_dir(dir)
.output(),
"git config user.name",
);
git_ok(
Command::new("git")
.args(["commit", "--allow-empty", "-m", "init"])
.current_dir(dir)
.output(),
"git commit",
);
}
fn setup_project(tmp: &TempDir) -> PathBuf {
let root = tmp.path().join("project");
fs::create_dir_all(root.join(".huskies")).unwrap();
+1 -71
View File
@@ -169,81 +169,11 @@ pub fn format_report(report: &CleanupReport, confirm: bool) -> String {
#[cfg(test)]
mod tests {
use super::*;
use crate::config::WatcherConfig;
use chrono::Utc;
use crate::worktree::test_support::{done_stage, empty_config, init_git_repo};
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use tempfile::TempDir;
fn init_git_repo(dir: &std::path::Path) {
use crate::git_test_support::git_ok;
git_ok(
Command::new("git").args(["init"]).current_dir(dir).output(),
"git init",
);
git_ok(
Command::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(dir)
.output(),
"git config user.email",
);
git_ok(
Command::new("git")
.args(["config", "user.name", "Test"])
.current_dir(dir)
.output(),
"git config user.name",
);
git_ok(
Command::new("git")
.args(["commit", "--allow-empty", "-m", "init"])
.current_dir(dir)
.output(),
"git commit",
);
}
fn empty_config() -> ProjectConfig {
ProjectConfig {
component: vec![],
agent: vec![],
watcher: WatcherConfig::default(),
disk_watch: Default::default(),
default_qa: "server".to_string(),
default_coder_model: None,
max_coders: None,
max_retries: 2,
base_branch: None,
rate_limit_notifications: true,
web_ui_status_consumer: true,
matrix_status_consumer: true,
slack_status_consumer: true,
discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None,
rendezvous: None,
trusted_keys: Vec::new(),
crdt_require_token: false,
crdt_tokens: Vec::new(),
max_mesh_peers: 3,
gateway_url: None,
gateway_project: None,
status_push_enabled: true,
merge_failure_block_threshold: 3,
gc_min_free_gb: 0,
snapshot_interval_ops: 5000,
}
}
fn done_stage() -> Stage {
Stage::Done {
merged_at: Utc::now(),
merge_commit: crate::pipeline_state::GitSha("abc123".to_string()),
}
}
async fn setup_project_with_real_worktree(story_id: &str) -> (TempDir, PathBuf) {
let tmp = TempDir::new().unwrap();
let project_root = tmp.path().join("project");
+2 -62
View File
@@ -210,72 +210,12 @@ pub(crate) async fn run_shell_command(cmd: &str, cwd: &Path) -> Result<(), Strin
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{ComponentConfig, WatcherConfig};
use crate::config::ComponentConfig;
use crate::worktree::test_support::{empty_config, init_git_repo};
use std::fs;
use std::process::Command;
use tempfile::TempDir;
fn init_git_repo(dir: &Path) {
use crate::git_test_support::git_ok;
git_ok(
Command::new("git").args(["init"]).current_dir(dir).output(),
"git init",
);
git_ok(
Command::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(dir)
.output(),
"git config user.email",
);
git_ok(
Command::new("git")
.args(["config", "user.name", "Test"])
.current_dir(dir)
.output(),
"git config user.name",
);
git_ok(
Command::new("git")
.args(["commit", "--allow-empty", "-m", "init"])
.current_dir(dir)
.output(),
"git commit",
);
}
fn empty_config() -> ProjectConfig {
ProjectConfig {
component: vec![],
agent: vec![],
watcher: WatcherConfig::default(),
disk_watch: Default::default(),
default_qa: "server".to_string(),
default_coder_model: None,
max_coders: None,
max_retries: 2,
base_branch: None,
rate_limit_notifications: true,
web_ui_status_consumer: true,
matrix_status_consumer: true,
slack_status_consumer: true,
discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None,
rendezvous: None,
trusted_keys: Vec::new(),
crdt_require_token: false,
crdt_tokens: Vec::new(),
max_mesh_peers: 3,
gateway_url: None,
gateway_project: None,
status_push_enabled: true,
merge_failure_block_threshold: 3,
gc_min_free_gb: 0,
snapshot_interval_ops: 5000,
}
}
fn failing_setup_config() -> ProjectConfig {
ProjectConfig {
component: vec![ComponentConfig {
+1 -29
View File
@@ -232,38 +232,10 @@ pub fn migrate_slug_paths(project_root: &Path, migrations: &[(String, String)])
#[cfg(test)]
mod tests {
use super::*;
use crate::worktree::test_support::init_git_repo;
use std::fs;
use tempfile::TempDir;
fn init_git_repo(dir: &Path) {
use crate::git_test_support::git_ok;
git_ok(
Command::new("git").args(["init"]).current_dir(dir).output(),
"git init",
);
git_ok(
Command::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(dir)
.output(),
"git config user.email",
);
git_ok(
Command::new("git")
.args(["config", "user.name", "Test"])
.current_dir(dir)
.output(),
"git config user.name",
);
git_ok(
Command::new("git")
.args(["commit", "--allow-empty", "-m", "init"])
.current_dir(dir)
.output(),
"git commit",
);
}
#[test]
fn branch_name_format() {
assert_eq!(branch_name("42_my_story"), "feature/story-42_my_story");
+4
View File
@@ -6,6 +6,10 @@ mod create;
mod git;
mod remove;
mod sweep;
/// Shared test fixtures (`init_git_repo`, `empty_config`) reused across the
/// worktree module's test suites and the pool worktree-lifecycle tests.
#[cfg(test)]
pub(crate) mod test_support;
pub use cleanup::{format_report, run_cleanup};
pub use create::create_worktree;
+1 -63
View File
@@ -68,72 +68,10 @@ pub async fn remove_worktree_by_story_id(
#[cfg(test)]
mod tests {
use super::*;
use crate::config::WatcherConfig;
use crate::worktree::test_support::{empty_config, init_git_repo};
use std::fs;
use std::process::Command;
use tempfile::TempDir;
fn init_git_repo(dir: &std::path::Path) {
use crate::git_test_support::git_ok;
git_ok(
Command::new("git").args(["init"]).current_dir(dir).output(),
"git init",
);
git_ok(
Command::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(dir)
.output(),
"git config user.email",
);
git_ok(
Command::new("git")
.args(["config", "user.name", "Test"])
.current_dir(dir)
.output(),
"git config user.name",
);
git_ok(
Command::new("git")
.args(["commit", "--allow-empty", "-m", "init"])
.current_dir(dir)
.output(),
"git commit",
);
}
fn empty_config() -> ProjectConfig {
ProjectConfig {
component: vec![],
agent: vec![],
watcher: WatcherConfig::default(),
disk_watch: Default::default(),
default_qa: "server".to_string(),
default_coder_model: None,
max_coders: None,
max_retries: 2,
base_branch: None,
rate_limit_notifications: true,
web_ui_status_consumer: true,
matrix_status_consumer: true,
slack_status_consumer: true,
discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None,
rendezvous: None,
trusted_keys: Vec::new(),
crdt_require_token: false,
crdt_tokens: Vec::new(),
max_mesh_peers: 3,
gateway_url: None,
gateway_project: None,
status_push_enabled: true,
merge_failure_block_threshold: 3,
gc_min_free_gb: 0,
snapshot_interval_ops: 5000,
}
}
#[tokio::test]
async fn remove_worktree_by_story_id_returns_err_when_not_found() {
let tmp = TempDir::new().unwrap();
+1 -70
View File
@@ -89,82 +89,13 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::config::WatcherConfig;
use crate::worktree::test_support::{done_stage, empty_config, init_git_repo};
use chrono::Utc;
use std::fs;
use std::num::NonZeroU32;
use std::path::PathBuf;
use std::process::Command;
use tempfile::TempDir;
fn init_git_repo(dir: &Path) {
use crate::git_test_support::git_ok;
git_ok(
Command::new("git").args(["init"]).current_dir(dir).output(),
"git init",
);
git_ok(
Command::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(dir)
.output(),
"git config user.email",
);
git_ok(
Command::new("git")
.args(["config", "user.name", "Test"])
.current_dir(dir)
.output(),
"git config user.name",
);
git_ok(
Command::new("git")
.args(["commit", "--allow-empty", "-m", "init"])
.current_dir(dir)
.output(),
"git commit",
);
}
fn empty_config() -> ProjectConfig {
ProjectConfig {
component: vec![],
agent: vec![],
watcher: WatcherConfig::default(),
disk_watch: Default::default(),
default_qa: "server".to_string(),
default_coder_model: None,
max_coders: None,
max_retries: 2,
base_branch: None,
rate_limit_notifications: true,
web_ui_status_consumer: true,
matrix_status_consumer: true,
slack_status_consumer: true,
discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None,
rendezvous: None,
trusted_keys: Vec::new(),
crdt_require_token: false,
crdt_tokens: Vec::new(),
max_mesh_peers: 3,
gateway_url: None,
gateway_project: None,
status_push_enabled: true,
merge_failure_block_threshold: 3,
gc_min_free_gb: 0,
snapshot_interval_ops: 5000,
}
}
fn done_stage() -> Stage {
Stage::Done {
merged_at: Utc::now(),
merge_commit: crate::pipeline_state::GitSha("abc123".to_string()),
}
}
fn archived_stage() -> Stage {
Stage::Archived {
archived_at: Utc::now(),
+84
View File
@@ -0,0 +1,84 @@
//! Shared test fixtures for worktree module tests: a throwaway git repo and a
//! minimal [`ProjectConfig`] with no components, used by the worktree
//! create/remove/cleanup/sweep tests and the pool worktree-lifecycle
//! subscriber tests so each doesn't hand-roll its own copy.
use crate::config::{ProjectConfig, WatcherConfig};
use crate::git_test_support::git_ok;
use crate::pipeline_state::{GitSha, Stage};
use chrono::Utc;
use std::path::Path;
use std::process::Command;
/// Initialize a git repo at `dir` with test identity configured and a
/// committed empty initial commit, so worktree creation has a valid base
/// branch to fork from.
pub(crate) fn init_git_repo(dir: &Path) {
git_ok(
Command::new("git").args(["init"]).current_dir(dir).output(),
"git init",
);
git_ok(
Command::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(dir)
.output(),
"git config user.email",
);
git_ok(
Command::new("git")
.args(["config", "user.name", "Test"])
.current_dir(dir)
.output(),
"git config user.name",
);
git_ok(
Command::new("git")
.args(["commit", "--allow-empty", "-m", "init"])
.current_dir(dir)
.output(),
"git commit",
);
}
/// A minimal [`ProjectConfig`] with no components, suitable for worktree
/// tests that don't exercise setup/teardown commands.
pub(crate) fn empty_config() -> ProjectConfig {
ProjectConfig {
component: vec![],
agent: vec![],
watcher: WatcherConfig::default(),
disk_watch: Default::default(),
default_qa: "server".to_string(),
default_coder_model: None,
max_coders: None,
max_retries: 2,
base_branch: None,
rate_limit_notifications: true,
web_ui_status_consumer: true,
matrix_status_consumer: true,
slack_status_consumer: true,
discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None,
rendezvous: None,
trusted_keys: Vec::new(),
crdt_require_token: false,
crdt_tokens: Vec::new(),
max_mesh_peers: 3,
gateway_url: None,
gateway_project: None,
status_push_enabled: true,
merge_failure_block_threshold: 3,
gc_min_free_gb: 0,
snapshot_interval_ops: 5000,
}
}
/// A `Stage::Done` fixture for tests that need a terminal-stage story.
pub(crate) fn done_stage() -> Stage {
Stage::Done {
merged_at: Utc::now(),
merge_commit: GitSha("abc123".to_string()),
}
}