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