Repository: orhun/git-cliff (rust)

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.

Pull request description:
feat(changelog): support generating changelog for different branches
## Description

close #750 

## Motivation and Context

When a project needs to maintain diverged branches and create changelog for each branch this change is necessary. 

for example a project's v3 is a complete rewrite and the changes onward have nothing to do which v2 branch. While v2 is in maintenance mode that the fixes are not relevant to v3 anymore.

when creating changelog for v3, it is necessary not to include the changes in the v2 which are not even included in the current branch

## How Has This Been Tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

## Screenshots / Logs (if applicable)

## Types of Changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] 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)
- [ ] Other <!--- (provide information) -->

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] My code follows the code style of this project.
- [ ] 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).
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.

<!--- Thank you for contributing to git-cliff! ⛰️  -->


Patch:
diff --git a/git-cliff-core/src/changelog.rs b/git-cliff-core/src/changelog.rs
index 06b77a2..4d4e58e 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 5949d33..2677f31 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 5696209..9390de3 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 2be4fbd..c45c174 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 ea8dcb2..7631c30 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 6b3f657..38b8b24 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 e7b7800..d5d2e12 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 58141a9..6952eac 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![