From c25d593aabd2684dfeb1e82857aea084fe847e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Tue, 16 Sep 2025 15:31:41 +0200 Subject: [PATCH] add cl status, improve cl pack --- "changelog/entries/Add \"cl status\".md" | 3 + src/action_pack.rs | 106 ++++++++++++++--------- src/action_status.rs | 14 +++ src/main.rs | 26 ++++-- src/store.rs | 28 ++++-- 5 files changed, 121 insertions(+), 56 deletions(-) create mode 100644 "changelog/entries/Add \"cl status\".md" create mode 100644 src/action_status.rs diff --git "a/changelog/entries/Add \"cl status\".md" "b/changelog/entries/Add \"cl status\".md" new file mode 100644 index 0000000..286be14 --- /dev/null +++ "b/changelog/entries/Add \"cl status\".md" @@ -0,0 +1,3 @@ +# New features +- Add command line option `cl status` (#SW-4716) +- Change `cl pack` to show a preview of the rendered changelog before asking for version. diff --git a/src/action_pack.rs b/src/action_pack.rs index 3c6ffad..53af397 100644 --- a/src/action_pack.rs +++ b/src/action_pack.rs @@ -1,21 +1,56 @@ use crate::AppContext; use crate::config::ChannelName; -use crate::git::get_branch_name; +use crate::git::{BranchName, get_branch_name}; use crate::store::{Release, Store}; use anyhow::bail; use colored::Colorize; -/// Perform the action of packing changelog entries for a release -pub(crate) fn cl_pack(ctx: AppContext, channel: Option) -> anyhow::Result<()> { - let mut store = Store::new(&ctx, false)?; - let branch = get_branch_name(&ctx); +pub fn pack_resolve_and_show_preview( + ctx: &AppContext, + user_chosen_channel: Option, + branch: Option<&BranchName>, +) -> anyhow::Result> { + let channel = resolve_channel(&ctx, user_chosen_channel, branch)?; + let store = Store::new(&ctx, false)?; + + let unreleased = store.find_unreleased_changes(&channel)?; + + if unreleased.is_empty() { + eprintln!("No unreleased changes."); + return Ok(None); + } + + println!(); + println!("Changes waiting for release:"); + for entry in &unreleased { + println!("+ {}", entry.cyan()); + } + println!(); - let (channel_detected, channel_explicit) = match channel { + let release = Release { + version: "Unreleased".to_string(), + entries: unreleased, + }; + + let rendered = store.render_release(&release)?; + + println!("\nPreview:\n\n{}", rendered); + + Ok(Some((release, channel))) +} + +/// Resolve channel from current branch or other context info, ask if needed +fn resolve_channel( + ctx: &AppContext, + user_chosen_channel: Option, + branch: Option<&BranchName>, +) -> anyhow::Result { + let (channel_detected, channel_explicit) = match user_chosen_channel { Some(ch) => (Some(ch), true), // passed via flag already None => ( branch .as_ref() - .map(|b| b.parse_channel(&ctx)) + .map(|b| b.parse_channel(ctx)) .transpose()? .flatten(), false, @@ -28,22 +63,6 @@ pub(crate) fn cl_pack(ctx: AppContext, channel: Option) -> anyhow:: bail!("No such channel: {ch}"); } - // If the branch is named rel/3.40, this can extract 3.40. - // TODO try to get something better from git! - let version_base = branch - .as_ref() - .map(|b| b.parse_version(&ctx)) - .transpose()? - .flatten(); - - // TODO detect version from git query? - - // TODO remove this - eprintln!( - "Branch name: {:?}, channel: {:?}, version: {:?}", - branch, channel_detected, version_base - ); - // Ask for the channel let channel = if ctx.config.channels.len() > 1 { if channel_explicit { @@ -66,19 +85,31 @@ pub(crate) fn cl_pack(ctx: AppContext, channel: Option) -> anyhow:: }; println!("Channel: {}", channel.green().bold()); - let unreleased = store.find_unreleased_changes(&channel)?; + Ok(channel) +} - if unreleased.is_empty() { - eprintln!("No unreleased changes."); +/// Perform the action of packing changelog entries for a release +pub(crate) fn cl_pack( + ctx: AppContext, + user_chosen_channel: Option, +) -> anyhow::Result<()> { + let branch = get_branch_name(&ctx); + let Some((mut release, channel)) = + pack_resolve_and_show_preview(&ctx, user_chosen_channel, branch.as_ref())? + else { + // No changes return Ok(()); - } + }; - println!(); - println!("Changes waiting for release:"); - for entry in &unreleased { - println!("+ {}", entry.cyan()); - } - println!(); + let mut store = Store::new(&ctx, false)?; + + // If the branch is named rel/3.40, this can extract 3.40. + // TODO try to get something better from git! + let version_base = branch + .as_ref() + .map(|b| b.parse_version(&ctx)) + .transpose()? + .flatten(); // Ask for the version let mut version = version_base.unwrap_or_default(); @@ -99,14 +130,7 @@ pub(crate) fn cl_pack(ctx: AppContext, channel: Option) -> anyhow:: } } - let release = Release { - version, - entries: unreleased, - }; - - let rendered = store.render_release(&release)?; - - println!("\n\nPreview:\n\n{}\n", rendered); + release.version = version; if !inquire::Confirm::new("Continue - write to changelog file?") .with_default(true) diff --git a/src/action_status.rs b/src/action_status.rs new file mode 100644 index 0000000..c372670 --- /dev/null +++ b/src/action_status.rs @@ -0,0 +1,14 @@ +use crate::AppContext; +use crate::action_pack::pack_resolve_and_show_preview; +use crate::config::ChannelName; +use crate::git::{get_branch_name}; + +/// Perform the action of packing changelog entries for a release +pub(crate) fn cl_status( + ctx: AppContext, + user_chosen_channel: Option, +) -> anyhow::Result<()> { + let branch = get_branch_name(&ctx); + pack_resolve_and_show_preview(&ctx, user_chosen_channel, branch.as_ref())?; + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 49a0caf..b13aa8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ -use crate::action_init::{cl_init, ClInit}; +use crate::action_init::{ClInit, cl_init}; use crate::action_log::cl_log; use crate::action_pack::cl_pack; +use crate::action_status::cl_status; use crate::config::{ChannelName, Config}; use anyhow::bail; use clap::builder::NonEmptyStringValueParser; @@ -17,6 +18,8 @@ mod action_pack; mod action_init; +mod action_status; + mod store; mod utils; @@ -47,6 +50,12 @@ fn main_try() -> anyhow::Result<()> { .flatten() .unwrap_or_else(|| "cl".to_string()); + let optional_channel_arg = clap::Arg::new("CHANNEL") + .short('x') + .long("channel") + .value_parser(NonEmptyStringValueParser::new()) + .required(false); + let args = clap::Command::new(&binary_name) .version(env!("CARGO_PKG_VERSION")) .author(env!("CARGO_PKG_AUTHORS")) @@ -57,11 +66,12 @@ fn main_try() -> anyhow::Result<()> { clap::Command::new("pack") .visible_alias("release") .about("Pack changelog entries to a changelog section") - .arg(clap::Arg::new("CHANNEL") - .short('x') - .long("channel") - .value_parser(NonEmptyStringValueParser::new()) - .required(false)), + .arg(optional_channel_arg.clone()), + ) + .subcommand( + clap::Command::new("status") + .about("Show outstanding change entries on the current channel (or specified channel)") + .arg(optional_channel_arg), ) .subcommand(clap::Command::new("add") .visible_alias("log") @@ -133,6 +143,10 @@ fn main_try() -> anyhow::Result<()> { let channel: Option = subargs.get_one("CHANNEL").cloned(); cl_pack(ctx, channel)?; } + Some(("status", subargs)) => { + let channel: Option = subargs.get_one("CHANNEL").cloned(); + cl_status(ctx, channel)?; + } None | Some(("add", _)) => cl_log(ctx)?, // TODO: status, flush Some((other, _)) => { diff --git a/src/store.rs b/src/store.rs index ba747a1..20105bf 100644 --- a/src/store.rs +++ b/src/store.rs @@ -382,22 +382,32 @@ struct ChannelReleaseStore { impl ChannelReleaseStore { /// Load from a versions file fn load(releases_file: PathBuf, channel_name: ChannelName) -> anyhow::Result { - println!( - "Loading versions for channel {} from: {}", - channel_name, - releases_file.display() - ); let releases = if !releases_file.exists() { // File did not exist yet, create it - this catches error with write access early let mut f = OpenOptions::new() .write(true) .create(true) - .open(&releases_file)?; - f.write_all("[]".as_bytes())?; + .open(&releases_file) + .with_context(|| { + format!("Failed to open channel file: {}", releases_file.display()) + })?; + f.write_all("[]".as_bytes()).with_context(|| { + format!( + "Failed to write into channel file: {}", + releases_file.display() + ) + })?; Default::default() } else { - let channel_json = read_to_string(&releases_file)?; - serde_json::from_str::(&channel_json)? + let channel_json = read_to_string(&releases_file).with_context(|| { + format!("Failed to read channel file: {}", releases_file.display()) + })?; + serde_json::from_str::(&channel_json).with_context(|| { + format!( + "Failed to parse content of channel file: {}", + releases_file.display() + ) + })? }; Ok(Self {