cargo_release/steps/
tag.rs1use std::collections::HashSet;
2
3use crate::error::CliError;
4use crate::ops::git;
5use crate::ops::replace::NOW;
6use crate::ops::replace::Template;
7use crate::steps::plan;
8
9#[derive(Debug, Clone, clap::Args)]
13pub struct TagStep {
14 #[command(flatten)]
15 manifest: clap_cargo::Manifest,
16
17 #[command(flatten)]
18 workspace: clap_cargo::Workspace,
19
20 #[arg(short, long = "config")]
22 custom_config: Option<std::path::PathBuf>,
23
24 #[arg(long)]
26 isolated: bool,
27
28 #[arg(short, value_name = "FEATURE")]
30 z: Vec<crate::config::UnstableValues>,
31
32 #[arg(long, value_delimiter = ',')]
34 allow_branch: Option<Vec<String>>,
35
36 #[arg(short = 'x', long)]
38 execute: bool,
39
40 #[arg(short = 'n', long, conflicts_with = "execute", hide = true)]
41 dry_run: bool,
42
43 #[arg(long)]
45 no_confirm: bool,
46
47 #[command(flatten)]
48 tag: crate::config::TagArgs,
49}
50
51impl TagStep {
52 pub fn run(&self) -> Result<(), CliError> {
53 git::git_version()?;
54
55 if self.dry_run {
56 let _ =
57 crate::ops::shell::warn("`--dry-run` is superfluous, dry-run is done by default");
58 }
59
60 let ws_meta = self
61 .manifest
62 .metadata()
63 .features(cargo_metadata::CargoOpt::AllFeatures)
65 .exec()?;
66 let config = self.to_config();
67 let ws_config = crate::config::load_workspace_config(&config, &ws_meta)?;
68 let mut pkgs = plan::load(&config, &ws_meta)?;
69
70 let (_selected_pkgs, excluded_pkgs) = self.workspace.partition_packages(&ws_meta);
71 for excluded_pkg in excluded_pkgs {
72 let Some(pkg) = pkgs.get_mut(&excluded_pkg.id) else {
73 continue;
75 };
76 if !pkg.config.release() {
77 continue;
78 }
79
80 pkg.planned_tag = None;
81 pkg.config.tag = Some(false);
82 pkg.config.release = Some(false);
83
84 let crate_name = pkg.meta.name.as_str();
85 log::debug!("disabled by user, skipping {crate_name}",);
86 }
87
88 let mut pkgs = plan::plan(pkgs)?;
89
90 for pkg in pkgs.values_mut() {
91 if let Some(tag_name) = pkg.planned_tag.as_ref()
92 && git::tag_exists(ws_meta.workspace_root.as_std_path(), tag_name)?
93 {
94 let crate_name = pkg.meta.name.as_str();
95 let _ = crate::ops::shell::warn(format!(
96 "disabled due to existing tag ({tag_name}), skipping {crate_name}"
97 ));
98 pkg.planned_tag = None;
99 pkg.config.tag = Some(false);
100 pkg.config.release = Some(false);
101 }
102 }
103
104 let (selected_pkgs, _excluded_pkgs): (Vec<_>, Vec<_>) = pkgs
105 .into_iter()
106 .map(|(_, pkg)| pkg)
107 .partition(|p| p.config.release());
108 if selected_pkgs.is_empty() {
109 let _ = crate::ops::shell::error("no packages selected");
110 return Err(2.into());
111 }
112
113 let dry_run = !self.execute;
114 let mut failed = false;
115
116 failed |= !super::verify_git_is_clean(
118 ws_meta.workspace_root.as_std_path(),
119 dry_run,
120 log::Level::Error,
121 )?;
122
123 failed |= !super::verify_git_branch(
124 ws_meta.workspace_root.as_std_path(),
125 &ws_config,
126 dry_run,
127 log::Level::Error,
128 )?;
129
130 failed |= !super::verify_if_behind(
131 ws_meta.workspace_root.as_std_path(),
132 &ws_config,
133 dry_run,
134 log::Level::Warn,
135 )?;
136
137 super::confirm("Tag", &selected_pkgs, self.no_confirm, dry_run)?;
139
140 tag(&selected_pkgs, dry_run)?;
142
143 super::finish(failed, dry_run)
144 }
145
146 fn to_config(&self) -> crate::config::ConfigArgs {
147 crate::config::ConfigArgs {
148 custom_config: self.custom_config.clone(),
149 isolated: self.isolated,
150 z: self.z.clone(),
151 allow_branch: self.allow_branch.clone(),
152 tag: self.tag.clone(),
153 ..Default::default()
154 }
155 }
156}
157
158pub fn tag(pkgs: &[plan::PackageRelease], dry_run: bool) -> Result<(), CliError> {
159 let mut seen_tags = HashSet::new();
160 for pkg in pkgs {
161 if let Some(tag_name) = pkg.planned_tag.as_ref()
162 && seen_tags.insert(tag_name)
163 {
164 let cwd = &pkg.package_root;
165 let crate_name = pkg.meta.name.as_str();
166
167 let version = pkg.planned_version.as_ref().unwrap_or(&pkg.initial_version);
168 let prev_version_var = pkg.initial_version.bare_version_string.as_str();
169 let prev_metadata_var = pkg.initial_version.full_version.build.as_str();
170 let version_var = version.bare_version_string.as_str();
171 let metadata_var = version.full_version.build.as_str();
172 let template = Template {
173 prev_version: Some(prev_version_var),
174 prev_metadata: Some(prev_metadata_var),
175 version: Some(version_var),
176 metadata: Some(metadata_var),
177 crate_name: Some(crate_name),
178 tag_name: Some(tag_name),
179 date: Some(NOW.as_str()),
180 ..Default::default()
181 };
182 let tag_message = template.render(pkg.config.tag_message());
183
184 log::debug!("creating git tag {tag_name}");
185 if !git::tag(cwd, tag_name, &tag_message, pkg.config.sign_tag(), dry_run)? {
186 return Err(101.into());
188 }
189 }
190 }
191
192 Ok(())
193}