Repository: orhun/git-cliff (language: python)

Issue:
--unreleased does not behave as expected with tags on side branches
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Description of the bug

If I have a project where tags are also created on side branches and not only on the master branch, git cliff seems to get confused, if the `--unreleased` flag is used.
I would expect to see only commit messages parsed that do not belong to a commit, but this does not seem to be the case.
If on a side branch a new tag was created to fix a older version also already tagged commits form the master branch start to appear in the change log. It seams the last commit in time on any branch is used to determine the unreleased commits.

### Steps To Reproduce

1. Create tag `2.0.0` on branch `master`
2. Create tag `1.0.1` on branch e.g. `release/1`
3. Create tag `2.1.0` on head of  `master`
4. Add new commit on `master`
5. Create a change log on `master` with `git cliff -u`

### Expected behavior

It is expected that only the newly created commit on `master` appears in the change log, created by git cliff.

But what I see instead, that also the commits of the tag 2.0.0 are included.


### Screenshots / Logs

![image001](https://github.com/orhun/git-cliff/assets/5878894/85865f23-d915-4453-8636-b1c2f40a651e)

### Software information

- Operating system: WSL - Ubuntu 22.04
- Rust version: 
- Project version: 2.4.0


### Additional context

As soon as a new tag is created on the `master` branch, the behavior is again as expected.


Discussion / hints:
Thanks for opening your first issue at **git-cliff**! Be sure to follow the issue template! ⛰️

also observed this one. but if the changelog is generated from scratch without any flags the result is correct. 

Fix patch:
diff --git a/git-cliff-core/src/changelog.rs b/git-cliff-core/src/changelog.rs
index 06b77a252c..4d4e58e7df 100644
--- a/git-cliff-core/src/changelog.rs
+++ b/git-cliff-core/src/changelog.rs
@@ -805,6 +805,7 @@ mod test {
 				skip_tags:                Regex::new("v3.*").ok(),
 				ignore_tags:              None,
 				count_tags:               None,
+				use_branch_tags:          Some(false),
 				topo_order:               Some(false),
 				sort_commits:             Some(String::from("oldest")),
 				link_parsers:             None,
diff --git a/git-cliff-core/src/config.rs b/git-cliff-core/src/config.rs
index 5949d332e9..2677f31bf9 100644
--- a/git-cliff-core/src/config.rs
+++ b/git-cliff-core/src/config.rs
@@ -115,6 +115,8 @@ pub struct GitConfig {
 	/// Regex to count matched tags.
 	#[serde(with = "serde_regex", default)]
 	pub count_tags:               Option<Regex>,
+	/// Include only the tags that belong to the current branch.
+	pub use_branch_tags:          Option<bool>,
 	/// Whether to sort tags topologically.
 	pub topo_order:               Option<bool>,
 	/// Sorting of the commits inside sections.
diff --git a/git-cliff-core/src/repo.rs b/git-cliff-core/src/repo.rs
index 569620900f..9390de3ea8 100644
--- a/git-cliff-core/src/repo.rs
+++ b/git-cliff-core/src/repo.rs
@@ -310,6 +310,23 @@ impl Repository {
 		None
 	}
 
+	/// Decide whether to include tag
+	///
+	/// `head_commit` is the `latest` commit to generate changelog. It can be a
+	/// branch head or a detached head. `tag_commit` is a tagged commit. If the
+	/// commit is in the descendant graph of the head_commit or is the
+	/// head_commit itself, Changelog should include the tag.
+	fn should_include_tag(
+		&self,
+		head_commit: &Commit,
+		tag_commit: &Commit,
+	) -> Result<bool> {
+		Ok(self
+			.inner
+			.graph_descendant_of(head_commit.id(), tag_commit.id())? ||
+			head_commit.id() == tag_commit.id())
+	}
+
 	/// Parses and returns a commit-tag map.
 	///
 	/// It collects lightweight and annotated tags.
@@ -317,9 +334,11 @@ impl Repository {
 		&self,
 		pattern: &Option<Regex>,
 		topo_order: bool,
+		use_branch_tags: bool,
 	) -> Result<IndexMap<String, Tag>> {
 		let mut tags: Vec<(Commit, Tag)> = Vec::new();
 		let tag_names = self.inner.tag_names(None)?;
+		let head_commit = self.inner.head()?.peel_to_commit()?;
 		for name in tag_names
 			.iter()
 			.flatten()
@@ -330,6 +349,12 @@ impl Repository {
 		{
 			let obj = self.inner.revparse_single(&name)?;
 			if let Ok(commit) = obj.clone().into_commit() {
+				if use_branch_tags &&
+					!self.should_include_tag(&head_commit, &commit)?
+				{
+					continue;
+				}
+
 				tags.push((commit, Tag {
 					name,
 					message: None,
@@ -340,6 +365,11 @@ impl Repository {
 					.ok()
 					.and_then(|target| target.into_commit().ok())
 				{
+					if use_branch_tags &&
+						!self.should_include_tag(&head_commit, &commit)?
+					{
+						continue;
+					}
 					tags.push((commit, Tag {
 						name:    tag.name().map(String::from).unwrap_or(name),
 						message: tag.message().map(|msg| {
@@ -468,7 +498,7 @@ mod test {
 	#[test]
 	fn get_latest_tag() -> Result<()> {
 		let repository = get_repository()?;
-		let tags = repository.tags(&None, false)?;
+		let tags = repository.tags(&None, false, false)?;
 		assert_eq!(get_last_tag()?, tags.last().expect("no tags found").1.name);
 		Ok(())
 	}
@@ -476,7 +506,7 @@ mod test {
 	#[test]
 	fn git_tags() -> Result<()> {
 		let repository = get_repository()?;
-		let tags = repository.tags(&None, true)?;
+		let tags = repository.tags(&None, true, false)?;
 		assert_eq!(
 			tags.get("2b8b4d3535f29231e05c3572e919634b9af907b6")
 				.expect(
@@ -500,6 +530,7 @@ mod test {
 					.expect("the regex is not valid"),
 			),
 			true,
+			false,
 		)?;
 		assert_eq!(
 			tags.get("2b8b4d3535f29231e05c3572e919634b9af907b6")
diff --git a/git-cliff/src/args.rs b/git-cliff/src/args.rs
index 2be4fbd9c3..c45c174ab4 100644
--- a/git-cliff/src/args.rs
+++ b/git-cliff/src/args.rs
@@ -229,6 +229,9 @@ pub struct Opt {
 	/// Sorts the tags topologically.
 	#[arg(long, help_heading = Some("FLAGS"))]
 	pub topo_order:       bool,
+	/// Include only the tags that belong to the current branch.
+	#[arg(long, help_heading = Some("FLAGS"))]
+	pub use_branch_tags:  bool,
 	/// Disables the external command execution.
 	#[arg(long, help_heading = Some("FLAGS"))]
 	pub no_exec:          bool,
diff --git a/git-cliff/src/lib.rs b/git-cliff/src/lib.rs
index ea8dcb2299..7631c30a43 100644
--- a/git-cliff/src/lib.rs
+++ b/git-cliff/src/lib.rs
@@ -83,7 +83,11 @@ fn process_repository<'a>(
 	config: &mut Config,
 	args: &Opt,
 ) -> Result<Vec<Release<'a>>> {
-	let mut tags = repository.tags(&config.git.tag_pattern, args.topo_order)?;
+	let mut tags = repository.tags(
+		&config.git.tag_pattern,
+		args.topo_order,
+		args.use_branch_tags,
+	)?;
 	let skip_regex = config.git.skip_tags.as_ref();
 	let ignore_regex = config.git.ignore_tags.as_ref();
 	let count_tags = config.git.count_tags.as_ref();
diff --git a/website/docs/usage/args.md b/website/docs/usage/args.md
index 6b3f6570a4..38b8b246f4 100644
--- a/website/docs/usage/args.md
+++ b/website/docs/usage/args.md
@@ -11,16 +11,17 @@ git-cliff [FLAGS] [OPTIONS] [--] [RANGE]
 ## Flags
 
 ```
--h, --help            Prints help information
--V, --version         Prints version information
--v, --verbose...      Increases the logging verbosity
-    --bumped-version  Prints bumped version for unreleased changes
--l, --latest          Processes the commits starting from the latest tag
-    --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
-    --no-exec         Disables the external command execution
--x, --context         Prints changelog context as JSON
+-h, --help             Prints help information
+-V, --version          Prints version information
+-v, --verbose...       Increases the logging verbosity
+    --bumped-version   Prints bumped version for unreleased changes
+-l, --latest           Processes the commits starting from the latest tag
+    --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
+    --use-branch-tags  Include only the tags that belong to the current branch
+    --no-exec          Disables the external command execution
+-x, --context          Prints changelog context as JSON
 ```
 
 ## Options
diff --git a/website/docs/usage/examples.md b/website/docs/usage/examples.md
index e7b780092c..d5d2e1238e 100644
--- a/website/docs/usage/examples.md
+++ b/website/docs/usage/examples.md
@@ -45,6 +45,12 @@ git cliff v2.2.1..
 git cliff v0.1.0..HEAD
 ```
 
+Only include the tags from the current branch:
+
+```bash
+git cliff --use-branch-tags
+```
+
 Sort the commits inside sections:
 
 ```bash


Test patch:
diff --git a/git-cliff-core/tests/integration_test.rs b/git-cliff-core/tests/integration_test.rs
index 58141a93b3..6952eac5a0 100644
--- a/git-cliff-core/tests/integration_test.rs
+++ b/git-cliff-core/tests/integration_test.rs
@@ -119,6 +119,7 @@ fn generate_changelog() -> Result<()> {
 		skip_tags:                None,
 		ignore_tags:              None,
 		count_tags:               None,
+		use_branch_tags:          None,
 		topo_order:               None,
 		sort_commits:             None,
 		link_parsers:             Some(vec![