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
mod apply;
mod detect;
mod lifecycle;
pub(crate) mod monorepo;
mod plan;
mod stable;
use std::io::IsTerminal;
use crate::app::OutputFormat;
use crate::{git, ui};
/// Options for the bump subcommand.
pub struct BumpOptions {
/// Print the plan without writing anything.
pub dry_run: bool,
/// Bump as pre-release (e.g. `2.0.0-rc.1`).
pub prerelease: Option<String>,
/// Force a specific version, skip calculation.
pub release_as: Option<String>,
/// Use current version for initial changelog (no bump).
pub first_release: bool,
/// Skip tag creation.
pub no_tag: bool,
/// Skip commit and tag (update files only).
pub no_commit: bool,
/// Skip changelog generation.
pub skip_changelog: bool,
/// GPG-sign the commit and tag.
pub sign: bool,
/// Allow breaking changes in patch-only scheme.
pub force: bool,
/// Create a stable branch for patch-only releases.
///
/// `None` = flag not used, `Some(None)` = `--stable` without value
/// (auto-generate branch name), `Some(Some(name))` = custom branch name.
pub stable: Option<Option<String>>,
/// Use minor bump instead of major when advancing main after `--stable`.
pub minor: bool,
/// Output format (text or json).
pub format: OutputFormat,
/// Filter bump to specific package(s) (monorepo only).
pub packages: Vec<String>,
/// Push commit and tags to the given remote after tagging.
///
/// `None` = flag not used, `Some(name)` = push to the named remote.
pub push: Option<String>,
/// Skip the branch confirmation prompt (`--yes` / `-y` / `GIT_STD_YES=1`).
pub yes: bool,
}
/// Context passed from the version-computation phase to the shared finalize logic.
pub(super) struct FinalizeContext<'a> {
/// The new version string (semver or calver).
pub(super) new_version: String,
/// The previous version string, if any (used for changelog compare links).
pub(super) prev_version: Option<&'a str>,
/// Raw commits since the last tag, used for changelog generation.
pub(super) raw_commits: &'a [(String, String)],
}
/// Run the bump subcommand. Returns the exit code.
pub fn run(config: &crate::config::ProjectConfig, opts: &BumpOptions) -> i32 {
// Branch guard: warn and prompt when bumping on a non-release branch.
// Skipped for --dry-run (read-only), --yes, and GIT_STD_YES=1.
if !opts.dry_run && !opts.yes {
let env_yes = std::env::var("GIT_STD_YES")
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
.unwrap_or(false);
if !env_yes {
let cwd = std::env::current_dir().unwrap_or_default();
if let Ok(branch) = git::current_branch(&cwd) {
// Detached HEAD → skip the check.
if branch != "HEAD" {
let release = config.release_branch.as_deref().unwrap_or("main");
let on_release = branch == release
|| (config.release_branch.is_none() && branch == "master");
if !on_release {
ui::warning(&format!("you are on branch '{branch}', not '{release}'"));
ui::warning(
"bumping here will create a version commit and tag on this branch",
);
if !std::io::stdin().is_terminal() {
ui::hint("use --yes / -y to bypass, or set GIT_STD_YES=1");
return 1;
}
match inquire::Confirm::new("Continue?")
.with_default(false)
.prompt()
{
Ok(true) => {}
_ => {
ui::error("bump cancelled");
return 1;
}
}
}
}
}
}
}
// pre-bump gate: runs before version detection, non-zero exit aborts bump.
// Skipped for --dry-run.
if !opts.dry_run
&& let Err(code) = lifecycle::run_lifecycle_hook("pre-bump", &[])
{
return code;
}
if opts.stable.is_some() {
return stable::run_stable(config, opts);
}
if config.monorepo {
return monorepo::plan_monorepo_bump(config, opts, &opts.packages);
}
plan::dispatch(config, opts)
}