mirror of
https://github.com/orhun/git-cliff.git
synced 2024-08-16 18:30:30 +03:00
Compare commits
3 Commits
89ca5fddbe
...
27e1129dea
Author | SHA1 | Date | |
---|---|---|---|
|
27e1129dea | ||
|
3eb828e69a | ||
|
3d78b729a4 |
37
.github/fixtures/test-bump-unreleased-with-tag-message-arg/cliff.toml
vendored
Normal file
37
.github/fixtures/test-bump-unreleased-with-tag-message-arg/cliff.toml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
[changelog]
|
||||
# template for the changelog footer
|
||||
header = """
|
||||
# Changelog\n
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://keats.github.io/tera/docs/#introduction
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}]
|
||||
{% if message %}
|
||||
{{ message }}
|
||||
{% endif %}\
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {{ commit.message | upper_first }}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# template for the changelog footer
|
||||
footer = """
|
||||
<!-- generated by git-cliff -->
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the templates
|
||||
trim = true
|
||||
|
||||
[git]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features", default_scope = "app" },
|
||||
{ message = "^fix", group = "Bug Fixes", scope = "cli" },
|
||||
]
|
11
.github/fixtures/test-bump-unreleased-with-tag-message-arg/commit.sh
vendored
Executable file
11
.github/fixtures/test-bump-unreleased-with-tag-message-arg/commit.sh
vendored
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:08" git commit --allow-empty -m "Initial commit"
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:09" git commit --allow-empty -m "feat: add feature 1"
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:10" git commit --allow-empty -m "fix: fix feature 1"
|
||||
git tag v0.1.0
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:11" git commit --allow-empty -m "feat(gui): add feature 2"
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:12" git commit --allow-empty -m "fix(gui): fix feature 2"
|
||||
git tag v0.2.0
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:13" git commit --allow-empty -m "test: add tests"
|
13
.github/fixtures/test-bump-unreleased-with-tag-message-arg/expected.md
vendored
Normal file
13
.github/fixtures/test-bump-unreleased-with-tag-message-arg/expected.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.2.1]
|
||||
|
||||
Some text
|
||||
|
||||
### Test
|
||||
|
||||
- Add tests
|
||||
|
||||
<!-- generated by git-cliff -->
|
37
.github/fixtures/test-tag-message/cliff.toml
vendored
Normal file
37
.github/fixtures/test-tag-message/cliff.toml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
[changelog]
|
||||
# template for the changelog footer
|
||||
header = """
|
||||
# Changelog\n
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://keats.github.io/tera/docs/#introduction
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% if message %}
|
||||
{{ message }}
|
||||
{% endif %}\
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {{ commit.message | upper_first }}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# template for the changelog footer
|
||||
footer = """
|
||||
<!-- generated by git-cliff -->
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the templates
|
||||
trim = true
|
||||
|
||||
[git]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features", default_scope = "app" },
|
||||
{ message = "^fix", group = "Bug Fixes", scope = "cli" },
|
||||
]
|
11
.github/fixtures/test-tag-message/commit.sh
vendored
Executable file
11
.github/fixtures/test-tag-message/commit.sh
vendored
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:08" git commit --allow-empty -m "Initial commit"
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:09" git commit --allow-empty -m "feat: add feature 1"
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:10" git commit --allow-empty -m "fix: fix feature 1"
|
||||
git tag v0.1.0 -m "Some text"
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:11" git commit --allow-empty -m "feat(gui): add feature 2"
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:12" git commit --allow-empty -m "fix(gui): fix feature 2"
|
||||
git tag v0.2.0
|
||||
GIT_COMMITTER_DATE="2022-04-06 01:25:13" git commit --allow-empty -m "test: add tests"
|
33
.github/fixtures/test-tag-message/expected.md
vendored
Normal file
33
.github/fixtures/test-tag-message/expected.md
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [unreleased]
|
||||
|
||||
### Test
|
||||
|
||||
- Add tests
|
||||
|
||||
## [0.2.0] - 2022-04-06
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix feature 2
|
||||
|
||||
### Features
|
||||
|
||||
- Add feature 2
|
||||
|
||||
## [0.1.0] - 2022-04-06
|
||||
|
||||
Some text
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix feature 1
|
||||
|
||||
### Features
|
||||
|
||||
- Add feature 1
|
||||
|
||||
<!-- generated by git-cliff -->
|
3
.github/workflows/test-fixtures.yml
vendored
3
.github/workflows/test-fixtures.yml
vendored
@ -81,6 +81,9 @@ jobs:
|
||||
command: --bump --tag=2.1.1
|
||||
- fixtures-name: test-cli-arg-ignore-tags
|
||||
command: --ignore-tags ".*beta"
|
||||
- fixtures-name: test-tag-message
|
||||
- fixtures-name: test-bump-unreleased-with-tag-message-arg
|
||||
command: --bump --unreleased --with-tag-message "Some text"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -828,9 +828,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.18.3"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70"
|
||||
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"libc",
|
||||
@ -1364,9 +1364,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.16.2+1.7.2"
|
||||
version = "0.17.0+1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8"
|
||||
checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -76,7 +76,7 @@ url = "2.5.2"
|
||||
dyn-clone = "1.0.17"
|
||||
|
||||
[dependencies.git2]
|
||||
version = "0.18.3"
|
||||
version = "0.19.0"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
|
@ -815,6 +815,7 @@ mod test {
|
||||
};
|
||||
let test_release = Release {
|
||||
version: Some(String::from("v1.0.0")),
|
||||
message: None,
|
||||
commits: vec![
|
||||
Commit::new(
|
||||
String::from("coffee"),
|
||||
@ -909,6 +910,7 @@ mod test {
|
||||
},
|
||||
Release {
|
||||
version: None,
|
||||
message: None,
|
||||
commits: vec![
|
||||
Commit::new(
|
||||
String::from("abc123"),
|
||||
|
@ -33,6 +33,8 @@ pub mod remote;
|
||||
/// Git repository.
|
||||
#[cfg(feature = "repo")]
|
||||
pub mod repo;
|
||||
/// Git tag.
|
||||
pub mod tag;
|
||||
/// Template engine.
|
||||
pub mod template;
|
||||
|
||||
|
@ -21,6 +21,8 @@ use serde::{
|
||||
pub struct Release<'a> {
|
||||
/// Release version, git tag.
|
||||
pub version: Option<String>,
|
||||
/// git tag's message.
|
||||
pub message: Option<String>,
|
||||
/// Commits made for the release.
|
||||
pub commits: Vec<Commit<'a>>,
|
||||
/// Commit ID of the tag.
|
||||
@ -174,6 +176,7 @@ mod test {
|
||||
fn build_release<'a>(version: &str, commits: &'a [&str]) -> Release<'a> {
|
||||
Release {
|
||||
version: None,
|
||||
message: None,
|
||||
commits: commits
|
||||
.iter()
|
||||
.map(|v| Commit::from(v.to_string()))
|
||||
@ -363,6 +366,7 @@ mod test {
|
||||
|
||||
let mut release = Release {
|
||||
version: None,
|
||||
message: None,
|
||||
commits: vec![
|
||||
Commit::from(String::from(
|
||||
"1d244937ee6ceb8e0314a4a201ba93a7a61f2071 add github \
|
||||
@ -648,6 +652,7 @@ mod test {
|
||||
|
||||
let mut release = Release {
|
||||
version: None,
|
||||
message: None,
|
||||
commits: vec![
|
||||
Commit::from(String::from(
|
||||
"1d244937ee6ceb8e0314a4a201ba93a7a61f2071 add github \
|
||||
@ -991,6 +996,7 @@ mod test {
|
||||
|
||||
let mut release = Release {
|
||||
version: None,
|
||||
message: None,
|
||||
commits: vec![
|
||||
Commit::from(String::from(
|
||||
"1d244937ee6ceb8e0314a4a201ba93a7a61f2071 add github \
|
||||
|
@ -3,6 +3,7 @@ use crate::error::{
|
||||
Error,
|
||||
Result,
|
||||
};
|
||||
use crate::tag::Tag;
|
||||
use git2::{
|
||||
BranchType,
|
||||
Commit,
|
||||
@ -13,11 +14,20 @@ use git2::{
|
||||
};
|
||||
use glob::Pattern;
|
||||
use indexmap::IndexMap;
|
||||
use regex::Regex;
|
||||
use lazy_regex::{
|
||||
lazy_regex,
|
||||
Lazy,
|
||||
Regex,
|
||||
};
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use url::Url;
|
||||
|
||||
/// Regex for replacing the signature part of a tag message.
|
||||
static TAG_SIGNATURE_REGEX: Lazy<Regex> = lazy_regex!(
|
||||
r"(?s)-----BEGIN PGP SIGNATURE-----(.*?)-----END PGP SIGNATURE-----"
|
||||
);
|
||||
|
||||
/// Wrapper for [`Repository`] type from git2.
|
||||
///
|
||||
/// [`Repository`]: GitRepository
|
||||
@ -95,11 +105,38 @@ impl Repository {
|
||||
/// Returns the current tag.
|
||||
///
|
||||
/// It is the same as running `git describe --tags`
|
||||
pub fn current_tag(&self) -> Option<String> {
|
||||
pub fn current_tag(&self) -> Option<Tag> {
|
||||
self.inner
|
||||
.describe(DescribeOptions::new().describe_tags())
|
||||
.ok()
|
||||
.and_then(|describe| describe.format(None).ok())
|
||||
.and_then(|describe| {
|
||||
describe
|
||||
.format(None)
|
||||
.ok()
|
||||
.map(|name| self.resolve_tag(&name))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the tag object of the given name.
|
||||
///
|
||||
/// If given name doesn't exist, it still returns `Tag` with the given name.
|
||||
pub fn resolve_tag(&self, name: &str) -> Tag {
|
||||
match self
|
||||
.inner
|
||||
.resolve_reference_from_short_name(name)
|
||||
.and_then(|r| r.peel_to_tag())
|
||||
{
|
||||
Ok(tag) => Tag {
|
||||
name: tag.name().unwrap_or_default().to_owned(),
|
||||
message: tag.message().map(|msg| {
|
||||
TAG_SIGNATURE_REGEX.replace(msg, "").trim().to_owned()
|
||||
}),
|
||||
},
|
||||
_ => Tag {
|
||||
name: name.to_owned(),
|
||||
message: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the commit object of the given ID.
|
||||
@ -119,8 +156,8 @@ impl Repository {
|
||||
&self,
|
||||
pattern: &Option<Regex>,
|
||||
topo_order: bool,
|
||||
) -> Result<IndexMap<String, String>> {
|
||||
let mut tags: Vec<(Commit, String)> = Vec::new();
|
||||
) -> Result<IndexMap<String, Tag>> {
|
||||
let mut tags: Vec<(Commit, Tag)> = Vec::new();
|
||||
let tag_names = self.inner.tag_names(None)?;
|
||||
for name in tag_names
|
||||
.iter()
|
||||
@ -132,14 +169,22 @@ impl Repository {
|
||||
{
|
||||
let obj = self.inner.revparse_single(&name)?;
|
||||
if let Ok(commit) = obj.clone().into_commit() {
|
||||
tags.push((commit, name));
|
||||
tags.push((commit, Tag {
|
||||
name,
|
||||
message: None,
|
||||
}));
|
||||
} else if let Some(tag) = obj.as_tag() {
|
||||
if let Some(commit) = tag
|
||||
.target()
|
||||
.ok()
|
||||
.and_then(|target| target.into_commit().ok())
|
||||
{
|
||||
tags.push((commit, name));
|
||||
tags.push((commit, Tag {
|
||||
name: tag.name().map(String::from).unwrap_or(name),
|
||||
message: tag.message().map(|msg| {
|
||||
TAG_SIGNATURE_REGEX.replace(msg, "").trim().to_owned()
|
||||
}),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -261,7 +306,7 @@ mod test {
|
||||
fn get_latest_tag() -> Result<()> {
|
||||
let repository = get_repository()?;
|
||||
let tags = repository.tags(&None, false)?;
|
||||
assert_eq!(&get_last_tag()?, tags.last().expect("no tags found").1);
|
||||
assert_eq!(get_last_tag()?, tags.last().expect("no tags found").1.name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -270,16 +315,20 @@ mod test {
|
||||
let repository = get_repository()?;
|
||||
let tags = repository.tags(&None, true)?;
|
||||
assert_eq!(
|
||||
tags.get("2b8b4d3535f29231e05c3572e919634b9af907b6").expect(
|
||||
"the commit hash does not exist in the repository (tag v0.1.0)"
|
||||
),
|
||||
tags.get("2b8b4d3535f29231e05c3572e919634b9af907b6")
|
||||
.expect(
|
||||
"the commit hash does not exist in the repository (tag v0.1.0)"
|
||||
)
|
||||
.name,
|
||||
"v0.1.0"
|
||||
);
|
||||
assert_eq!(
|
||||
tags.get("4ddef08debfff48117586296e49d5caa0800d1b5").expect(
|
||||
"the commit hash does not exist in the repository (tag \
|
||||
v0.1.0-beta.4)"
|
||||
),
|
||||
tags.get("4ddef08debfff48117586296e49d5caa0800d1b5")
|
||||
.expect(
|
||||
"the commit hash does not exist in the repository (tag \
|
||||
v0.1.0-beta.4)"
|
||||
)
|
||||
.name,
|
||||
"v0.1.0-beta.4"
|
||||
);
|
||||
let tags = repository.tags(
|
||||
@ -290,9 +339,11 @@ mod test {
|
||||
true,
|
||||
)?;
|
||||
assert_eq!(
|
||||
tags.get("2b8b4d3535f29231e05c3572e919634b9af907b6").expect(
|
||||
"the commit hash does not exist in the repository (tag v0.1.0)"
|
||||
),
|
||||
tags.get("2b8b4d3535f29231e05c3572e919634b9af907b6")
|
||||
.expect(
|
||||
"the commit hash does not exist in the repository (tag v0.1.0)"
|
||||
)
|
||||
.name,
|
||||
"v0.1.0"
|
||||
);
|
||||
assert!(!tags.contains_key("4ddef08debfff48117586296e49d5caa0800d1b5"));
|
||||
@ -313,4 +364,30 @@ mod test {
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_existing_tag_with_name_and_message() -> Result<()> {
|
||||
let repository = get_repository()?;
|
||||
let tag = repository.resolve_tag("v0.2.3");
|
||||
assert_eq!(tag.name, "v0.2.3");
|
||||
assert_eq!(
|
||||
tag.message,
|
||||
Some(
|
||||
"Release v0.2.3\n\nBug Fixes\n- Fetch the dependencies before \
|
||||
copying the file to embed (9e29c95)"
|
||||
.to_string()
|
||||
)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_tag_when_no_tags_exist() -> Result<()> {
|
||||
let repository = get_repository()?;
|
||||
let tag = repository.resolve_tag("nonexistent-tag");
|
||||
assert_eq!(tag.name, "nonexistent-tag");
|
||||
assert_eq!(tag.message, None);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
47
git-cliff-core/src/tag.rs
Normal file
47
git-cliff-core/src/tag.rs
Normal file
@ -0,0 +1,47 @@
|
||||
/// Common tag object that is parsed from a repository.
|
||||
///
|
||||
/// Lightweight tags will have `None` as message.
|
||||
#[derive(Debug)]
|
||||
pub struct Tag {
|
||||
/// The name of the tag
|
||||
pub name: String,
|
||||
/// The message of the tag (only if it was annotated).
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn create_tag_with_name_and_message() {
|
||||
let tag = Tag {
|
||||
name: String::from("v1.0"),
|
||||
message: Some(String::from("Initial release")),
|
||||
};
|
||||
assert_eq!(tag.name, "v1.0");
|
||||
assert_eq!(tag.message, Some(String::from("Initial release")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_tag_with_name_and_no_message() {
|
||||
let tag = Tag {
|
||||
name: String::from("v1.0"),
|
||||
message: None,
|
||||
};
|
||||
assert_eq!(tag.name, "v1.0");
|
||||
assert_eq!(tag.message, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_print_tag_with_message() {
|
||||
let tag = Tag {
|
||||
name: String::from("v1.0"),
|
||||
message: Some(String::from("Initial release")),
|
||||
};
|
||||
assert_eq!(
|
||||
format!("{:?}", tag),
|
||||
"Tag { name: \"v1.0\", message: Some(\"Initial release\") }"
|
||||
);
|
||||
}
|
||||
}
|
@ -189,6 +189,7 @@ mod test {
|
||||
fn get_fake_release_data() -> Release<'static> {
|
||||
Release {
|
||||
version: Some(String::from("1.0")),
|
||||
message: None,
|
||||
commits: vec![
|
||||
Commit::new(
|
||||
String::from("123123"),
|
||||
|
@ -149,6 +149,7 @@ fn generate_changelog() -> Result<()> {
|
||||
let releases = vec![
|
||||
Release {
|
||||
version: Some(String::from("v2.0.0")),
|
||||
message: None,
|
||||
commits: vec![
|
||||
|
||||
Commit::new(
|
||||
@ -212,6 +213,7 @@ fn generate_changelog() -> Result<()> {
|
||||
},
|
||||
Release {
|
||||
version: Some(String::from("v1.0.0")),
|
||||
message: None,
|
||||
commits: vec![
|
||||
Commit::new(
|
||||
String::from("0bc123"),
|
||||
|
@ -64,7 +64,7 @@ pub struct Opt {
|
||||
help = "Prints help information",
|
||||
help_heading = "FLAGS"
|
||||
)]
|
||||
pub help: Option<bool>,
|
||||
pub help: Option<bool>,
|
||||
#[arg(
|
||||
short = 'V',
|
||||
long,
|
||||
@ -73,10 +73,10 @@ pub struct Opt {
|
||||
help = "Prints version information",
|
||||
help_heading = "FLAGS"
|
||||
)]
|
||||
pub version: Option<bool>,
|
||||
pub version: Option<bool>,
|
||||
/// Increases the logging verbosity.
|
||||
#[arg(short, long, action = ArgAction::Count, alias = "debug", help_heading = Some("FLAGS"))]
|
||||
pub verbose: u8,
|
||||
pub verbose: u8,
|
||||
/// Writes the default configuration file to cliff.toml
|
||||
#[arg(
|
||||
short,
|
||||
@ -85,7 +85,7 @@ pub struct Opt {
|
||||
num_args = 0..=1,
|
||||
required = false
|
||||
)]
|
||||
pub init: Option<Option<String>>,
|
||||
pub init: Option<Option<String>>,
|
||||
/// Sets the configuration file.
|
||||
#[arg(
|
||||
short,
|
||||
@ -95,7 +95,7 @@ pub struct Opt {
|
||||
default_value = DEFAULT_CONFIG,
|
||||
value_parser = Opt::parse_dir
|
||||
)]
|
||||
pub config: PathBuf,
|
||||
pub config: PathBuf,
|
||||
/// Sets the working directory.
|
||||
#[arg(
|
||||
short,
|
||||
@ -104,7 +104,7 @@ pub struct Opt {
|
||||
value_name = "PATH",
|
||||
value_parser = Opt::parse_dir
|
||||
)]
|
||||
pub workdir: Option<PathBuf>,
|
||||
pub workdir: Option<PathBuf>,
|
||||
/// Sets the git repository.
|
||||
#[arg(
|
||||
short,
|
||||
@ -114,7 +114,7 @@ pub struct Opt {
|
||||
num_args(1..),
|
||||
value_parser = Opt::parse_dir
|
||||
)]
|
||||
pub repository: Option<Vec<PathBuf>>,
|
||||
pub repository: Option<Vec<PathBuf>>,
|
||||
/// Sets the path to include related commits.
|
||||
#[arg(
|
||||
long,
|
||||
@ -122,7 +122,7 @@ pub struct Opt {
|
||||
value_name = "PATTERN",
|
||||
num_args(1..)
|
||||
)]
|
||||
pub include_path: Option<Vec<Pattern>>,
|
||||
pub include_path: Option<Vec<Pattern>>,
|
||||
/// Sets the path to exclude related commits.
|
||||
#[arg(
|
||||
long,
|
||||
@ -130,10 +130,10 @@ pub struct Opt {
|
||||
value_name = "PATTERN",
|
||||
num_args(1..)
|
||||
)]
|
||||
pub exclude_path: Option<Vec<Pattern>>,
|
||||
pub exclude_path: Option<Vec<Pattern>>,
|
||||
/// Sets the regex for matching git tags.
|
||||
#[arg(long, env = "GIT_CLIFF_TAG_PATTERN", value_name = "PATTERN")]
|
||||
pub tag_pattern: Option<Regex>,
|
||||
pub tag_pattern: Option<Regex>,
|
||||
/// Sets custom commit messages to include in the changelog.
|
||||
#[arg(
|
||||
long,
|
||||
@ -141,10 +141,18 @@ pub struct Opt {
|
||||
value_name = "MSG",
|
||||
num_args(1..)
|
||||
)]
|
||||
pub with_commit: Option<Vec<String>>,
|
||||
pub with_commit: Option<Vec<String>>,
|
||||
/// Sets custom message for the latest release.
|
||||
#[arg(
|
||||
long,
|
||||
env = "GIT_CLIFF_WITH_TAG_MESSAGE",
|
||||
value_name = "MSG",
|
||||
num_args = 0..=1,
|
||||
)]
|
||||
pub with_tag_message: Option<String>,
|
||||
/// Sets the tags to ignore in the changelog.
|
||||
#[arg(long, env = "GIT_CLIFF_IGNORE_TAGS", value_name = "PATTERN")]
|
||||
pub ignore_tags: Option<Regex>,
|
||||
pub ignore_tags: Option<Regex>,
|
||||
/// Sets commits that will be skipped in the changelog.
|
||||
#[arg(
|
||||
long,
|
||||
@ -152,7 +160,7 @@ pub struct Opt {
|
||||
value_name = "SHA1",
|
||||
num_args(1..)
|
||||
)]
|
||||
pub skip_commit: Option<Vec<String>>,
|
||||
pub skip_commit: Option<Vec<String>>,
|
||||
/// Prepends entries to the given changelog file.
|
||||
#[arg(
|
||||
short,
|
||||
@ -161,7 +169,7 @@ pub struct Opt {
|
||||
value_name = "PATH",
|
||||
value_parser = Opt::parse_dir
|
||||
)]
|
||||
pub prepend: Option<PathBuf>,
|
||||
pub prepend: Option<PathBuf>,
|
||||
/// Writes output to the given file.
|
||||
#[arg(
|
||||
short,
|
||||
@ -172,7 +180,7 @@ pub struct Opt {
|
||||
num_args = 0..=1,
|
||||
default_missing_value = DEFAULT_OUTPUT
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
pub output: Option<PathBuf>,
|
||||
/// Sets the tag for the latest version.
|
||||
#[arg(
|
||||
short,
|
||||
@ -181,13 +189,13 @@ pub struct Opt {
|
||||
value_name = "TAG",
|
||||
allow_hyphen_values = true
|
||||
)]
|
||||
pub tag: Option<String>,
|
||||
pub tag: Option<String>,
|
||||
/// Bumps the version for unreleased changes.
|
||||
#[arg(long, help_heading = Some("FLAGS"))]
|
||||
pub bump: bool,
|
||||
pub bump: bool,
|
||||
/// Prints bumped version for unreleased changes.
|
||||
#[arg(long, help_heading = Some("FLAGS"))]
|
||||
pub bumped_version: bool,
|
||||
pub bumped_version: bool,
|
||||
/// Sets the template for the changelog body.
|
||||
#[arg(
|
||||
short,
|
||||
@ -196,38 +204,38 @@ pub struct Opt {
|
||||
value_name = "TEMPLATE",
|
||||
allow_hyphen_values = true
|
||||
)]
|
||||
pub body: Option<String>,
|
||||
pub body: Option<String>,
|
||||
/// Processes the commits starting from the latest tag.
|
||||
#[arg(short, long, help_heading = Some("FLAGS"))]
|
||||
pub latest: bool,
|
||||
pub latest: bool,
|
||||
/// Processes the commits that belong to the current tag.
|
||||
#[arg(long, help_heading = Some("FLAGS"))]
|
||||
pub current: bool,
|
||||
pub current: bool,
|
||||
/// Processes the commits that do not belong to a tag.
|
||||
#[arg(short, long, help_heading = Some("FLAGS"))]
|
||||
pub unreleased: bool,
|
||||
pub unreleased: bool,
|
||||
/// Sorts the tags topologically.
|
||||
#[arg(long, help_heading = Some("FLAGS"))]
|
||||
pub topo_order: bool,
|
||||
pub topo_order: bool,
|
||||
/// Disables the external command execution.
|
||||
#[arg(long, help_heading = Some("FLAGS"))]
|
||||
pub no_exec: bool,
|
||||
pub no_exec: bool,
|
||||
/// Prints changelog context as JSON.
|
||||
#[arg(short = 'x', long, help_heading = Some("FLAGS"))]
|
||||
pub context: bool,
|
||||
pub context: bool,
|
||||
/// Strips the given parts from the changelog.
|
||||
#[arg(short, long, value_name = "PART", value_enum)]
|
||||
pub strip: Option<Strip>,
|
||||
pub strip: Option<Strip>,
|
||||
/// Sets sorting of the commits inside sections.
|
||||
#[arg(
|
||||
long,
|
||||
value_enum,
|
||||
default_value_t = Sort::Oldest
|
||||
)]
|
||||
pub sort: Sort,
|
||||
pub sort: Sort,
|
||||
/// Sets the commit range to process.
|
||||
#[arg(value_name = "RANGE", help_heading = Some("ARGS"))]
|
||||
pub range: Option<String>,
|
||||
pub range: Option<String>,
|
||||
/// Sets the GitHub API token.
|
||||
#[arg(
|
||||
long,
|
||||
@ -236,7 +244,7 @@ pub struct Opt {
|
||||
hide_env_values = true,
|
||||
hide = !cfg!(feature = "github"),
|
||||
)]
|
||||
pub github_token: Option<SecretString>,
|
||||
pub github_token: Option<SecretString>,
|
||||
/// Sets the GitHub repository.
|
||||
#[arg(
|
||||
long,
|
||||
@ -245,7 +253,7 @@ pub struct Opt {
|
||||
value_name = "OWNER/REPO",
|
||||
hide = !cfg!(feature = "github"),
|
||||
)]
|
||||
pub github_repo: Option<RemoteValue>,
|
||||
pub github_repo: Option<RemoteValue>,
|
||||
/// Sets the GitLab API token.
|
||||
#[arg(
|
||||
long,
|
||||
@ -254,7 +262,7 @@ pub struct Opt {
|
||||
hide_env_values = true,
|
||||
hide = !cfg!(feature = "gitlab"),
|
||||
)]
|
||||
pub gitlab_token: Option<SecretString>,
|
||||
pub gitlab_token: Option<SecretString>,
|
||||
/// Sets the GitLab repository.
|
||||
#[arg(
|
||||
long,
|
||||
@ -263,7 +271,7 @@ pub struct Opt {
|
||||
value_name = "OWNER/REPO",
|
||||
hide = !cfg!(feature = "gitlab"),
|
||||
)]
|
||||
pub gitlab_repo: Option<RemoteValue>,
|
||||
pub gitlab_repo: Option<RemoteValue>,
|
||||
/// Sets the Gitea API token.
|
||||
#[arg(
|
||||
long,
|
||||
@ -272,7 +280,7 @@ pub struct Opt {
|
||||
hide_env_values = true,
|
||||
hide = !cfg!(feature = "gitea"),
|
||||
)]
|
||||
pub gitea_token: Option<SecretString>,
|
||||
pub gitea_token: Option<SecretString>,
|
||||
/// Sets the GitLab repository.
|
||||
#[arg(
|
||||
long,
|
||||
@ -281,7 +289,7 @@ pub struct Opt {
|
||||
value_name = "OWNER/REPO",
|
||||
hide = !cfg!(feature = "gitea"),
|
||||
)]
|
||||
pub gitea_repo: Option<RemoteValue>,
|
||||
pub gitea_repo: Option<RemoteValue>,
|
||||
/// Sets the Bitbucket API token.
|
||||
#[arg(
|
||||
long,
|
||||
@ -290,7 +298,7 @@ pub struct Opt {
|
||||
hide_env_values = true,
|
||||
hide = !cfg!(feature = "bitbucket"),
|
||||
)]
|
||||
pub bitbucket_token: Option<SecretString>,
|
||||
pub bitbucket_token: Option<SecretString>,
|
||||
/// Sets the Bitbucket repository.
|
||||
#[arg(
|
||||
long,
|
||||
@ -299,7 +307,7 @@ pub struct Opt {
|
||||
value_name = "OWNER/REPO",
|
||||
hide = !cfg!(feature = "bitbucket"),
|
||||
)]
|
||||
pub bitbucket_repo: Option<RemoteValue>,
|
||||
pub bitbucket_repo: Option<RemoteValue>,
|
||||
}
|
||||
|
||||
/// Custom type for the remote value.
|
||||
|
@ -90,7 +90,9 @@ fn process_repository<'a>(
|
||||
let ignore_regex = config.git.ignore_tags.as_ref();
|
||||
tags = tags
|
||||
.into_iter()
|
||||
.filter(|(_, name)| {
|
||||
.filter(|(_, tag)| {
|
||||
let name = &tag.name;
|
||||
|
||||
// Keep skip tags to drop commits in the later stage.
|
||||
let skip = skip_regex.map(|r| r.is_match(name)).unwrap_or_default();
|
||||
|
||||
@ -184,7 +186,7 @@ fn process_repository<'a>(
|
||||
repository.current_tag().as_ref().and_then(|tag| {
|
||||
tags.iter()
|
||||
.enumerate()
|
||||
.find(|(_, (_, v))| v == &tag)
|
||||
.find(|(_, (_, v))| v.name == tag.name)
|
||||
.map(|(i, _)| i)
|
||||
}) {
|
||||
match current_tag_index.checked_sub(1) {
|
||||
@ -226,10 +228,10 @@ fn process_repository<'a>(
|
||||
if let Some(commit_id) = commits.first().map(|c| c.id().to_string()) {
|
||||
match tags.get(&commit_id) {
|
||||
Some(tag) => {
|
||||
warn!("There is already a tag ({}) for {}", tag, commit_id)
|
||||
warn!("There is already a tag ({:?}) for {}", tag, commit_id)
|
||||
}
|
||||
None => {
|
||||
tags.insert(commit_id, tag.to_string());
|
||||
tags.insert(commit_id, repository.resolve_tag(tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -249,9 +251,13 @@ fn process_repository<'a>(
|
||||
releases[release_index].commits.push(commit);
|
||||
}
|
||||
if let Some(tag) = tags.get(&commit_id) {
|
||||
releases[release_index].version = Some(tag.to_string());
|
||||
let tag_name = &tag.name;
|
||||
|
||||
releases[release_index].version = Some(tag_name.clone());
|
||||
releases[release_index].message = tag.message.clone();
|
||||
releases[release_index].commit_id = Some(commit_id);
|
||||
releases[release_index].timestamp = if args.tag.as_deref() == Some(tag) {
|
||||
releases[release_index].timestamp = if args.tag == Some(tag_name.clone())
|
||||
{
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)?
|
||||
.as_secs()
|
||||
@ -286,6 +292,13 @@ fn process_repository<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
// Set custom message for the latest release.
|
||||
if let Some(message) = &args.with_tag_message {
|
||||
if let Some(latest_release) = releases.iter_mut().last() {
|
||||
latest_release.message = Some(message.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
// Set the previous release if the first release does not have one set.
|
||||
if !releases.is_empty() &&
|
||||
releases
|
||||
@ -299,7 +312,7 @@ fn process_repository<'a>(
|
||||
.map(|tag| {
|
||||
tags.iter()
|
||||
.enumerate()
|
||||
.find(|(_, (_, v))| v == &tag)
|
||||
.find(|(_, (_, v))| v.name == tag.name)
|
||||
.and_then(|(i, _)| i.checked_sub(1))
|
||||
.and_then(|i| tags.get_index(i))
|
||||
})
|
||||
@ -307,10 +320,10 @@ fn process_repository<'a>(
|
||||
.flatten();
|
||||
|
||||
// Set the previous release if the first tag is found.
|
||||
if let Some((commit_id, version)) = first_tag {
|
||||
if let Some((commit_id, tag)) = first_tag {
|
||||
let previous_release = Release {
|
||||
commit_id: Some(commit_id.to_string()),
|
||||
version: Some(version.to_string()),
|
||||
version: Some(tag.name.clone()),
|
||||
timestamp: repository
|
||||
.find_commit(commit_id.to_string())
|
||||
.map(|v| v.time().seconds())
|
||||
|
@ -25,6 +25,7 @@ following context is generated to use for templating:
|
||||
```json
|
||||
{
|
||||
"version": "v0.1.0-rc.21",
|
||||
"message": "The annotated tag message for the release"
|
||||
"commits": [
|
||||
{
|
||||
"id": "e795460c9bb7275294d1fa53a9d73258fb51eb10",
|
||||
@ -129,6 +130,7 @@ If [`conventional_commits`](/docs/configuration/git#conventional_commits) is set
|
||||
```json
|
||||
{
|
||||
"version": "v0.1.0-rc.21",
|
||||
"message": "The annotated tag message for the release"
|
||||
"commits": [
|
||||
{
|
||||
"id": "e795460c9bb7275294d1fa53a9d73258fb51eb10",
|
||||
|
42
website/docs/usage/adding-tag-messages.md
Normal file
42
website/docs/usage/adding-tag-messages.md
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
sidebar_position: 9
|
||||
---
|
||||
|
||||
# Adding version (tag) message
|
||||
|
||||
Sometimes, you might want to include a special message or note related to a version of your project.
|
||||
This can be used to highlight significant milestones, provide additional context, or share information not captured by individual commit messages.
|
||||
|
||||
There are currently 2 ways of doing this, in both ways, the message is available in the context of the template under the name `message`:
|
||||
|
||||
```
|
||||
{% if message %}
|
||||
{{ message }}
|
||||
{% endif %}\
|
||||
```
|
||||
|
||||
## Using annotated tags
|
||||
|
||||
The recommended way of adding a version message is to add the message to the tag:
|
||||
|
||||
```bash
|
||||
git tag v1.0.0 -m "first release, yay!"
|
||||
```
|
||||
|
||||
So in the release's context, `message` will be "first release, yay!" (even if it is signed).
|
||||
|
||||
## Using `--with-tag-message`
|
||||
|
||||
If for some reason you don't want to have the message in the tag (or don't have a tag yet) but want to include it in the generated changelog, you can use the `--with-tag-message` flag:
|
||||
|
||||
```bash
|
||||
git cliff --bump --unreleased --with-tag-message "some text"
|
||||
```
|
||||
|
||||
In this case, you can only add a message to the latest release.
|
||||
|
||||
:::note
|
||||
|
||||
Please note that if you use `--with-tag-message` on a version it will ignore the original tag's message and use the one from the argument.
|
||||
|
||||
:::
|
@ -20,33 +20,38 @@ git-cliff [FLAGS] [OPTIONS] [--] [RANGE]
|
||||
--current Processes the commits that belong to the current tag
|
||||
-u, --unreleased Processes the commits that do not belong to a tag
|
||||
--topo-order Sorts the tags topologically
|
||||
-x, --context Prints changelog context as JSON
|
||||
--no-exec Disables the external command execution
|
||||
-x, --context Prints changelog context as JSON
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```
|
||||
-i, --init [<CONFIG>] Writes the default configuration file to cliff.toml
|
||||
-c, --config <PATH> Sets the configuration file [env: GIT_CLIFF_CONFIG=] [default: cliff.toml]
|
||||
-w, --workdir <PATH> Sets the working directory [env: GIT_CLIFF_WORKDIR=]
|
||||
-r, --repository <PATH>... Sets the git repository [env: GIT_CLIFF_REPOSITORY=]
|
||||
--include-path <PATTERN>... Sets the path to include related commits [env: GIT_CLIFF_INCLUDE_PATH=]
|
||||
--exclude-path <PATTERN>... Sets the path to exclude related commits [env: GIT_CLIFF_EXCLUDE_PATH=]
|
||||
--tag-pattern <PATTERN> Sets the regex for matching git tags [env: GIT_CLIFF_TAG_PATTERN=]
|
||||
--with-commit <MSG>... Sets custom commit messages to include in the changelog [env: GIT_CLIFF_WITH_COMMIT=]
|
||||
--ignore-tags <PATTERN> Sets the tags to ignore in the changelog [env: GIT_CLIFF_IGNORE_TAGS=]
|
||||
--skip-commit <SHA1>... Sets commits that will be skipped in the changelog [env: GIT_CLIFF_SKIP_COMMIT=]
|
||||
-p, --prepend <PATH> Prepends entries to the given changelog file [env: GIT_CLIFF_PREPEND=]
|
||||
-o, --output [<PATH>] Writes output to the given file [env: GIT_CLIFF_OUTPUT=]
|
||||
-t, --tag <TAG> Sets the tag for the latest version [env: GIT_CLIFF_TAG=]
|
||||
-b, --body <TEMPLATE> Sets the template for the changelog body [env: GIT_CLIFF_TEMPLATE=]
|
||||
-s, --strip <PART> Strips the given parts from the changelog [possible values: header, footer, all]
|
||||
--sort <SORT> Sets sorting of the commits inside sections [default: oldest] [possible values: oldest, newest]
|
||||
--github-token <TOKEN> Sets the GitHub API token [env: GITHUB_TOKEN]
|
||||
--github-repo <OWNER/REPO> Sets the GitHub repository [env: GITHUB_REPO=]
|
||||
--gitlab-token <TOKEN> Sets the GitLab API token [env: GITLAB_TOKEN]
|
||||
--gitlab-repo <OWNER/REPO> Sets the GitLab repository [env: GITLAB_REPO=]
|
||||
-i, --init [<CONFIG>] Writes the default configuration file to cliff.toml
|
||||
-c, --config <PATH> Sets the configuration file [env: GIT_CLIFF_CONFIG=] [default: cliff.toml]
|
||||
-w, --workdir <PATH> Sets the working directory [env: GIT_CLIFF_WORKDIR=]
|
||||
-r, --repository <PATH>... Sets the git repository [env: GIT_CLIFF_REPOSITORY=]
|
||||
--include-path <PATTERN>... Sets the path to include related commits [env: GIT_CLIFF_INCLUDE_PATH=]
|
||||
--exclude-path <PATTERN>... Sets the path to exclude related commits [env: GIT_CLIFF_EXCLUDE_PATH=]
|
||||
--tag-pattern <PATTERN> Sets the regex for matching git tags [env: GIT_CLIFF_TAG_PATTERN=]
|
||||
--with-commit <MSG>... Sets custom commit messages to include in the changelog [env: GIT_CLIFF_WITH_COMMIT=]
|
||||
--with-tag-message [<MSG>] Sets custom message for the latest release [env: GIT_CLIFF_WITH_TAG_MESSAGE=]
|
||||
--ignore-tags <PATTERN> Sets the tags to ignore in the changelog [env: GIT_CLIFF_IGNORE_TAGS=]
|
||||
--skip-commit <SHA1>... Sets commits that will be skipped in the changelog [env: GIT_CLIFF_SKIP_COMMIT=]
|
||||
-p, --prepend <PATH> Prepends entries to the given changelog file [env: GIT_CLIFF_PREPEND=]
|
||||
-o, --output [<PATH>] Writes output to the given file [env: GIT_CLIFF_OUTPUT=]
|
||||
-t, --tag <TAG> Sets the tag for the latest version [env: GIT_CLIFF_TAG=]
|
||||
-b, --body <TEMPLATE> Sets the template for the changelog body [env: GIT_CLIFF_TEMPLATE=]
|
||||
-s, --strip <PART> Strips the given parts from the changelog [possible values: header, footer, all]
|
||||
--sort <SORT> Sets sorting of the commits inside sections [default: oldest] [possible values: oldest, newest]
|
||||
--github-token <TOKEN> Sets the GitHub API token [env: GITHUB_TOKEN]
|
||||
--github-repo <OWNER/REPO> Sets the GitHub repository [env: GITHUB_REPO=]
|
||||
--gitlab-token <TOKEN> Sets the GitLab API token [env: GITLAB_TOKEN]
|
||||
--gitlab-repo <OWNER/REPO> Sets the GitLab repository [env: GITLAB_REPO=]
|
||||
--gitea-token <TOKEN> Sets the Gitea API token [env: GITEA_TOKEN]
|
||||
--gitea-repo <OWNER/REPO> Sets the GitLab repository [env: GITEA_REPO=]
|
||||
--bitbucket-token <TOKEN> Sets the Bitbucket API token [env: BITBUCKET_TOKEN]
|
||||
--bitbucket-repo <OWNER/REPO> Sets the Bitbucket repository [env: BITBUCKET_REPO=]
|
||||
```
|
||||
|
||||
## Args
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
sidebar_position: 9
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
# Print context
|
||||
|
Loading…
Reference in New Issue
Block a user