Skip to main content

mars_agents/cli/
resolve_cmd.rs

1//! `mars resolve` — mark conflicts as resolved after user fixes them.
2
3use std::path::PathBuf;
4
5use crate::error::MarsError;
6use crate::hash;
7use crate::types::{ContentHash, DestPath};
8
9use super::output;
10
11/// Arguments for `mars resolve`.
12#[derive(Debug, clap::Args)]
13pub struct ResolveArgs {
14    /// Specific file to resolve (default: all conflicted).
15    pub file: Option<PathBuf>,
16}
17
18/// Run `mars resolve`.
19pub fn run(args: &ResolveArgs, ctx: &super::MarsContext, json: bool) -> Result<i32, MarsError> {
20    let mars_dir = ctx.project_root.join(".mars");
21    let lock_path = mars_dir.join("sync.lock");
22    let _sync_lock = crate::fs::FileLock::acquire(&lock_path)?;
23
24    let mut lock = crate::lock::load(&ctx.project_root)?;
25    let mut resolved_files = Vec::new();
26    let mut still_conflicted = Vec::new();
27
28    let items_to_check: Vec<DestPath> = if let Some(file) = &args.file {
29        // Check specific file
30        let rel = DestPath::from(file.as_path());
31        if lock.items.contains_key(&rel) {
32            vec![rel]
33        } else {
34            return Err(MarsError::Source {
35                source_name: "resolve".to_string(),
36                message: format!("{} is not a managed item", file.display()),
37            });
38        }
39    } else {
40        // Check all items
41        lock.items.keys().cloned().collect()
42    };
43
44    for dest_path_str in &items_to_check {
45        let disk_path = mars_dir.join(dest_path_str);
46        if !disk_path.exists() {
47            continue;
48        }
49
50        // Check for conflict markers
51        if crate::merge::file_has_conflict_markers(&disk_path) {
52            still_conflicted.push(dest_path_str.clone());
53            continue;
54        }
55
56        // File has no conflict markers — update lock checksums
57        if let Some(item) = lock.items.get_mut(dest_path_str) {
58            let new_hash = hash::compute_hash(&disk_path, item.kind)?;
59            if new_hash != item.installed_checksum {
60                item.installed_checksum = ContentHash::from(new_hash);
61                resolved_files.push(dest_path_str.to_string());
62            }
63        }
64    }
65
66    // Write updated lock
67    if !resolved_files.is_empty() {
68        crate::lock::write(&ctx.project_root, &lock)?;
69    }
70
71    if json {
72        output::print_json(&serde_json::json!({
73            "resolved": resolved_files,
74            "still_conflicted": still_conflicted,
75        }));
76    } else {
77        for file in &resolved_files {
78            output::print_success(&format!("resolved {file}"));
79        }
80        for file in &still_conflicted {
81            eprintln!("  ! {file} still has conflict markers");
82        }
83        if resolved_files.is_empty() && still_conflicted.is_empty() {
84            output::print_info("no conflicts to resolve");
85        }
86    }
87
88    if still_conflicted.is_empty() {
89        Ok(0)
90    } else {
91        Ok(1)
92    }
93}