diff --git a/.github/fixtures/test-tag-message/cliff.toml b/.github/fixtures/test-tag-message/cliff.toml new file mode 100644 index 0000000000..374c30aac3 --- /dev/null +++ b/.github/fixtures/test-tag-message/cliff.toml @@ -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 = """ + +""" +# 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" }, +] diff --git a/.github/fixtures/test-tag-message/commit.sh b/.github/fixtures/test-tag-message/commit.sh new file mode 100755 index 0000000000..f2ec2b9ca0 --- /dev/null +++ b/.github/fixtures/test-tag-message/commit.sh @@ -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" diff --git a/.github/fixtures/test-tag-message/expected.md b/.github/fixtures/test-tag-message/expected.md new file mode 100644 index 0000000000..afcaca4dd9 --- /dev/null +++ b/.github/fixtures/test-tag-message/expected.md @@ -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 + + diff --git a/.github/workflows/test-fixtures.yml b/.github/workflows/test-fixtures.yml index 0afbefd0d9..03d88183bd 100644 --- a/.github/workflows/test-fixtures.yml +++ b/.github/workflows/test-fixtures.yml @@ -79,6 +79,7 @@ jobs: command: --bump --tag=2.1.1 - fixtures-name: test-cli-arg-ignore-tags command: --ignore-tags ".*beta" + - fixtures-name: test-tag-message steps: - name: Checkout diff --git a/config/cliff.toml b/config/cliff.toml index ba12c0b7a8..a6729c2b03 100644 --- a/config/cliff.toml +++ b/config/cliff.toml @@ -16,6 +16,9 @@ All notable changes to this project will be documented in this file.\n body = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} + {% if message %} + {{ message }} + {% endif %}\ {% else %}\ ## [unreleased] {% endif %}\ diff --git a/git-cliff-core/src/changelog.rs b/git-cliff-core/src/changelog.rs index 870f633c63..5680d49f76 100644 --- a/git-cliff-core/src/changelog.rs +++ b/git-cliff-core/src/changelog.rs @@ -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"), diff --git a/git-cliff-core/src/lib.rs b/git-cliff-core/src/lib.rs index 6f78c91ea4..812e0e777d 100644 --- a/git-cliff-core/src/lib.rs +++ b/git-cliff-core/src/lib.rs @@ -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; diff --git a/git-cliff-core/src/release.rs b/git-cliff-core/src/release.rs index 4d3f9308f1..3449f2eac4 100644 --- a/git-cliff-core/src/release.rs +++ b/git-cliff-core/src/release.rs @@ -21,6 +21,8 @@ use serde::{ pub struct Release<'a> { /// Release version, git tag. pub version: Option, + /// git tag's message. + pub message: Option, /// Commits made for the release. pub commits: Vec>, /// Commit ID of the tag. @@ -159,6 +161,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())) @@ -340,6 +343,7 @@ mod test { let mut release = Release { version: None, + message: None, commits: vec![ Commit::from(String::from( "1d244937ee6ceb8e0314a4a201ba93a7a61f2071 add github \ @@ -625,6 +629,7 @@ mod test { let mut release = Release { version: None, + message: None, commits: vec![ Commit::from(String::from( "1d244937ee6ceb8e0314a4a201ba93a7a61f2071 add github \ @@ -968,6 +973,7 @@ mod test { let mut release = Release { version: None, + message: None, commits: vec![ Commit::from(String::from( "1d244937ee6ceb8e0314a4a201ba93a7a61f2071 add github \ diff --git a/git-cliff-core/src/repo.rs b/git-cliff-core/src/repo.rs index 8723a640e7..1a9da2f595 100644 --- a/git-cliff-core/src/repo.rs +++ b/git-cliff-core/src/repo.rs @@ -3,6 +3,7 @@ use crate::error::{ Error, Result, }; +use crate::tag::Tag; use git2::{ BranchType, Commit, @@ -95,11 +96,47 @@ impl Repository { /// Returns the current tag. /// /// It is the same as running `git describe --tags` - pub fn current_tag(&self) -> Option { + pub fn current_tag(&self) -> Option { 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 don't exists, still returns Tag object with the given name + pub fn resolve_tag(&self, name: &str) -> Tag { + match self.inner.resolve_reference_from_short_name(name) { + Ok(reference) => match reference.peel_to_tag() { + Ok(tag) => Tag { + name: tag.name().unwrap_or_default().to_owned(), + message: tag.message().map(Self::cleanup_message), + }, + _ => Tag { + name: name.to_owned(), + message: None, + }, + }, + _ => Tag { + name: name.to_owned(), + message: None, + }, + } + } + + /// used to remove signature from signed tag message + fn cleanup_message(message: &str) -> String { + let re = Regex::new( + r"(?s)-----BEGIN PGP SIGNATURE-----(.*?)-----END PGP SIGNATURE-----", + ) + .expect("invalid regex, wtf"); + + re.replace(message, "").trim().to_string() } /// Returns the commit object of the given ID. @@ -119,8 +156,8 @@ impl Repository { &self, pattern: &Option, topo_order: bool, - ) -> Result> { - let mut tags: Vec<(Commit, String)> = Vec::new(); + ) -> Result> { + 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,21 @@ impl Repository { { let obj = self.inner.revparse_single(&name)?; if let Ok(commit) = obj.clone().into_commit() { - tags.push((commit, name)); + // lightweight commit? + 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().expect("tag don't have name").to_owned(), + message: tag.message().map(|msg| msg.to_owned()), + })); } } } @@ -261,7 +305,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 +314,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 +338,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 +363,35 @@ 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(()) + } } diff --git a/git-cliff-core/src/tag.rs b/git-cliff-core/src/tag.rs new file mode 100644 index 0000000000..401a5d3d0f --- /dev/null +++ b/git-cliff-core/src/tag.rs @@ -0,0 +1,46 @@ +/// Common tag object that is parsed from a repository. +/// lightweight tag will have None at 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, +} + +#[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\") }" + ); + } +} diff --git a/git-cliff-core/src/template.rs b/git-cliff-core/src/template.rs index d5dbf93012..3fe0e8a3a9 100644 --- a/git-cliff-core/src/template.rs +++ b/git-cliff-core/src/template.rs @@ -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"), diff --git a/git-cliff-core/tests/integration_test.rs b/git-cliff-core/tests/integration_test.rs index 95fd991ab6..3e960fac69 100644 --- a/git-cliff-core/tests/integration_test.rs +++ b/git-cliff-core/tests/integration_test.rs @@ -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"), diff --git a/git-cliff/src/lib.rs b/git-cliff/src/lib.rs index a58c5d5695..f29021af91 100644 --- a/git-cliff/src/lib.rs +++ b/git-cliff/src/lib.rs @@ -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() @@ -299,7 +305,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 +313,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())