cargo_release/steps/
owner.rs

1use crate::error::CliError;
2use crate::ops::git;
3use crate::steps::plan;
4
5/// Ensure owners are set on specified packages
6#[derive(Debug, Clone, clap::Args)]
7pub struct OwnerStep {
8    #[command(flatten)]
9    manifest: clap_cargo::Manifest,
10
11    #[command(flatten)]
12    workspace: clap_cargo::Workspace,
13
14    /// Custom config file
15    #[arg(short, long = "config", value_name = "PATH")]
16    custom_config: Option<std::path::PathBuf>,
17
18    /// Ignore implicit configuration files.
19    #[arg(long)]
20    isolated: bool,
21
22    /// Unstable options
23    #[arg(short = 'Z', value_name = "FEATURE")]
24    z: Vec<crate::config::UnstableValues>,
25
26    /// Comma-separated globs of branch names a release can happen from
27    #[arg(long, value_delimiter = ',')]
28    allow_branch: Option<Vec<String>>,
29
30    /// Actually perform a release. Dry-run mode is the default
31    #[arg(short = 'x', long)]
32    execute: bool,
33
34    #[arg(short = 'n', long, conflicts_with = "execute", hide = true)]
35    dry_run: bool,
36
37    /// Skip release confirmation and version preview
38    #[arg(long)]
39    no_confirm: bool,
40}
41
42impl OwnerStep {
43    pub fn run(&self) -> Result<(), CliError> {
44        git::git_version()?;
45
46        if self.dry_run {
47            let _ =
48                crate::ops::shell::warn("`--dry-run` is superfluous, dry-run is done by default");
49        }
50
51        let ws_meta = self
52            .manifest
53            .metadata()
54            // When evaluating dependency ordering, we need to consider optional dependencies
55            .features(cargo_metadata::CargoOpt::AllFeatures)
56            .exec()?;
57        let config = self.to_config();
58        let ws_config = crate::config::load_workspace_config(&config, &ws_meta)?;
59        let mut pkgs = plan::load(&config, &ws_meta)?;
60
61        let (_selected_pkgs, excluded_pkgs) = self.workspace.partition_packages(&ws_meta);
62        for excluded_pkg in excluded_pkgs {
63            let Some(pkg) = pkgs.get_mut(&excluded_pkg.id) else {
64                // Either not in workspace or marked as `release = false`.
65                continue;
66            };
67            if !pkg.config.release() {
68                continue;
69            }
70
71            pkg.config.publish = Some(false);
72            pkg.config.owners = Some(vec![]);
73            pkg.config.release = Some(false);
74
75            let crate_name = pkg.meta.name.as_str();
76            log::debug!("disabled by user, skipping {crate_name}",);
77        }
78
79        let mut pkgs = plan::plan(pkgs)?;
80
81        for pkg in pkgs.values_mut() {
82            if pkg.config.owners().is_empty() {
83                log::debug!("disabled due to no owners, skipping {}", pkg.meta.name);
84                pkg.config.publish = Some(false);
85                pkg.config.owners = Some(vec![]);
86                pkg.config.release = Some(false);
87            } else if !pkg.config.publish() {
88                log::debug!("disabled due to publish=false, skipping {}", pkg.meta.name);
89                pkg.config.publish = Some(false);
90                pkg.config.owners = Some(vec![]);
91                pkg.config.release = Some(false);
92            }
93        }
94
95        let (selected_pkgs, _excluded_pkgs): (Vec<_>, Vec<_>) = pkgs
96            .into_iter()
97            .map(|(_, pkg)| pkg)
98            .partition(|p| p.config.release());
99        if selected_pkgs.is_empty() {
100            let _ = crate::ops::shell::error("no packages selected");
101            return Err(2.into());
102        }
103
104        let dry_run = !self.execute;
105        let mut failed = false;
106
107        // STEP 0: Help the user make the right decisions.
108        failed |= !super::verify_git_is_clean(
109            ws_meta.workspace_root.as_std_path(),
110            dry_run,
111            log::Level::Error,
112        )?;
113
114        failed |= !super::verify_git_branch(
115            ws_meta.workspace_root.as_std_path(),
116            &ws_config,
117            dry_run,
118            log::Level::Error,
119        )?;
120
121        failed |= !super::verify_if_behind(
122            ws_meta.workspace_root.as_std_path(),
123            &ws_config,
124            dry_run,
125            log::Level::Warn,
126        )?;
127
128        // STEP 1: Release Confirmation
129        super::confirm("Owner", &selected_pkgs, self.no_confirm, dry_run)?;
130
131        ensure_owners(&selected_pkgs, dry_run)?;
132
133        super::finish(failed, dry_run)
134    }
135
136    fn to_config(&self) -> crate::config::ConfigArgs {
137        crate::config::ConfigArgs {
138            custom_config: self.custom_config.clone(),
139            isolated: self.isolated,
140            z: self.z.clone(),
141            allow_branch: self.allow_branch.clone(),
142            ..Default::default()
143        }
144    }
145}
146
147pub fn ensure_owners(pkgs: &[plan::PackageRelease], dry_run: bool) -> Result<(), CliError> {
148    for pkg in pkgs {
149        if !pkg.config.publish() || !pkg.ensure_owners {
150            continue;
151        }
152
153        let crate_name = pkg.meta.name.as_str();
154        crate::ops::cargo::ensure_owners(
155            crate_name,
156            pkg.config.owners(),
157            pkg.config.registry(),
158            dry_run,
159        )?;
160    }
161
162    Ok(())
163}