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
use eyre::Result;
use crate::config::{Config, Settings};
use crate::system;
/// Apply dotfiles from `[dotfiles]`
///
/// Applies configured whole-file entries and edits that aren't in their
/// desired state. Whole-file entries may symlink, copy, or render templates.
/// Edit entries manage a marker-delimited block or a single line in a file
/// mise doesn't otherwise own.
#[derive(Debug, usage_rs::Args)]
#[usage(
verbatim_doc_comment,
example(
r###"mise dot apply
mise dot apply --dry-run
mise dot apply --force --yes"###
)
)]
pub(crate) struct DotfilesApply {
/// Only apply these targets
#[usage(value_name = "TARGET")]
targets: Vec<String>,
/// Overwrite existing files that conflict with whole-file dotfile entries
#[usage(long, short)]
force: bool,
/// Print the actions that would run without writing anything
#[usage(long, short = 'n')]
dry_run: bool,
/// Skip the confirmation prompt
#[usage(long, short)]
yes: bool,
/// Prompt securely for missing bootstrap secret inputs
#[usage(long)]
prompt_secrets: bool,
}
impl DotfilesApply {
pub(crate) fn dry_run(&self) -> bool {
self.dry_run
}
/// Load and filter the configured whole-file and edit requests.
pub(crate) fn requests(
&self,
config: &Config,
) -> Result<(
Vec<system::files::FileRequest>,
Vec<system::edits::EditRequest>,
)> {
super::select_requests(config, &self.targets)
}
/// The apply without an operation of its own, for a caller that already
/// opened one.
pub(crate) async fn run_inner(self) -> Result<bool> {
let config = Config::get().await?;
let secrets = system::secrets::resolve(&config, self.prompt_secrets)?;
let (files, edits) = self.requests(&config)?;
if files.is_empty() && edits.is_empty() {
super::warn_if_dotfiles_ignored();
info!("no dotfiles configured in [dotfiles]");
return Ok(true);
}
// resolved from the trusted layers before anything is written, so
// nothing this apply writes can change which commands run afterwards
let reload = system::history::config::reload_commands()?;
let mut written = vec![];
let result = self.write(&config, &files, &edits, &secrets, &mut written);
// a dry run writes nothing, so nothing is reloaded. A declined edit
// prompt or a failed later entry still leaves what was written before
// it, so its applications are reloaded before the error is reported
if !self.dry_run && !written.is_empty() {
let touched = written
.iter()
.map(|path| system::history::replay::reload_path(path))
.collect::<Vec<_>>();
system::history::replay::run_reload(&reload, &touched);
}
result
}
/// Apply the whole-file entries, then the edits, appending each written
/// target to `written` as it goes. Returns `false` when a prompt was
/// declined.
fn write(
&self,
config: &Config,
files: &[system::files::FileRequest],
edits: &[system::edits::EditRequest],
secrets: &system::secrets::SecretValues,
written: &mut Vec<std::path::PathBuf>,
) -> Result<bool> {
if !files.is_empty() {
let opts = system::files::ApplyOpts {
dry_run: self.dry_run,
verbose: Settings::get().verbose,
force: self.force,
force_hint: "use --force",
yes: self.yes,
};
if !system::files::apply(config, files, &opts, secrets, written)? {
return Ok(false);
}
}
if !edits.is_empty() {
let opts = system::edits::ApplyOpts {
part: "dotfiles",
dry_run: self.dry_run,
verbose: Settings::get().verbose,
yes: self.yes,
};
if !system::edits::apply(config, edits, &opts, written)? {
return Ok(false);
}
}
Ok(true)
}
}