mars_agents/cli/
resolve_cmd.rs1use std::path::PathBuf;
4
5use crate::error::MarsError;
6use crate::hash;
7use crate::types::{ContentHash, DestPath};
8
9use super::output;
10
11#[derive(Debug, clap::Args)]
13pub struct ResolveArgs {
14 pub file: Option<PathBuf>,
16}
17
18pub 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 let rel_input = if file.is_absolute() {
31 file.strip_prefix(&mars_dir).map_or_else(
32 |_| file.to_string_lossy().to_string(),
33 |r| r.to_string_lossy().to_string(),
34 )
35 } else {
36 file.to_string_lossy().to_string()
37 };
38 let rel = DestPath::new(&rel_input).map_err(|e| MarsError::Source {
39 source_name: "resolve".to_string(),
40 message: format!("invalid managed item path `{}`: {e}", file.display()),
41 })?;
42 if lock.contains_dest_path(&rel) {
43 vec![rel]
44 } else {
45 return Err(MarsError::Source {
46 source_name: "resolve".to_string(),
47 message: format!("{} is not a managed item", file.display()),
48 });
49 }
50 } else {
51 lock.all_output_dest_paths().cloned().collect()
53 };
54
55 for dest_path_str in &items_to_check {
56 let disk_path = dest_path_str.resolve(&mars_dir);
57 if !disk_path.exists() {
58 continue;
59 }
60
61 if crate::merge::file_has_conflict_markers(&disk_path) {
63 still_conflicted.push(dest_path_str.clone());
64 continue;
65 }
66
67 'outer: for item_v2 in lock.items.values_mut() {
69 for output in &mut item_v2.outputs {
70 if &output.dest_path == dest_path_str {
71 let new_hash = hash::compute_hash(&disk_path, item_v2.kind)?;
72 if new_hash != output.installed_checksum {
73 output.installed_checksum = ContentHash::from(new_hash);
74 resolved_files.push(dest_path_str.to_string());
75 }
76 break 'outer;
77 }
78 }
79 }
80 }
81
82 if !resolved_files.is_empty() {
84 crate::lock::write(&ctx.project_root, &lock)?;
85 }
86
87 if json {
88 output::print_json(&serde_json::json!({
89 "resolved": resolved_files,
90 "still_conflicted": still_conflicted,
91 }));
92 } else {
93 for file in &resolved_files {
94 output::print_success(&format!("resolved {file}"));
95 }
96 for file in &still_conflicted {
97 eprintln!(" ! {file} still has conflict markers");
98 }
99 if resolved_files.is_empty() && still_conflicted.is_empty() {
100 output::print_info("no conflicts to resolve");
101 }
102 }
103
104 if still_conflicted.is_empty() {
105 Ok(0)
106 } else {
107 Ok(1)
108 }
109}