From a3c4b9d341860f38d34ec179a1b5c729da1f36a0 Mon Sep 17 00:00:00 2001 From: "Flavien H." Date: Wed, 27 May 2026 14:58:39 -0400 Subject: [PATCH 1/2] feat(config): support configurable tag version prefix --- src/config.rs | 11 +++++++++-- src/mono.rs | 17 +++++++++-------- src/output.rs | 7 +++++-- src/template.rs | 1 + 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/config.rs b/src/config.rs index 8a48b25..e084474 100644 --- a/src/config.rs +++ b/src/config.rs @@ -355,6 +355,7 @@ pub struct Project { labels: Vec, tag_prefix: Option, tag_prefix_separator: Option, + tag_version_prefix: Option, #[serde(default)] subs: Option, #[serde(default)] @@ -400,6 +401,7 @@ impl Project { pub fn tag_prefix(&self) -> &Option { &self.tag_prefix } pub fn tag_prefix_separator(&self) -> &str { self.tag_prefix_separator.as_deref().unwrap_or("-") } + pub fn tag_version_prefix(&self) -> &str { self.tag_version_prefix.as_deref().unwrap_or("v") } pub fn tag_majors(&self) -> Option<&[u32]> { self.version.tag_majors() } pub async fn write_changelog( @@ -523,12 +525,13 @@ impl Project { pub fn full_version(&self, vers: &str) -> Option { let tag_prefix_separator = self.tag_prefix_separator(); + let tag_version_prefix = self.tag_version_prefix(); self.tag_prefix.as_ref().map(|tag_prefix| { if tag_prefix.is_empty() { - format!("v{}", vers) + format!("{}{}", tag_version_prefix, vers) } else { - format!("{}{}v{}", tag_prefix, tag_prefix_separator, vers) + format!("{}{}{}{}", tag_prefix, tag_prefix_separator, tag_version_prefix, vers) } }) } @@ -560,6 +563,7 @@ impl Project { labels: Default::default(), tag_prefix: self.tag_prefix.clone(), tag_prefix_separator: self.tag_prefix_separator.clone(), + tag_version_prefix: self.tag_version_prefix.clone(), subs: None, hooks: self.hooks.clone() }))) @@ -1711,6 +1715,7 @@ sizes: also: Vec::new(), tag_prefix: None, tag_prefix_separator: None, + tag_version_prefix: None, labels: Default::default(), hooks: Default::default(), subs: None @@ -1738,6 +1743,7 @@ sizes: also: Vec::new(), tag_prefix: None, tag_prefix_separator: None, + tag_version_prefix: None, labels: Default::default(), hooks: Default::default(), subs: None @@ -1764,6 +1770,7 @@ sizes: also: Vec::new(), tag_prefix: None, tag_prefix_separator: None, + tag_version_prefix: None, labels: Default::default(), hooks: Default::default(), subs: None diff --git a/src/mono.rs b/src/mono.rs index 2b5315f..4d55ec2 100644 --- a/src/mono.rs +++ b/src/mono.rs @@ -689,8 +689,8 @@ fn find_old_tags<'s, I: Iterator>(projects: I, prev_tag: &st trace!("Found proj {} tag {} at {}.", proj.id(), tag, oid); let by_id = by_proj_oid .entry(proj.id().clone()) - .or_insert_with(|| (proj.tag_prefix_separator().to_string(), HashMap::new())); - by_id.1.entry(oid).or_insert_with(Vec::new).push(tag.to_string()); + .or_insert_with(|| (proj.tag_prefix_separator().to_string(), proj.tag_version_prefix().to_string(), HashMap::new())); + by_id.2.entry(oid).or_insert_with(Vec::new).push(tag.to_string()); } } } @@ -698,9 +698,9 @@ fn find_old_tags<'s, I: Iterator>(projects: I, prev_tag: &st let mut current = HashMap::new(); for commit_oid in repo.commits_to_head(FromTag::new(prev_tag, true), false)?.map(|c| c.map(|c| c.id())) { let commit_oid = commit_oid?; - by_proj_oid.retain(|proj_id, (sep, by_id)| { + by_proj_oid.retain(|proj_id, (sep, tag_version_prefix, by_id)| { if let Some(tags) = by_id.remove(&commit_oid) { - let mut versions = tags_to_versions(sep, &tags); + let mut versions = tags_to_versions(sep, tag_version_prefix, &tags); versions.sort_unstable_by(version_sort); current.insert(proj_id.clone(), versions[0].clone()); false @@ -749,11 +749,12 @@ fn fill_from_prev( /// usable by both `Repository::tag_names` and as a git fetch refspec `refs/tags/{pattern}`. fn tag_fnmatches(proj: &Project) -> impl Iterator + '_ { let majors = proj.tag_majors(); + let tag_version_prefix = proj.tag_version_prefix(); let majors_v = if let Some(majors) = majors { - E2::A(majors.iter().map(|major| format!("v{}.*", major))) + E2::A(majors.iter().map(move |major| format!("{}{}.*", tag_version_prefix, major))) } else { - E2::B(once("v*".to_string())) + E2::B(once(format!("{}*", tag_version_prefix))) }; let sep = proj.tag_prefix_separator(); @@ -765,12 +766,12 @@ fn tag_fnmatches(proj: &Project) -> impl Iterator + '_ { } } -fn tags_to_versions(prefix_sep: &str, tags: &[String]) -> Vec { +fn tags_to_versions(prefix_sep: &str, tag_version_prefix: &str, tags: &[String]) -> Vec { tags .iter() .map(|tag| { let v = tag.rfind(prefix_sep).map(|d| d + 1).unwrap_or(0); - tag[v + 1 ..].to_string() + tag[v + tag_version_prefix.len() ..].to_string() }) .filter(|v| Size::parts(v).is_ok()) .collect() diff --git a/src/output.rs b/src/output.rs index f2aaab9..7f7f227 100644 --- a/src/output.rs +++ b/src/output.rs @@ -142,6 +142,7 @@ pub struct ProjLine { pub name: String, pub tag_prefix: Option, pub tag_prefix_separator: String, + pub tag_version_prefix: String, pub version: String, pub full_version: Option, pub root: Option @@ -154,9 +155,10 @@ impl ProjLine { let version = p.get_value(read)?; let tag_prefix = p.tag_prefix().clone(); let tag_prefix_separator = p.tag_prefix_separator().to_string(); + let tag_version_prefix = p.tag_version_prefix().to_string(); let full_version = p.full_version(&version); let root = p.root().cloned(); - Ok(ProjLine { id: id.clone(), name, tag_prefix, tag_prefix_separator, version, full_version, root }) + Ok(ProjLine { id: id.clone(), name, tag_prefix, tag_prefix_separator, tag_version_prefix, version, full_version, root }) } pub fn from_version(p: &Project, vers: String) -> Result { @@ -165,9 +167,10 @@ impl ProjLine { let version = vers; let tag_prefix = p.tag_prefix().clone(); let tag_prefix_separator = p.tag_prefix_separator().to_string(); + let tag_version_prefix = p.tag_version_prefix().to_string(); let full_version = p.full_version(&version); let root = p.root().cloned(); - Ok(ProjLine { id: id.clone(), name, tag_prefix, tag_prefix_separator, version, full_version, root }) + Ok(ProjLine { id: id.clone(), name, tag_prefix, tag_prefix_separator, tag_version_prefix, version, full_version, root }) } } diff --git a/src/template.rs b/src/template.rs index 43e4d9e..3242bbf 100644 --- a/src/template.rs +++ b/src/template.rs @@ -97,6 +97,7 @@ pub fn construct_changelog_html( "name": proj.name, "tag_prefix": proj.tag_prefix.unwrap_or_default(), "tag_prefix_separator": proj.tag_prefix_separator, + "tag_version_prefix": proj.tag_version_prefix, "version": proj.version, "full_version": proj.full_version.unwrap_or_default(), "root": proj.root.unwrap_or_default(), From adaf04273ade1125b1d41d7038ba040b4770c14f Mon Sep 17 00:00:00 2001 From: "Flavien H." Date: Wed, 27 May 2026 15:22:13 -0400 Subject: [PATCH 2/2] docs: document the new configurable tag_version_prefix option --- docs/changelog.md | 4 +++- docs/reference.md | 4 ++++ docs/version_tags.md | 13 +++++++------ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 240d880..2fc1a1b 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -140,10 +140,12 @@ format. The following variables are available to the template: - `tag_prefix`: The tag-prefix of the project (if it exists). - `tag_prefix_separator`: The tag-prefix separator of the project (defaults to "-") + - `tag_version_prefix`: The tag version prefix of the project + (defaults to "v") - `version`: The current version of the project (which should match `release.version`). - `full_version`: The full version name of the project. The version - number is preceded by the letter `v`. If there is a `tag_prefix`, + number is preceded by `tag_version_prefix`. If there is a `tag_prefix`, it is prepended and separated from the version number with `tag_prefix_separator`. - `root`: The directory root of the project, (relative to the diff --git a/docs/reference.md b/docs/reference.md index 6e7fb23..57e0483 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -383,6 +383,10 @@ relative to the base of the repo; other paths are relative to that root separator used between the tag prefix and the version number when generating the full tag for this project. In the above example, the first project's version tags would look like `proj1/v1.2.3`. + - `tag_version_prefix`: (optional, defaults to "v") The + prefix preceding the version number itself (e.g. the `'v'` in `v1.2.3`). + Using the empty string `""` will completely omit the `'v'` prefix, + which is useful if you want plain version tags (e.g. `1.2.3`). - `subs`: If provided, allows a project to be subdivided into "major" versions, each in its own subdirectory. See [Major Subdirectories](./subs.md) for more info on this feature. diff --git a/docs/version_tags.md b/docs/version_tags.md index 1e2a908..299d371 100644 --- a/docs/version_tags.md +++ b/docs/version_tags.md @@ -15,15 +15,15 @@ version: ``` The `tag_prefix` property causes Versio to write out a new -"[tag\_prefix]-v*x.y.z*" tag for the project when the version number is +"[tag\_prefix]-[separator][tag\_version\_prefix]*x.y.z*" tag for the project when the version number is changed. The property is optional for most projects, but required for projects that use `version: tags`. The default value is used when no -existing "projname-v*x.y.z*" tags currently exist. You can use the -`tag_prefix_separator` property to use a separator other than `-`. +existing "projname-[tag\_version\_prefix]*x.y.z*" tags currently exist. You can use the +`tag_prefix_separator` property to use a separator other than `-`. You can also use the +`tag_version_prefix` property to configure or completely omit the `'v'` prefix preceding the version number (defaults to `"v"`). Since `tag_prefix` is also used to find older tags of a project, you -should not change it. If you change the `tag_prefix` or -`tag_prefix_separator`, you may need to manually re-tag your commit +should not change it. If you change the `tag_prefix`, `tag_prefix_separator` or `tag_version_prefix`, you may need to manually re-tag your commit history, or else Versio may be unable to locate past version numbers. If a project uses `version: tags:`, you may want to use the @@ -51,7 +51,8 @@ config file section of the [Reference](./reference.md). ## In Go projects If the tag prefix is *empty* (`tag_prefix: ""`), then tags for the -project take a non-prefixed form "v*a.b.c*", which is compatible with +project take a non-prefixed form "[tag\_version\_prefix]*a.b.c*", which is compatible with +most Go tools when left as the default `"v"`. Setting both `tag_prefix: ""` and `tag_version_prefix: ""` generates plain version tags of the form "*a.b.c*". most Go tools. Especially `go get` and `go mod`, which search for version tags in that form. If you do use a prefix, you'll need to reference your project with the fully-qualified tag: e.g. `go get