1use crate::error::CargoResult;
2use crate::error::CliError;
3use crate::ops::git;
4use crate::steps::plan;
5
6#[derive(Debug, Clone, clap::Args)]
8pub struct VersionStep {
9 #[command(flatten)]
10 manifest: clap_cargo::Manifest,
11
12 #[command(flatten)]
13 workspace: clap_cargo::Workspace,
14
15 #[arg(short, long = "config")]
17 custom_config: Option<std::path::PathBuf>,
18
19 #[arg(long)]
21 isolated: bool,
22
23 #[arg(short, value_name = "FEATURE")]
25 z: Vec<crate::config::UnstableValues>,
26
27 #[arg(long, value_delimiter = ',')]
29 allow_branch: Option<Vec<String>>,
30
31 #[arg(short = 'x', long)]
33 execute: bool,
34
35 #[arg(short = 'n', long, conflicts_with = "execute", hide = true)]
36 dry_run: bool,
37
38 #[arg(long)]
40 no_confirm: bool,
41
42 #[arg(value_name = "LEVEL|VERSION", help_heading = "Version")]
44 level_or_version: super::TargetVersion,
45
46 #[arg(short, long, help_heading = "Version")]
48 metadata: Option<String>,
49
50 #[arg(long, value_name = "NAME", help_heading = "Version")]
52 prev_tag_name: Option<String>,
53}
54
55impl VersionStep {
56 pub fn run(&self) -> Result<(), CliError> {
57 git::git_version()?;
58
59 if self.dry_run {
60 let _ =
61 crate::ops::shell::warn("`--dry-run` is superfluous, dry-run is done by default");
62 }
63
64 let ws_meta = self
65 .manifest
66 .metadata()
67 .features(cargo_metadata::CargoOpt::AllFeatures)
69 .exec()?;
70 let config = self.to_config();
71 let ws_config = crate::config::load_workspace_config(&config, &ws_meta)?;
72 let mut pkgs = plan::load(&config, &ws_meta)?;
73
74 for pkg in pkgs.values_mut() {
75 if let Some(prev_tag) = self.prev_tag_name.as_ref() {
76 pkg.set_prior_tag(prev_tag.to_owned());
79 }
80 if pkg.config.release() {
81 pkg.bump(&self.level_or_version, self.metadata.as_deref())?;
82 }
83 }
84
85 let (_selected_pkgs, excluded_pkgs) = self.workspace.partition_packages(&ws_meta);
86 for excluded_pkg in excluded_pkgs {
87 let Some(pkg) = pkgs.get_mut(&excluded_pkg.id) else {
88 continue;
90 };
91 if !pkg.config.release() {
92 continue;
93 }
94
95 pkg.planned_version = None;
96 pkg.config.release = Some(false);
97 }
98
99 let pkgs = plan::plan(pkgs)?;
100
101 let (selected_pkgs, excluded_pkgs): (Vec<_>, Vec<_>) = pkgs
102 .into_iter()
103 .map(|(_, pkg)| pkg)
104 .partition(|p| p.config.release());
105 if selected_pkgs.is_empty() {
106 let _ = crate::ops::shell::error("no packages selected");
107 return Err(2.into());
108 }
109
110 let dry_run = !self.execute;
111 let mut failed = false;
112
113 failed |= !super::verify_git_is_clean(
115 ws_meta.workspace_root.as_std_path(),
116 dry_run,
117 log::Level::Warn,
118 )?;
119
120 failed |=
121 !super::verify_monotonically_increasing(&selected_pkgs, dry_run, log::Level::Error)?;
122
123 super::warn_changed(&ws_meta, &selected_pkgs)?;
124
125 failed |= !super::verify_git_branch(
126 ws_meta.workspace_root.as_std_path(),
127 &ws_config,
128 dry_run,
129 log::Level::Warn,
130 )?;
131
132 failed |= !super::verify_if_behind(
133 ws_meta.workspace_root.as_std_path(),
134 &ws_config,
135 dry_run,
136 log::Level::Warn,
137 )?;
138
139 super::confirm("Bump", &selected_pkgs, self.no_confirm, dry_run)?;
141
142 let update_lock = update_versions(&ws_meta, &selected_pkgs, &excluded_pkgs, dry_run)?;
144 if update_lock {
145 log::debug!("Updating lock file");
146 if !dry_run {
147 let workspace_path = ws_meta.workspace_root.as_std_path().join("Cargo.toml");
148 crate::ops::cargo::update_lock(&workspace_path)?;
149 }
150 }
151
152 super::finish(failed, dry_run)
153 }
154
155 fn to_config(&self) -> crate::config::ConfigArgs {
156 crate::config::ConfigArgs {
157 custom_config: self.custom_config.clone(),
158 isolated: self.isolated,
159 z: self.z.clone(),
160 allow_branch: self.allow_branch.clone(),
161 ..Default::default()
162 }
163 }
164}
165
166pub fn changed_since(
167 ws_meta: &cargo_metadata::Metadata,
168 pkg: &plan::PackageRelease,
169 since_ref: &str,
170) -> Option<Vec<std::path::PathBuf>> {
171 let changed_root = if pkg.bin {
172 ws_meta.workspace_root.as_std_path()
173 } else {
174 &pkg.package_root
176 };
177 let changed = git::changed_files(changed_root, since_ref).ok().flatten()?;
178 let changed: Vec<_> = changed
179 .into_iter()
180 .filter(|p| pkg.package_content.contains(p))
181 .collect();
182
183 Some(changed)
184}
185
186pub fn update_versions(
187 ws_meta: &cargo_metadata::Metadata,
188 selected_pkgs: &[plan::PackageRelease],
189 excluded_pkgs: &[plan::PackageRelease],
190 dry_run: bool,
191) -> CargoResult<bool> {
192 let mut changed = false;
193
194 let workspace_version = selected_pkgs
195 .iter()
196 .filter(|p| p.config.shared_version() == Some(crate::config::SharedVersion::WORKSPACE))
197 .find_map(|p| p.planned_version.clone());
198
199 if let Some(workspace_version) = &workspace_version {
200 let _ = crate::ops::shell::status(
201 "Upgrading",
202 format!(
203 "workspace to version {}",
204 workspace_version.full_version_string
205 ),
206 );
207 let workspace_path = ws_meta.workspace_root.as_std_path().join("Cargo.toml");
208 crate::ops::cargo::set_workspace_version(
209 &workspace_path,
210 workspace_version.full_version_string.as_str(),
211 dry_run,
212 )?;
213 changed = true;
215 }
216
217 for (selected, pkg) in selected_pkgs
218 .iter()
219 .map(|s| (true, s))
220 .chain(excluded_pkgs.iter().map(|s| (false, s)))
221 {
222 let is_inherited =
223 pkg.config.shared_version() == Some(crate::config::SharedVersion::WORKSPACE);
224 let planned_version = if is_inherited {
225 workspace_version.as_ref()
226 } else if let Some(version) = pkg.planned_version.as_ref() {
227 assert!(selected);
228 Some(version)
229 } else {
230 None
231 };
232
233 if let Some(version) = planned_version {
234 if is_inherited {
235 let crate_name = pkg.meta.name.as_str();
236 let _ = crate::ops::shell::status(
237 "Upgrading",
238 format!(
239 "{} from {} to {} (inherited from workspace)",
240 crate_name,
241 pkg.initial_version.full_version_string,
242 version.full_version_string
243 ),
244 );
245 } else {
246 let crate_name = pkg.meta.name.as_str();
247 let _ = crate::ops::shell::status(
248 "Upgrading",
249 format!(
250 "{} from {} to {}",
251 crate_name,
252 pkg.initial_version.full_version_string,
253 version.full_version_string
254 ),
255 );
256 crate::ops::cargo::set_package_version(
257 &pkg.manifest_path,
258 version.full_version_string.as_str(),
259 dry_run,
260 )?;
261 }
262 update_dependent_versions(ws_meta, pkg, version, dry_run)?;
263 changed = true;
264 }
265 }
266
267 Ok(changed)
268}
269
270pub fn update_dependent_versions(
271 ws_meta: &cargo_metadata::Metadata,
272 pkg: &plan::PackageRelease,
273 version: &plan::Version,
274 dry_run: bool,
275) -> CargoResult<()> {
276 {
284 let workspace_path = ws_meta.workspace_root.as_std_path().join("Cargo.toml");
285 crate::ops::cargo::upgrade_dependency_req(
286 "workspace",
287 &workspace_path,
288 &pkg.package_root,
289 &pkg.meta.name,
290 &version.full_version,
291 pkg.config.dependent_version(),
292 dry_run,
293 )?;
294 }
295
296 for dep in find_ws_members(ws_meta) {
297 crate::ops::cargo::upgrade_dependency_req(
298 &dep.name,
299 dep.manifest_path.as_std_path(),
300 &pkg.package_root,
301 &pkg.meta.name,
302 &version.full_version,
303 pkg.config.dependent_version(),
304 dry_run,
305 )?;
306 }
307
308 Ok(())
309}
310
311fn find_ws_members(
312 ws_meta: &cargo_metadata::Metadata,
313) -> impl Iterator<Item = &cargo_metadata::Package> {
314 let workspace_members: std::collections::HashSet<_> =
315 ws_meta.workspace_members.iter().collect();
316 ws_meta
317 .packages
318 .iter()
319 .filter(move |p| workspace_members.contains(&p.id))
320}