Repository: orhun/git-cliff
Pull request #853 (2024-09-15 16:33:42)

chore(ci): check pedantic lints
## Description

Add pedantic lints checking to the CI and do not fail the CI if warnings exist.

## Motivation and Context

It is good to have some reports about pedantic errors at the merging level, especially in pull requests. Now, users can see the quality of their code on a pedantic linter level. 

We are currently resolving many pedantic errors in the existing code in the other MR.

## How Has This Been Tested?

I tested the command manually in the terminal.
I tested the CI in that pull request. If the build passes and we have an extra step in the Lints job with pedantic warnings, that means we have configured the CI correctly.
![image](https://github.com/user-attachments/assets/6fb1cff1-3e34-413e-b0d8-c1d136ddc044)

## Types of Changes

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation (no code change)
- [ ] Refactor (refactoring production code)
- [x] Other  - CI 

## Checklist:

- [x] My code follows the code style of this project.
- [x] I have updated the documentation accordingly.
- [x] I have formatted the code with [rustfmt](https://github.com/rust-lang/rustfmt).
- [x] I checked the lints with [clippy](https://github.com/rust-lang/rust-clippy).
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.


codecov-commenter: :warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov.

## [Codecov](https://app.codecov.io/gh/orhun/git-cliff/pull/853?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Orhun+Parmaks%C4%B1z) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 39.94%. Comparing base [(`fe37d14`)](https://app.codecov.io/gh/orhun/git-cliff/commit/fe37d148fbada25f78a8d8ee41f4c4ec6b976fa0?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Orhun+Parmaks%C4%B1z) to head [(`9eba16d`)](https://app.codecov.io/gh/orhun/git-cliff/commit/9eba16d21e493ea3b87e3faec68ed9b66b60e835?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Orhun+Parmaks%C4%B1z).

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #853   +/-   ##
=======================================
  Coverage   39.94%   39.94%           
=======================================
  Files          20       20           
  Lines        1660     1660           
=======================================
  Hits          663      663           
  Misses        997      997           
```

| [Flag](https://app.codecov.io/gh/orhun/git-cliff/pull/853/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Orhun+Parmaks%C4%B1z) | Coverage Δ | |
|---|---|---|
| [unit-tests](https://app.codecov.io/gh/orhun/git-cliff/pull/853/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Orhun+Parmaks%C4%B1z) | `39.94% <ø> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Orhun+Parmaks%C4%B1z#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/orhun/git-cliff/pull/853?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Orhun+Parmaks%C4%B1z).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Orhun+Parmaks%C4%B1z).

orhun: Oops, there seems to be a lot of lints :D I guess it's fine to ignore some of them for now.
nejcgalof: Yes :) For that reason, I split the nice checks into multiple MRs instead of solving them in one MR. I will ignore some of them. I still have around 15 checks left on my TODO list. Is it okay with you if I continue with them using the workflow I had before (each check is in a separate MR)?

When I finish with them, I can ping you, and we can decide what to do with the rest of them. We can also ignore the rest of the checks in the pedantic CI check.
orhun: Yup, sounds good, as long as we don't exaggerate things a lot :) I don't like all the pedantic lints tbh, the ones you sent were reasonable so far.

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 08db573..3dd9f6e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -94,6 +94,11 @@ jobs:
         with:
           command: clippy
           args: --tests --verbose -- -D warnings
+      - name: Check the pedantic lints
+        uses: actions-rs/cargo@v1
+        with:
+          command: clippy
+          args: --tests --verbose -- -W clippy::pedantic
 
   rustfmt:
     name: Formatting
diff --git a/Cargo.lock b/Cargo.lock
index d21e1e0..81e0fb0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -377,9 +377,9 @@ dependencies = [
 
 [[package]]
 name = "clap_complete"
-version = "4.5.23"
+version = "4.5.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "531d7959c5bbb6e266cecdd0f20213639c3a5c3e4d615f97db87661745f781ff"
+checksum = "9b378c786d3bde9442d2c6dd7e6080b2a818db2b96e30d6e7f1b6d224eb617d3"
 dependencies = [
  "clap",
 ]
@@ -1953,9 +1953,9 @@ dependencies = [
 
 [[package]]
 name = "pretty_assertions"
-version = "1.4.0"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
+checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
 dependencies = [
  "diff",
  "yansi",
@@ -3595,9 +3595,9 @@ dependencies = [
 
 [[package]]
 name = "yansi"
-version = "0.5.1"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
+checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
 
 [[package]]
 name = "zerocopy"
diff --git a/examples/github-keepachangelog.toml b/examples/github-keepachangelog.toml
index 0c587aa..1a8f2f2 100644
--- a/examples/github-keepachangelog.toml
+++ b/examples/github-keepachangelog.toml
@@ -27,9 +27,9 @@ body = """
     ### {{ group | upper_first }}
     {%- for commit in commits %}
         - {{ commit.message | split(pat="\n") | first | upper_first | trim }}\
-            {% if commit.github.username %} by @{{ commit.github.username }}{%- endif -%}
-            {% if commit.github.pr_number %} in \
-            [#{{ commit.github.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.github.pr_number }}) \
+            {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}
+            {% if commit.remote.pr_number %} in \
+            [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) \
             {%- endif -%}
     {% endfor %}
 {% endfor %}
diff --git a/examples/github.toml b/examples/github.toml
index 9853f54..9ec3e91 100644
--- a/examples/github.toml
+++ b/examples/github.toml
@@ -14,15 +14,15 @@ body = """
 
 {%- if version %} in {{ version }}{%- endif -%}
 {% for commit in commits %}
-  {% if commit.github.pr_title -%}
-    {%- set commit_message = commit.github.pr_title -%}
+  {% if commit.remote.pr_title -%}
+    {%- set commit_message = commit.remote.pr_title -%}
   {%- else -%}
     {%- set commit_message = commit.message -%}
   {%- endif -%}
   * {{ commit_message | split(pat="\n") | first | trim }}\
-    {% if commit.github.username %} by @{{ commit.github.username }}{%- endif -%}
-    {% if commit.github.pr_number %} in \
-      [#{{ commit.github.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.github.pr_number }}) \
+    {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}
+    {% if commit.remote.pr_number %} in \
+      [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) \
     {%- endif %}
 {%- endfor -%}
 
diff --git a/git-cliff-core/Cargo.toml b/git-cliff-core/Cargo.toml
index 1eee1e7..aa62238 100644
--- a/git-cliff-core/Cargo.toml
+++ b/git-cliff-core/Cargo.toml
@@ -97,7 +97,7 @@ version = "8.5.0"
 features = ["debug-embed", "compression"]
 
 [dev-dependencies]
-pretty_assertions = "1.4.0"
+pretty_assertions = "1.4.1"
 expect-test = "1.5.0"
 temp-dir = "0.1.13"
 
diff --git a/git-cliff-core/src/changelog.rs b/git-cliff-core/src/changelog.rs
index 32317a5..7cf3115 100644
--- a/git-cliff-core/src/changelog.rs
+++ b/git-cliff-core/src/changelog.rs
@@ -55,20 +55,16 @@ impl<'a> Changelog<'a> {
 		Ok(Self {
 			releases,
 			header_template: match &config.changelog.header {
-				Some(header) => Some(Template::new(header.to_string(), trim)?),
+				Some(header) => {
+					Some(Template::new("header", header.to_string(), trim)?)
+				}
 				None => None,
 			},
-			body_template: Template::new(
-				config
-					.changelog
-					.body
-					.as_deref()
-					.unwrap_or_default()
-					.to_string(),
-				trim,
-			)?,
+			body_template: get_body_template(config, trim)?,
 			footer_template: match &config.changelog.footer {
-				Some(footer) => Some(Template::new(footer.to_string(), trim)?),
+				Some(footer) => {
+					Some(Template::new("footer", footer.to_string(), trim)?)
+				}
 				None => None,
 			},
 			config,
@@ -162,21 +158,19 @@ impl<'a> Changelog<'a> {
 			.rev()
 			.filter(|release| {
 				if release.commits.is_empty() {
-					if let Some(version) = release.version.as_ref().cloned() {
+					if let Some(version) = release.version.clone() {
 						trace!("Release doesn't have any commits: {}", version);
 					}
 					false
 				} else if let Some(version) = &release.version {
-					!skip_regex
-						.map(|r| {
-							let skip_tag = r.is_match(version);
-							if skip_tag {
-								skipped_tags.push(version.clone());
-								trace!("Skipping release: {}", version);
-							}
-							skip_tag
-						})
-						.unwrap_or_default()
+					!skip_regex.is_some_and(|r| {
+						let skip_tag = r.is_match(version);
+						if skip_tag {
+							skipped_tags.push(version.clone());
+							trace!("Skipping release: {}", version);
+						}
+						skip_tag
+					})
 				} else {
 					true
 				}
@@ -475,7 +469,8 @@ impl<'a> Changelog<'a> {
 			} else {
 				(vec![], vec![])
 			};
-		for release in self.releases.iter_mut() {
+		#[cfg(feature = "remote")]
+		for release in &mut self.releases {
 			#[cfg(feature = "github")]
 			release.update_github_metadata(
 				github_commits.clone(),
@@ -612,6 +607,29 @@ impl<'a> Changelog<'a> {
 	}
 }
 
+fn get_body_template(config: &Config, trim: bool) -> Result<Template> {
+	let template_str = config
+		.changelog
+		.body
+		.as_deref()
+		.unwrap_or_default()
+		.to_string();
+	let template = Template::new("body", template_str, trim)?;
+	let deprecated_vars = [
+		"commit.github",
+		"commit.gitea",
+		"commit.gitlab",
+		"commit.bitbucket",
+	];
+	if template.contains_variable(&deprecated_vars) {
+		warn!(
+			"Variables {deprecated_vars:?} are deprecated and will be removed in \
+			 the future. Use `commit.remote` instead."
+		);
+	}
+	Ok(template)
+}
+
 #[cfg(test)]
 mod test {
 	use super::*;
diff --git a/git-cliff-core/src/commit.rs b/git-cliff-core/src/commit.rs
index ebd5328..c5414fc 100644
--- a/git-cliff-core/src/commit.rs
+++ b/git-cliff-core/src/commit.rs
@@ -125,18 +125,24 @@ pub struct Commit<'a> {
 	pub merge_commit:  bool,
 	/// Arbitrary data to be used with the `--from-context` CLI option.
 	pub extra:         Option<Value>,
+	/// Remote metadata of the commit.
+	pub remote:        Option<crate::contributor::RemoteContributor>,
 	/// GitHub metadata of the commit.
 	#[cfg(feature = "github")]
-	pub github:        crate::remote::RemoteContributor,
+	#[deprecated(note = "Use `remote` field instead")]
+	pub github:        crate::contributor::RemoteContributor,
 	/// GitLab metadata of the commit.
 	#[cfg(feature = "gitlab")]
-	pub gitlab:        crate::remote::RemoteContributor,
+	#[deprecated(note = "Use `remote` field instead")]
+	pub gitlab:        crate::contributor::RemoteContributor,
 	/// Gitea metadata of the commit.
 	#[cfg(feature = "gitea")]
-	pub gitea:         crate::remote::RemoteContributor,
+	#[deprecated(note = "Use `remote` field instead")]
+	pub gitea:         crate::contributor::RemoteContributor,
 	/// Bitbucket metadata of the commit.
 	#[cfg(feature = "bitbucket")]
-	pub bitbucket:     crate::remote::RemoteContributor,
+	#[deprecated(note = "Use `remote` field instead")]
+	pub bitbucket:     crate::contributor::RemoteContributor,
 }
 
 impl<'a> From<String> for Commit<'a> {
@@ -166,7 +172,7 @@ impl<'a> From<&GitCommit<'a>> for Commit<'a> {
 	fn from(commit: &GitCommit<'a>) -> Self {
 		Commit {
 			id: commit.id().to_string(),
-			message: commit.message().unwrap_or_default().to_string(),
+			message: commit.message().unwrap_or_default().trim_end().to_string(),
 			author: commit.author().into(),
 			committer: commit.committer().into(),
 			merge_commit: commit.parent_count() > 1,
@@ -341,11 +347,9 @@ impl Commit<'_> {
 							}
 							value
 						};
-						self.group =
-							parser.group.as_ref().cloned().map(regex_replace);
-						self.scope =
-							parser.scope.as_ref().cloned().map(regex_replace);
-						self.default_scope = parser.default_scope.as_ref().cloned();
+						self.group = parser.group.clone().map(regex_replace);
+						self.scope = parser.scope.clone().map(regex_replace);
+						self.default_scope = parser.default_scope.clone();
 						return Ok(self);
 					}
 				}
@@ -398,6 +402,7 @@ impl Commit<'_> {
 }
 
 impl Serialize for Commit<'_> {
+	#[allow(deprecated)]
 	fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
 	where
 		S: Serializer,
@@ -466,6 +471,9 @@ impl Serialize for Commit<'_> {
 		commit.serialize_field("gitea", &self.gitea)?;
 		#[cfg(feature = "bitbucket")]
 		commit.serialize_field("bitbucket", &self.bitbucket)?;
+		if let Some(remote) = &self.remote {
+			commit.serialize_field("remote", remote)?;
+		}
 		commit.end()
 	}
 }
diff --git a/git-cliff-core/src/config.rs b/git-cliff-core/src/config.rs
index 2677f31..a39af6a 100644
--- a/git-cliff-core/src/config.rs
+++ b/git-cliff-core/src/config.rs
@@ -351,7 +351,7 @@ impl Config {
 	/// Reads the config file contents from project manifest (e.g. Cargo.toml,
 	/// pyproject.toml)
 	pub fn read_from_manifest() -> Result<Option<String>> {
-		for info in (*MANIFEST_INFO).iter() {
+		for info in &(*MANIFEST_INFO) {
 			if info.path.exists() {
 				let contents = fs::read_to_string(&info.path)?;
 				if info.regex.is_match(&contents) {
diff --git a/git-cliff-core/src/contributor.rs b/git-cliff-core/src/contributor.rs
new file mode 100644
index 00000000..957ce4a
--- /dev/null
+++ b/git-cliff-core/src/contributor.rs
@@ -0,0 +1,29 @@
+use serde::{
+	Deserialize,
+	Serialize,
+};
+use std::hash::{
+	Hash,
+	Hasher,
+};
+
+/// Representation of a remote contributor.
+#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)]
+pub struct RemoteContributor {
+	/// Username.
+	pub username:      Option<String>,
+	/// Title of the pull request.
+	pub pr_title:      Option<String>,
+	/// The pull request that the user created.
+	pub pr_number:     Option<i64>,
+	/// Labels of the pull request.
+	pub pr_labels:     Vec<String>,
+	/// Whether if the user contributed for the first time.
+	pub is_first_time: bool,
+}
+
+impl Hash for RemoteContributor {
+	fn hash<H: Hasher>(&self, state: &mut H) {
+		self.username.hash(state);
+	}
+}
diff --git a/git-cliff-core/src/embed.rs b/git-cliff-core/src/embed.rs
index 0cf2d29..79450df 100644
--- a/git-cliff-core/src/embed.rs
+++ b/git-cliff-core/src/embed.rs
@@ -4,6 +4,7 @@ use crate::error::{
 	Result,
 };
 use rust_embed::RustEmbed;
+use std::path::Path;
 use std::str;
 
 /// Default configuration file embedder/extractor.
@@ -44,7 +45,10 @@ pub struct BuiltinConfig;
 impl BuiltinConfig {
 	/// Extracts the embedded content.
 	pub fn get_config(mut name: String) -> Result<String> {
-		if !name.ends_with(".toml") {
+		if !Path::new(&name)
+			.extension()
+			.map_or(false, |ext| ext.eq_ignore_ascii_case("toml"))
+		{
 			name = format!("{name}.toml");
 		}
 		let contents = match Self::get(&name) {
diff --git a/git-cliff-core/src/lib.rs b/git-cliff-core/src/lib.rs
index 812e0e7..1851ca9 100644
--- a/git-cliff-core/src/lib.rs
+++ b/git-cliff-core/src/lib.rs
@@ -20,6 +20,8 @@ pub mod command;
 pub mod commit;
 /// Config file parser.
 pub mod config;
+/// Remote contributor.
+pub mod contributor;
 /// Embedded file handler.
 pub mod embed;
 /// Error handling.
diff --git a/git-cliff-core/src/release.rs b/git-cliff-core/src/release.rs
index 3c9fcab..3e4dd6f 100644
--- a/git-cliff-core/src/release.rs
+++ b/git-cliff-core/src/release.rs
@@ -1,16 +1,18 @@
 use crate::error::Result;
-#[cfg(feature = "remote")]
-use crate::remote::{
-	RemoteCommit,
-	RemoteContributor,
-	RemotePullRequest,
-	RemoteReleaseMetadata,
-};
 use crate::{
 	commit::Commit,
 	config::Bump,
 	config::BumpType,
 };
+#[cfg(feature = "remote")]
+use crate::{
+	contributor::RemoteContributor,
+	remote::{
+		RemoteCommit,
+		RemotePullRequest,
+		RemoteReleaseMetadata,
+	},
+};
 
 use next_version::{
 	NextVersion,
@@ -179,6 +181,7 @@ impl<'a> Releases<'a> {
 #[cfg(test)]
 mod test {
 	use super::*;
+	use pretty_assertions::assert_eq;
 	#[test]
 	fn bump_version() -> Result<()> {
 		fn build_release<'a>(version: &str, commits: &'a [&str]) -> Release<'a> {
@@ -539,6 +542,7 @@ mod test {
 			.map(|v| Box::new(v) as Box<dyn RemotePullRequest>)
 			.collect(),
 		)?;
+		#[allow(deprecated)]
 		let expected_commits = vec![
 			Commit {
 				id: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
@@ -550,6 +554,13 @@ mod test {
 					pr_labels:     vec![String::from("rust")],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("orhun")),
+					pr_title:      Some(String::from("1")),
+					pr_number:     Some(42),
+					pr_labels:     vec![String::from("rust")],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -562,6 +573,13 @@ mod test {
 					pr_labels:     vec![String::from("rust")],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("orhun")),
+					pr_title:      Some(String::from("2")),
+					pr_number:     Some(66),
+					pr_labels:     vec![String::from("rust")],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -574,6 +592,13 @@ mod test {
 					pr_labels:     vec![String::from("deps")],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("nuhro")),
+					pr_title:      Some(String::from("3")),
+					pr_number:     Some(53),
+					pr_labels:     vec![String::from("deps")],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -586,6 +611,13 @@ mod test {
 					pr_labels:     vec![String::from("deps")],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("awesome_contributor")),
+					pr_title:      Some(String::from("4")),
+					pr_number:     Some(1000),
+					pr_labels:     vec![String::from("deps")],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -598,6 +630,13 @@ mod test {
 					pr_labels:     vec![String::from("github")],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("orhun")),
+					pr_title:      Some(String::from("5")),
+					pr_number:     Some(999999),
+					pr_labels:     vec![String::from("github")],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -610,6 +649,13 @@ mod test {
 					pr_labels:     vec![],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("someone")),
+					pr_title:      None,
+					pr_number:     None,
+					pr_labels:     vec![],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 		];
@@ -703,6 +749,7 @@ mod test {
 			github: RemoteReleaseMetadata {
 				contributors: vec![],
 			},
+			#[cfg(feature = "gitlab")]
 			gitlab: RemoteReleaseMetadata {
 				contributors: vec![],
 			},
@@ -884,6 +931,7 @@ mod test {
 				labels:            vec![String::from("rust")],
 			})],
 		)?;
+		#[allow(deprecated)]
 		let expected_commits = vec![
 			Commit {
 				id: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
@@ -895,6 +943,13 @@ mod test {
 					pr_labels:     vec![String::from("rust")],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("orhun")),
+					pr_title:      Some(String::from("1")),
+					pr_number:     Some(1),
+					pr_labels:     vec![String::from("rust")],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -907,6 +962,13 @@ mod test {
 					pr_labels:     vec![],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("orhun")),
+					pr_title:      None,
+					pr_number:     None,
+					pr_labels:     vec![],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -919,6 +981,13 @@ mod test {
 					pr_labels:     vec![],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("nuhro")),
+					pr_title:      None,
+					pr_number:     None,
+					pr_labels:     vec![],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -931,6 +1000,13 @@ mod test {
 					pr_labels:     vec![],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("awesome_contributor")),
+					pr_title:      None,
+					pr_number:     None,
+					pr_labels:     vec![],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -943,6 +1019,13 @@ mod test {
 					pr_labels:     vec![],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("orhun")),
+					pr_title:      None,
+					pr_number:     None,
+					pr_labels:     vec![],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -955,6 +1038,13 @@ mod test {
 					pr_labels:     vec![],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("someone")),
+					pr_title:      None,
+					pr_number:     None,
+					pr_labels:     vec![],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 		];
@@ -1053,6 +1143,7 @@ mod test {
 			gitlab: RemoteReleaseMetadata {
 				contributors: vec![],
 			},
+			#[cfg(feature = "gitea")]
 			gitea: RemoteReleaseMetadata {
 				contributors: vec![],
 			},
@@ -1173,6 +1264,7 @@ mod test {
 			.map(|v| Box::new(v) as Box<dyn RemotePullRequest>)
 			.collect(),
 		)?;
+		#[allow(deprecated)]
 		let expected_commits = vec![
 			Commit {
 				id: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
@@ -1184,6 +1276,13 @@ mod test {
 					pr_labels:     vec![String::from("rust")],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("orhun")),
+					pr_title:      Some(String::from("1")),
+					pr_number:     Some(42),
+					pr_labels:     vec![String::from("rust")],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -1196,6 +1295,13 @@ mod test {
 					pr_labels:     vec![String::from("rust")],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("orhun")),
+					pr_title:      Some(String::from("2")),
+					pr_number:     Some(66),
+					pr_labels:     vec![String::from("rust")],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -1208,6 +1314,13 @@ mod test {
 					pr_labels:     vec![String::from("deps")],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("nuhro")),
+					pr_title:      Some(String::from("3")),
+					pr_number:     Some(53),
+					pr_labels:     vec![String::from("deps")],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -1220,6 +1333,13 @@ mod test {
 					pr_labels:     vec![String::from("deps")],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("awesome_contributor")),
+					pr_title:      Some(String::from("4")),
+					pr_number:     Some(1000),
+					pr_labels:     vec![String::from("deps")],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -1232,6 +1352,13 @@ mod test {
 					pr_labels:     vec![String::from("github")],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("orhun")),
+					pr_title:      Some(String::from("5")),
+					pr_number:     Some(999999),
+					pr_labels:     vec![String::from("github")],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 			Commit {
@@ -1244,6 +1371,13 @@ mod test {
 					pr_labels:     vec![],
 					is_first_time: false,
 				},
+				remote: Some(RemoteContributor {
+					username:      Some(String::from("someone")),
+					pr_title:      None,
+					pr_number:     None,
+					pr_labels:     vec![],
+					is_first_time: false,
+				}),
 				..Default::default()
 			},
 		];
diff --git a/git-cliff-core/src/remote/bitbucket.rs b/git-cliff-core/src/remote/bitbucket.rs
index 7597711..f45a47c 100644
--- a/git-cliff-core/src/remote/bitbucket.rs
+++ b/git-cliff-core/src/remote/bitbucket.rs
@@ -22,7 +22,8 @@ pub const START_FETCHING_MSG: &str = "Retrieving data from Bitbucket...";
 pub const FINISHED_FETCHING_MSG: &str = "Done fetching Bitbucket data.";
 
 /// Template variables related to this remote.
-pub(crate) const TEMPLATE_VARIABLES: &[&str] = &["bitbucket", "commit.bitbucket"];
+pub(crate) const TEMPLATE_VARIABLES: &[&str] =
+	&["bitbucket", "commit.bitbucket", "commit.remote"];
 
 /// Maximum number of entries to fetch for bitbucket pull requests.
 pub(crate) const BITBUCKET_MAX_PAGE_PRS: usize = 50;
diff --git a/git-cliff-core/src/remote/gitea.rs b/git-cliff-core/src/remote/gitea.rs
index 251776c..f6df6b2 100644
--- a/git-cliff-core/src/remote/gitea.rs
+++ b/git-cliff-core/src/remote/gitea.rs
@@ -22,7 +22,8 @@ pub const START_FETCHING_MSG: &str = "Retrieving data from Gitea...";
 pub const FINISHED_FETCHING_MSG: &str = "Done fetching Gitea data.";
 
 /// Template variables related to this remote.
-pub(crate) const TEMPLATE_VARIABLES: &[&str] = &["gitea", "commit.gitea"];
+pub(crate) const TEMPLATE_VARIABLES: &[&str] =
+	&["gitea", "commit.gitea", "commit.remote"];
 
 /// Representation of a single commit.
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
diff --git a/git-cliff-core/src/remote/github.rs b/git-cliff-core/src/remote/github.rs
index 91e5395..8a06a70 100644
--- a/git-cliff-core/src/remote/github.rs
+++ b/git-cliff-core/src/remote/github.rs
@@ -22,7 +22,8 @@ pub const START_FETCHING_MSG: &str = "Retrieving data from GitHub...";
 pub const FINISHED_FETCHING_MSG: &str = "Done fetching GitHub data.";
 
 /// Template variables related to this remote.
-pub(crate) const TEMPLATE_VARIABLES: &[&str] = &["github", "commit.github"];
+pub(crate) const TEMPLATE_VARIABLES: &[&str] =
+	&["github", "commit.github", "commit.remote"];
 
 /// Representation of a single commit.
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
diff --git a/git-cliff-core/src/remote/gitlab.rs b/git-cliff-core/src/remote/gitlab.rs
index ba21b33..facf311 100644
--- a/git-cliff-core/src/remote/gitlab.rs
+++ b/git-cliff-core/src/remote/gitlab.rs
@@ -22,7 +22,8 @@ pub const START_FETCHING_MSG: &str = "Retrieving data from GitLab...";
 pub const FINISHED_FETCHING_MSG: &str = "Done fetching GitLab data.";
 
 /// Template variables related to this remote.
-pub(crate) const TEMPLATE_VARIABLES: &[&str] = &["gitlab", "commit.gitlab"];
+pub(crate) const TEMPLATE_VARIABLES: &[&str] =
+	&["gitlab", "commit.gitlab", "commit.remote"];
 
 /// Representation of a single GitLab Project.
 ///
diff --git a/git-cliff-core/src/remote/mod.rs b/git-cliff-core/src/remote/mod.rs
index 626e1d2..77d14e7 100644
--- a/git-cliff-core/src/remote/mod.rs
+++ b/git-cliff-core/src/remote/mod.rs
@@ -15,6 +15,7 @@ pub mod bitbucket;
 pub mod gitea;
 
 use crate::config::Remote;
+use crate::contributor::RemoteContributor;
 use crate::error::{
 	Error,
 	Result,
@@ -48,10 +49,7 @@ use serde::{
 	Serialize,
 };
 use std::fmt::Debug;
-use std::hash::{
-	Hash,
-	Hasher,
-};
+use std::hash::Hash;
 use std::time::Duration;
 
 /// User agent for interacting with the GitHub API.
@@ -114,27 +112,6 @@ pub struct RemoteReleaseMetadata {
 	pub contributors: Vec<RemoteContributor>,
 }
 
-/// Representation of a remote contributor.
-#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)]
-pub struct RemoteContributor {
-	/// Username.
-	pub username:      Option<String>,
-	/// Title of the pull request.
-	pub pr_title:      Option<String>,
-	/// The pull request that the user created.
-	pub pr_number:     Option<i64>,
-	/// Labels of the pull request.
-	pub pr_labels:     Vec<String>,
-	/// Whether if the user contributed for the first time.
-	pub is_first_time: bool,
-}
-
-impl Hash for RemoteContributor {
-	fn hash<H: Hasher>(&self, state: &mut H) {
-		self.username.hash(state);
-	}
-}
-
 /// Creates a HTTP client for the remote.
 fn create_remote_client(
 	remote: &Remote,
@@ -316,6 +293,7 @@ macro_rules! update_release_metadata {
 			///   username.
 			/// - Pull requests: needed for generating the contributor list for the
 			///   release.
+			#[allow(deprecated)]
 			pub fn $fn(
 				&mut self,
 				mut commits: Vec<Box<dyn RemoteCommit>>,
@@ -350,6 +328,7 @@ macro_rules! update_release_metadata {
 								is_first_time: false,
 							});
 						}
+						commit.remote = Some(commit.$remote.clone());
 						false
 					} else {
 						true
diff --git a/git-cliff-core/src/template.rs b/git-cliff-core/src/template.rs
index a18204b..4cbe496 100644
--- a/git-cliff-core/src/template.rs
+++ b/git-cliff-core/src/template.rs
@@ -22,6 +22,9 @@ use tera::{
 /// Wrapper for [`Tera`].
 #[derive(Debug)]
 pub struct Template {
+	/// Template name.
+	name:          String,
+	/// Internal Tera instance.
 	tera:          Tera,
 	/// Template variables.
 	#[cfg_attr(not(feature = "github"), allow(dead_code))]
@@ -30,16 +33,16 @@ pub struct Template {
 
 impl Template {
 	/// Constructs a new instance.
-	pub fn new(mut template: String, trim: bool) -> Result<Self> {
+	pub fn new(name: &str, mut content: String, trim: bool) -> Result<Self> {
 		if trim {
-			template = template
+			content = content
 				.lines()
 				.map(|v| v.trim())
 				.collect::<Vec<&str>>()
 				.join("\n");
 		}
 		let mut tera = Tera::default();
-		if let Err(e) = tera.add_raw_template("template", &template) {
+		if let Err(e) = tera.add_raw_template(name, &content) {
 			return if let Some(error_source) = e.source() {
 				Err(Error::TemplateParseError(error_source.to_string()))
 			} else {
@@ -48,7 +51,8 @@ impl Template {
 		}
 		tera.register_filter("upper_first", Self::upper_first_filter);
 		Ok(Self {
-			variables: Self::get_template_variables(&tera)?,
+			name: name.to_string(),
+			variables: Self::get_template_variables(name, &tera)?,
 			tera,
 		})
 	}
@@ -129,18 +133,17 @@ impl Template {
 	}
 
 	/// Returns the variable names that are used in the template.
-	fn get_template_variables(tera: &Tera) -> Result<Vec<String>> {
+	fn get_template_variables(name: &str, tera: &Tera) -> Result<Vec<String>> {
 		let mut variables = HashSet::new();
-		let ast = &tera.get_template("template")?.ast;
+		let ast = &tera.get_template(name)?.ast;
 		for node in ast {
 			Self::find_identifiers(node, &mut variables);
 		}
-		trace!("Template variables: {variables:?}");
+		trace!("Template variables for {name}: {variables:?}");
 		Ok(variables.into_iter().collect())
 	}
 
 	/// Returns `true` if the template contains one of the given variables.
-	#[cfg(feature = "remote")]
 	pub(crate) fn contains_variable(&self, variables: &[&str]) -> bool {
 		variables
 			.iter()
@@ -160,7 +163,7 @@ impl Template {
 				context.insert(key.clone(), &value);
 			}
 		}
-		match self.tera.render("template", &context) {
+		match self.tera.render(&self.name, &context) {
 			Ok(mut v) => {
 				for postprocessor in postprocessors {
 					postprocessor.replace(&mut v, vec![])?;
@@ -243,7 +246,7 @@ mod test {
 		### {{ commit.group }}
 		- {{ commit.message | upper_first }}
 		{% endfor %}"#;
-		let mut template = Template::new(template.to_string(), false)?;
+		let mut template = Template::new("test", template.to_string(), false)?;
 		let release = get_fake_release_data();
 		assert_eq!(
 			"\n\t\t## 1.0 - 2023\n\t\t\n\t\t### feat\n\t\t- Add xyz\n\t\t\n\t\t### \
@@ -282,7 +285,7 @@ mod test {
 		let template = r#"
 		##  {{ version }}
 		"#;
-		let template = Template::new(template.to_string(), true)?;
+		let template = Template::new("test", template.to_string(), true)?;
 		let release = get_fake_release_data();
 		assert_eq!(
 			"\n##  1.0\n",
@@ -301,7 +304,7 @@ mod test {
 		let template =
 			"{% set hello_variable = 'hello' %}{{ hello_variable | upper_first }}";
 		let release = get_fake_release_data();
-		let template = Template::new(template.to_string(), true)?;
+		let template = Template::new("test", template.to_string(), true)?;
 		let r = template.render(
 			&release,
 			Option::<HashMap<&str, String>>::None.as_ref(),
diff --git a/git-cliff/Cargo.toml b/git-cliff/Cargo.toml
index 374baf2..312700b 100644
--- a/git-cliff/Cargo.toml
+++ b/git-cliff/Cargo.toml
@@ -48,7 +48,7 @@ secrecy.workspace = true
 lazy_static.workspace = true
 dirs.workspace = true
 clap = { version = "4.5.16", features = ["derive", "env", "wrap_help", "cargo"] }
-clap_complete = "4.5.23"
+clap_complete = "4.5.28"
 clap_mangen = "0.2.23"
 shellexpand = "3.1.0"
 update-informer = { version = "1.1.0", optional = true }
@@ -63,7 +63,7 @@ version = "2.5.0" # managed by release.sh
 path = "../git-cliff-core"
 
 [dev-dependencies]
-pretty_assertions = "1.4.0"
+pretty_assertions = "1.4.1"
 
 # metadata for cargo-binstall to get the right artifacts
 [package.metadata.binstall]
diff --git a/git-cliff/src/lib.rs b/git-cliff/src/lib.rs
index 95a36d9..bafb9b9 100644
--- a/git-cliff/src/lib.rs
+++ b/git-cliff/src/lib.rs
@@ -239,7 +239,7 @@ 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.name, commit_id);
 				}
 				None => {
 					tags.insert(commit_id, repository.resolve_tag(tag));
@@ -302,13 +302,6 @@ fn process_repository<'a>(
 			.extend(custom_commits.iter().cloned().map(Commit::from));
 	}
 
-	// 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[0]
 		.previous
@@ -343,6 +336,17 @@ 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()
+			.filter(|release| !release.commits.is_empty())
+			.last()
+		{
+			latest_release.message = Some(message.to_owned());
+		}
+	}
+
 	Ok(releases)
 }
 
@@ -414,7 +418,7 @@ pub fn run(mut args: Opt) -> Result<()> {
 		}
 		EmbeddedConfig::parse()?
 	};
-	if config.changelog.body.is_none() && !args.context {
+	if config.changelog.body.is_none() && !args.context && !args.bumped_version {
 		warn!("Changelog body is not specified, using the default template.");
 		config.changelog.body = EmbeddedConfig::parse()?.changelog.body;
 	}
diff --git a/website/docs/integration/bitbucket.md b/website/docs/integration/bitbucket.md
index 7295bb9..cf283a8 100644
--- a/website/docs/integration/bitbucket.md
+++ b/website/docs/integration/bitbucket.md
@@ -104,7 +104,7 @@ For each commit, Bitbucket related values are added as a nested object (named `b
 
   "...": "<strip>",
 
-  "bitbucket": {
+  "remote": {
     "username": "orhun",
     "pr_title": "some things have changed",
     "pr_number": 420,
@@ -119,8 +119,8 @@ This can be used in the template as follows:
 ```
 {% for commit in commits %}
   * {{ commit.message | split(pat="\n") | first | trim }}\
-    {% if commit.bitbucket.username %} by @{{ commit.bitbucket.username }}{%- endif %}\
-    {% if commit.bitbucket.pr_number %} in #{{ commit.bitbucket.pr_number }}{%- endif %}
+    {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif %}\
+    {% if commit.remote.pr_number %} in #{{ commit.remote.pr_number }}{%- endif %}
 {%- endfor -%}
 ```
 
diff --git a/website/docs/integration/gitea.md b/website/docs/integration/gitea.md
index 8020bd2..1d355c7 100644
--- a/website/docs/integration/gitea.md
+++ b/website/docs/integration/gitea.md
@@ -104,7 +104,7 @@ For each commit, Gitea related values are added as a nested object (named `gitea
 
   "...": "<strip>",
 
-  "gitea": {
+  "remote": {
     "username": "orhun",
     "pr_title": "some things have changed",
     "pr_number": 420,
@@ -119,8 +119,8 @@ This can be used in the template as follows:
 ```
 {% for commit in commits %}
   * {{ commit.message | split(pat="\n") | first | trim }}\
-    {% if commit.gitea.username %} by @{{ commit.gitea.username }}{%- endif %}\
-    {% if commit.gitea.pr_number %} in #{{ commit.gitea.pr_number }}{%- endif %}
+    {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif %}\
+    {% if commit.remote.pr_number %} in #{{ commit.remote.pr_number }}{%- endif %}
 {%- endfor -%}
 ```
 
diff --git a/website/docs/integration/github.md b/website/docs/integration/github.md
index d71e992..5afdec6 100644
--- a/website/docs/integration/github.md
+++ b/website/docs/integration/github.md
@@ -108,7 +108,7 @@ For each commit, GitHub related values are added as a nested object (named `gith
 
   "...": "<strip>",
 
-  "github": {
+  "remote": {
     "username": "orhun",
     "pr_title": "some things have changed",
     "pr_number": 420,
@@ -123,8 +123,8 @@ This can be used in the template as follows:
 ```
 {% for commit in commits %}
   * {{ commit.message | split(pat="\n") | first | trim }}\
-    {% if commit.github.username %} by @{{ commit.github.username }}{%- endif %}\
-    {% if commit.github.pr_number %} in #{{ commit.github.pr_number }}{%- endif %}
+    {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif %}\
+    {% if commit.remote.pr_number %} in #{{ commit.remote.pr_number }}{%- endif %}
 {%- endfor -%}
 ```
 
diff --git a/website/docs/integration/gitlab.md b/website/docs/integration/gitlab.md
index 375f7b7..1db4f64 100644
--- a/website/docs/integration/gitlab.md
+++ b/website/docs/integration/gitlab.md
@@ -106,7 +106,7 @@ For each commit, GitLab related values are added as a nested object (named `gitl
 
   "...": "<strip>",
 
-  "gitlab": {
+  "remote": {
     "username": "orhun",
     "pr_title": "some things have changed",
     "pr_number": 420,
@@ -121,8 +121,8 @@ This can be used in the template as follows:
 ```
 {% for commit in commits %}
   * {{ commit.message | split(pat="\n") | first | trim }}\
-    {% if commit.gitlab.username %} by @{{ commit.gitlab.username }}{%- endif %}\
-    {% if commit.gitlab.pr_number %} in #{{ commit.gitlab.pr_number }}{%- endif %}
+    {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif %}\
+    {% if commit.remote.pr_number %} in #{{ commit.remote.pr_number }}{%- endif %}
 {%- endfor -%}
 ```
 
diff --git a/website/docs/tips-and-tricks.md b/website/docs/tips-and-tricks.md
index 693bcc2..59960d0 100644
--- a/website/docs/tips-and-tricks.md
+++ b/website/docs/tips-and-tricks.md
@@ -65,7 +65,7 @@ commit_parsers = [
 ## Skip commits by GitHub PR label
 
 ```jinja2
-{% if commit.github.pr_labels is containing("skip-release-notes") %}
+{% if commit.remote.pr_labels is containing("skip-release-notes") %}
     {% continue %}
 {% endif %}
 ```
diff --git a/website/package-lock.json b/website/package-lock.json
index 686c79b..d33abbd 100644
--- a/website/package-lock.json
+++ b/website/package-lock.json
@@ -21,7 +21,7 @@
       "devDependencies": {
         "@docusaurus/module-type-aliases": "^3.4.0",
         "@docusaurus/tsconfig": "^3.5.2",
-        "typescript": "^5.5.4"
+        "typescript": "^5.6.2"
       },
       "engines": {
         "node": ">=18.0"
@@ -14092,9 +14092,9 @@
       }
     },
     "node_modules/typescript": {
-      "version": "5.5.4",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
-      "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
+      "version": "5.6.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
+      "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
diff --git a/website/package.json b/website/package.json
index 08f55cd..c895ae9 100644
--- a/website/package.json
+++ b/website/package.json
@@ -28,7 +28,7 @@
   "devDependencies": {
     "@docusaurus/module-type-aliases": "^3.4.0",
     "@docusaurus/tsconfig": "^3.5.2",
-    "typescript": "^5.5.4"
+    "typescript": "^5.6.2"
   },
   "browserslist": {
     "production": [
diff --git a/website/yarn.lock b/website/yarn.lock
index 820c131..f6fa286 100644
--- a/website/yarn.lock
+++ b/website/yarn.lock
@@ -8169,10 +8169,10 @@ typedarray-to-buffer@^3.1.5:
   dependencies:
     is-typedarray "^1.0.0"
 
-typescript@^5.5.4:
-  version "5.5.4"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"
-  integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
+typescript@^5.6.2:
+  version "5.6.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0"
+  integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==
 
 undici-types@~5.26.4:
   version "5.26.5"


Test patch:
diff --git a/.github/fixtures/test-bitbucket-integration/cliff.toml b/.github/fixtures/test-bitbucket-integration/cliff.toml
index b3a6c73..9ad05d1 100644
--- a/.github/fixtures/test-bitbucket-integration/cliff.toml
+++ b/.github/fixtures/test-bitbucket-integration/cliff.toml
@@ -10,8 +10,8 @@ body = """
 {%- if version %} in {{ version }}{%- endif -%}
 {% for commit in commits %}
   * {{ commit.message | split(pat="\n") | first | trim }}\
-    {% if commit.bitbucket.username %} by @{{ commit.bitbucket.username }}{%- endif -%}
-    {% if commit.bitbucket.pr_number %} in #{{ commit.bitbucket.pr_number }}{%- endif %}
+    {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}
+    {% if commit.remote.pr_number %} in #{{ commit.remote.pr_number }}{%- endif %}
 {%- endfor -%}
 
 {% if bitbucket.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
diff --git a/.github/fixtures/test-gitea-integration/cliff.toml b/.github/fixtures/test-gitea-integration/cliff.toml
index 20c9b4e..00c548f 100644
--- a/.github/fixtures/test-gitea-integration/cliff.toml
+++ b/.github/fixtures/test-gitea-integration/cliff.toml
@@ -10,8 +10,8 @@ body = """
 {%- if version %} in {{ version }}{%- endif -%}
 {% for commit in commits %}
   * {{ commit.message | split(pat="\n") | first | trim }}\
-    {% if commit.gitea.username %} by @{{ commit.gitea.username }}{%- endif -%}
-    {% if commit.gitea.pr_number %} in #{{ commit.gitea.pr_number }}{%- endif %}
+    {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}
+    {% if commit.remote.pr_number %} in #{{ commit.remote.pr_number }}{%- endif %}
 {%- endfor -%}
 
 {% if gitea.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
diff --git a/.github/fixtures/test-github-integration/cliff.toml b/.github/fixtures/test-github-integration/cliff.toml
index 17adc95..0bb109c 100644
--- a/.github/fixtures/test-github-integration/cliff.toml
+++ b/.github/fixtures/test-github-integration/cliff.toml
@@ -11,8 +11,8 @@ body = """
 {%- if version %} in {{ version }}{%- endif -%}
 {% for commit in commits %}
   * {{ commit.message | split(pat="\n") | first | trim }}\
-    {% if commit.github.username %} by @{{ commit.github.username }}{%- endif -%}
-    {% if commit.github.pr_number %} in #{{ commit.github.pr_number }}{%- endif %}
+    {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}
+    {% if commit.remote.pr_number %} in #{{ commit.remote.pr_number }}{%- endif %}
 {%- endfor -%}
 
 {% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
diff --git a/.github/fixtures/test-gitlab-integration/cliff.toml b/.github/fixtures/test-gitlab-integration/cliff.toml
index 70d8355..2490a37 100644
--- a/.github/fixtures/test-gitlab-integration/cliff.toml
+++ b/.github/fixtures/test-gitlab-integration/cliff.toml
@@ -11,8 +11,8 @@ body = """
 {%- if version %} in {{ version }}{%- endif -%}
 {% for commit in commits %}
   * {{ commit.message | split(pat="\n") | first | trim }}\
-    {% if commit.gitlab.username %} by @{{ commit.gitlab.username }}{%- endif -%}
-    {% if commit.gitlab.pr_number %} in #{{ commit.gitlab.pr_number }}{%- endif %}
+    {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}
+    {% if commit.remote.pr_number %} in #{{ commit.remote.pr_number }}{%- endif %}
 {%- endfor -%}
 
 {% if gitlab.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
diff --git a/git-cliff-core/tests/integration_test.rs b/git-cliff-core/tests/integration_test.rs
index 6952eac..45add26 100644
--- a/git-cliff-core/tests/integration_test.rs
+++ b/git-cliff-core/tests/integration_test.rs
@@ -261,7 +261,7 @@ fn generate_changelog() -> Result<()> {
 	];
 
 	let out = &mut String::new();
-	let template = Template::new(changelog_config.body.unwrap(), false)?;
+	let template = Template::new("test", changelog_config.body.unwrap(), false)?;
 
 	writeln!(out, "{}", changelog_config.header.unwrap()).unwrap();
 	for release in releases {