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 no_refresh_models: false,
42 },
43 };
44
45 let report = if recovered_corrupt_lock {
47 execute_repair_with_collision_cleanup(ctx, &request)?
48 } else {
49 crate::sync::execute(ctx, &request)?
50 };
51
52 output::print_sync_report(&report, json, true);
53
54 if report.has_conflicts() { Ok(1) } else { Ok(0) }
55}
56
57fn execute_repair_with_collision_cleanup(
58 ctx: &super::MarsContext,
59 request: &SyncRequest,
60) -> Result<SyncReport, MarsError> {
61 const MAX_RETRIES: usize = 1024;
62 let mut retries = 0usize;
63
64 loop {
65 match crate::sync::execute(ctx, request) {
66 Ok(report) => return Ok(report),
67 Err(err) => {
68 if let Some(path) = extract_unmanaged_collision_path(&err) {
69 if retries >= MAX_RETRIES {
70 return Err(MarsError::InvalidRequest {
71 message: format!(
72 "repair exceeded {MAX_RETRIES} unmanaged-collision retries while rebuilding from corrupt lock"
73 ),
74 });
75 }
76
77 let mars_dir = ctx.project_root.join(".mars");
78 let full_path = mars_dir.join(path);
79 if full_path.is_dir() {
80 std::fs::remove_dir_all(&full_path)?;
81 } else if full_path.exists() {
82 std::fs::remove_file(&full_path)?;
83 }
84
85 eprintln!(
86 "warning: removing unmanaged path `{}` to rebuild from corrupt lock",
87 path.display()
88 );
89 retries += 1;
90 continue;
91 }
92
93 return Err(err);
94 }
95 }
96 }
97}
98
99fn extract_unmanaged_collision_path(err: &MarsError) -> Option<&std::path::Path> {
100 match err {
101 MarsError::UnmanagedCollision { path, .. } => Some(path.as_path()),
102 _ => None,
103 }
104}