use crate::config::OrchestratorConfig;
use crate::error::Result;
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tracing::{debug, warn};
pub fn should_trigger_worktree_command(config: &OrchestratorConfig, is_git_repo: bool) -> bool {
config.get_worktree_command().is_some() && is_git_repo
}
pub fn build_worktree_path(base_dir: &Path) -> PathBuf {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_else(|_| Duration::from_secs(0))
.as_millis();
base_dir.join(format!("proposal-{}", timestamp))
}
pub async fn load_worktrees_with_conflict_check(
repo_root: &Path,
) -> Result<Vec<super::types::WorktreeInfo>> {
use super::types::{MergeConflictInfo, WorktreeInfo};
let worktrees_data = crate::vcs::git::commands::list_worktrees(repo_root).await?;
let mut worktrees: Vec<WorktreeInfo> = worktrees_data
.into_iter()
.map(|(path, head, branch, is_detached, is_main)| WorktreeInfo {
path: PathBuf::from(path),
head,
branch: branch.clone(),
is_detached,
is_main,
merge_conflict: None,
has_commits_ahead: false, is_merging: false,
})
.collect();
let _base_branch = if let Some(main_wt) = worktrees.iter().find(|wt| wt.is_main) {
main_wt.branch.clone()
} else {
match crate::vcs::git::commands::get_current_branch(repo_root).await {
Ok(Some(branch)) => branch,
Ok(None) | Err(_) => {
return Ok(worktrees);
}
}
};
let mut tasks = tokio::task::JoinSet::new();
for (idx, worktree) in worktrees.iter().enumerate() {
if worktree.is_main || worktree.is_detached || worktree.branch.is_empty() {
continue;
}
let wt_path = worktree.path.clone();
let branch_name = worktree.branch.clone();
let base_branch = _base_branch.clone();
tasks.spawn(async move {
let conflict_result =
crate::vcs::git::commands::check_merge_conflicts(&wt_path, &base_branch).await;
let ahead_result = crate::vcs::git::commands::count_commits_ahead(
&wt_path,
&base_branch,
&branch_name,
)
.await;
(idx, conflict_result, ahead_result)
});
}
while let Some(result) = tasks.join_next().await {
match result {
Ok((idx, conflict_result, ahead_result)) => {
match conflict_result {
Ok(conflict_files_opt) => {
if let Some(conflict_files) = conflict_files_opt {
worktrees[idx].merge_conflict =
Some(MergeConflictInfo { conflict_files });
} else {
worktrees[idx].merge_conflict = None;
}
}
Err(e) => {
debug!(
"Conflict check failed for worktree {}: {}",
worktrees[idx].path.display(),
e
);
worktrees[idx].merge_conflict = None;
}
}
match ahead_result {
Ok(count) => {
worktrees[idx].has_commits_ahead = count > 0;
debug!(
"Worktree {} has {} commits ahead of base",
worktrees[idx].path.display(),
count
);
}
Err(e) => {
debug!(
"Commits ahead check failed for worktree {}: {}",
worktrees[idx].path.display(),
e
);
worktrees[idx].has_commits_ahead = false;
}
}
}
Err(e) => {
warn!("Worktree check task panicked: {}", e);
}
}
}
Ok(worktrees)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::OrchestratorConfig;
#[test]
fn test_should_trigger_worktree_command_missing_config() {
let config = OrchestratorConfig::default();
assert!(!should_trigger_worktree_command(&config, true));
}
#[test]
fn test_should_trigger_worktree_command_not_git_repo() {
let config = OrchestratorConfig {
worktree_command: Some("cmd {workspace_dir}".to_string()),
..Default::default()
};
assert!(!should_trigger_worktree_command(&config, false));
}
#[test]
fn test_should_trigger_worktree_command_enabled() {
let config = OrchestratorConfig {
worktree_command: Some("cmd {repo_root}".to_string()),
..Default::default()
};
assert!(should_trigger_worktree_command(&config, true));
}
}