xianyun_cli/commands/
update.rs1use std::sync::Arc;
6
7use anyhow::Context as _;
8use clap::Args;
9use xianyun_core::config::caproni::load_from_context;
10use xianyun_core::config::global::Context;
11use xianyun_executor::exec::CommandRunner;
12use xianyun_update::updater::{UpdateOptions as UpdaterOptions, UpdateStrategy, Updater};
13
14pub type RepoFilter = Box<dyn Fn(&str) -> bool + Send + Sync>;
17
18#[derive(Args, Debug)]
19pub struct UpdateArgs {
20 #[arg(short = 'n', long = "verify-only", visible_alias = "dry-run")]
22 pub verify_only: bool,
23
24 #[arg(long = "stash")]
26 pub stash: bool,
27
28 #[arg(long = "repo")]
30 pub repo: Option<String>,
31
32 #[arg(long = "skip")]
34 pub skip: Option<String>,
35
36 #[arg(short = 'q', long = "quiet")]
38 pub quiet: bool,
39}
40
41pub fn run(args: &UpdateArgs, ctx: &Context) -> anyhow::Result<()> {
42 let executor: Arc<dyn CommandRunner + Send + Sync> =
43 Arc::new(xianyun_executor::exec::Client::new());
44
45 run_update(
46 ctx,
47 &executor,
48 UpdateCommandOptions {
49 verify_only: args.verify_only,
50 stash: args.stash,
51 repo_filter: args.repo.clone().unwrap_or_default(),
52 skip_filter: args.skip.clone().unwrap_or_default(),
53 quiet: args.quiet,
54 },
55 )
56}
57
58pub struct UpdateCommandOptions {
63 pub verify_only: bool,
64 pub stash: bool,
65 pub repo_filter: String,
66 pub skip_filter: String,
67 pub quiet: bool,
68}
69
70pub fn run_update(
81 ctx: &Context,
82 executor: &Arc<dyn CommandRunner + Send + Sync>,
83 opt: UpdateCommandOptions,
84) -> anyhow::Result<()> {
85 let container = load_from_context(ctx).context("loading configuration")?;
86
87 let strategy = if opt.verify_only {
88 UpdateStrategy::VerifyOnly
89 } else {
90 UpdateStrategy::FullUpdate
91 };
92
93 let filter = build_repo_filter(&opt.repo_filter, &opt.skip_filter);
94
95 let updater_options = UpdaterOptions {
96 strategy,
97 stash: opt.stash,
98 filter,
99 quiet: opt.quiet,
100 skip_prepare_hooks: false,
101 };
102
103 let git_backend: Arc<dyn xianyun_backends::git::Backend + Send + Sync> =
104 Arc::new(xianyun_backends::git::Client::new(executor.clone()));
105
106 let updater = Updater::new(git_backend, updater_options);
107
108 let (results, success) = updater.update_repositories(executor.as_ref(), &container);
109
110 print!(
111 "{}",
112 xianyun_update::updater::format_table(&results, opt.quiet)
113 );
114 print!("{}", xianyun_update::updater::format_summary(&results));
115 print!("{}", xianyun_update::updater::format_suggestions(&results));
116
117 if !success {
118 return Err(crate::exitcode::CliExit::new(1).into());
119 }
120
121 Ok(())
122}
123
124#[must_use]
133pub fn build_repo_filter(repo_filter: &str, skip_filter: &str) -> Option<RepoFilter> {
134 if repo_filter.is_empty() && skip_filter.is_empty() {
135 return None;
136 }
137
138 let repo_filter = repo_filter.to_string();
139 let skip_filter = skip_filter.to_string();
140
141 Some(Box::new(move |repo_name: &str| {
142 if !repo_filter.is_empty() && repo_name != repo_filter {
143 return false;
144 }
145
146 if !skip_filter.is_empty() && repo_name == skip_filter {
147 return false;
148 }
149
150 true
151 }))
152}