1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use anyhow::Result;
use clap::{Parser, Subcommand};
use crate::{
cli::banner,
cli::output,
sync::{
manager::{REPOS, SyncManager},
status::StatusDisplay,
},
};
#[derive(Parser, Clone)]
#[command(about = "File synchronization from remote repositories")]
pub struct SyncArgs {
#[command(subcommand)]
pub command: Option<SyncSubcommand>,
/// Force update, bypass interactive mode and update all changes without prompting
#[arg(long)]
pub force: bool,
/// Bootstrap from a specific repository (initial setup)
#[arg(long)]
pub repo: Option<String>,
/// Specific version to sync (tag, branch, or commit)
#[arg(long)]
pub version: Option<String>,
/// Source directory in the remote repository to sync from (default: ".")
#[arg(long)]
pub source_path: Option<String>,
/// Destination directory to sync files to (default: ".")
#[arg(long)]
pub dest_path: Option<String>,
/// Include patterns for files to sync (can be specified multiple times)
#[arg(long, action = clap::ArgAction::Append)]
pub include: Vec<String>,
/// Exclude patterns for files to skip (can be specified multiple times)
#[arg(long, action = clap::ArgAction::Append)]
pub exclude: Vec<String>,
}
#[derive(Subcommand, Clone)]
pub enum SyncSubcommand {
/// Show sync status and configuration
Status,
/// Update files from configured repositories (interactive by default)
Update {
/// Force update, bypass interactive mode and update all changes without prompting
#[arg(long)]
force: bool,
/// Bootstrap from a specific repository (initial setup)
#[arg(long)]
repo: Option<String>,
/// Specific version to sync (tag, branch, or commit)
#[arg(long)]
version: Option<String>,
/// Source directory in the remote repository to sync from (default: ".")
#[arg(long)]
source_path: Option<String>,
/// Destination directory to sync files to (default: ".")
#[arg(long)]
dest_path: Option<String>,
/// Include patterns for files to sync (can be specified multiple times)
#[arg(long, action = clap::ArgAction::Append)]
include: Vec<String>,
/// Exclude patterns for files to skip (can be specified multiple times)
#[arg(long, action = clap::ArgAction::Append)]
exclude: Vec<String>,
},
/// Show differences between local and remote files (what has drifted)
Diff,
}
pub async fn execute(args: SyncArgs) -> Result<()> {
match args.command {
Some(SyncSubcommand::Status) => execute_status().await,
Some(SyncSubcommand::Update { force, .. }) => {
// Prefer subcommand args over main args for force flag
let final_force = force || args.force;
execute_update(final_force).await
}
Some(SyncSubcommand::Diff) => execute_diff().await,
// Default to update behavior when no subcommand is provided, using main args
None => execute_update(args.force).await,
}
}
async fn execute_status() -> Result<()> {
// Print banner without context
banner::print_banner(None);
let manager = SyncManager::new()?;
let status_display = StatusDisplay::new(&manager);
status_display.show_detailed_status()
}
async fn execute_diff() -> Result<()> {
// Print banner without context
banner::print_banner(None);
let mut manager = SyncManager::new()?;
// Check if we have any configuration
if REPOS.is_empty() {
output::styled!("{} No sync configuration found", ("⚠️", "warning_symbol"));
return Ok(());
}
output::styled!("{} Checking for differences...", ("🔍", "info_symbol"));
// Use the dedicated diff-only method (no interactive prompts)
manager.show_all_diffs().await?;
Ok(())
}
async fn execute_update(force: bool) -> Result<()> {
// Print banner without context
banner::print_banner(None);
let mut manager = SyncManager::new()?;
let interactive = !force;
let updated_files = manager.update_all_repos(interactive).await?;
// Show results for force mode
if force {
if updated_files.is_empty() {
output::styled!("<info> No files were updated");
} else {
output::styled!(
"{} Successfully updated {} files:",
("✅", "success_symbol"),
(updated_files.len().to_string(), "property")
);
for file in &updated_files {
println!(" • {}", output::file_path(file.display().to_string()));
}
}
}
// Interactive mode shows its own summary
Ok(())
}