mars_agents/cli/
repair.rs1use crate::error::{LockError, MarsError};
4use crate::lock::LockFile;
5use crate::sync::{ResolutionMode, SyncOptions, SyncReport, SyncRequest};
6
7use super::output;
8
9#[derive(Debug, clap::Args)]
11pub struct RepairArgs {}
12
13pub fn run(_args: &RepairArgs, ctx: &super::MarsContext, json: bool) -> Result<i32, MarsError> {
19 if !json {
20 output::print_info("repairing — re-syncing from dependencies...");
21 }
22
23 let recovered_corrupt_lock = match crate::lock::load(&ctx.project_root) {
24 Ok(_) => false,
25 Err(MarsError::Lock(LockError::Corrupt { message })) => {
26 eprintln!("warning: {message}");
27 eprintln!("warning: lock is corrupt, rebuilding from mars.toml + dependencies");
28 crate::lock::write(&ctx.project_root, &LockFile::empty())?;
29 true
30 }
31 Err(err) => return Err(err),
32 };
33
34 let request = SyncRequest {
35 resolution: ResolutionMode::Normal,
36 mutation: None,
37 options: SyncOptions {
38 force: true,
39 dry_run: false,
40 frozen: false,
41 refresh_models: false,
42 no_refresh_models: false,
43 },
44 };
45
46 let report = if recovered_corrupt_lock {
48 execute_repair_with_collision_cleanup(ctx, &request)?
49 } else {
50 crate::sync::execute(ctx, &request)?
51 };
52
53 output::print_sync_report(&report, json, true);
54
55 if report.has_conflicts() { Ok(1) } else { Ok(0) }
56}
57
58fn execute_repair_with_collision_cleanup(
59 ctx: &super::MarsContext,
60 request: &SyncRequest,
61) -> Result<SyncReport, MarsError> {
62 const MAX_RETRIES: usize = 1024;
63 let mut retries = 0usize;
64
65 loop {
66 match crate::sync::execute(ctx, request) {
67 Ok(report) => return Ok(report),
68 Err(err) => {
69 if let Some(path) = extract_unmanaged_collision_path(&err) {
70 if retries >= MAX_RETRIES {
71 return Err(MarsError::InvalidRequest {
72 message: format!(
73 "repair exceeded {MAX_RETRIES} unmanaged-collision retries while rebuilding from corrupt lock"
74 ),
75 });
76 }
77
78 let mars_dir = ctx.project_root.join(".mars");
79 let full_path = mars_dir.join(path);
80 if full_path.is_dir() {
81 std::fs::remove_dir_all(&full_path)?;
82 } else if full_path.exists() {
83 std::fs::remove_file(&full_path)?;
84 }
85
86 eprintln!(
87 "warning: removing unmanaged path `{}` to rebuild from corrupt lock",
88 path.display()
89 );
90 retries += 1;
91 continue;
92 }
93
94 return Err(err);
95 }
96 }
97 }
98}
99
100fn extract_unmanaged_collision_path(err: &MarsError) -> Option<&std::path::Path> {
101 match err {
102 MarsError::UnmanagedCollision { path, .. } => Some(path.as_path()),
103 _ => None,
104 }
105}