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

Issue:
Bitbucket pull request matching does not work
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Description of the bug

When testing the Bitbucket integration the pull request matching does not work. Commits are not being matched to pull requests looking into the API response it looks to be for two reasons:

* The `BitbucketPullRequest` struct does not match the API response
* The `merge_commit` is shortened format in the API response.

<details><summary>Sample API response</summary>
<p>

```json
{
  "values": [
    {
      "comment_count": 0,
      "task_count": 0,
      "type": "pullrequest",
      "id": 1,
      "title": "feat(ci): add git-cliff",
      "description": "",
      "state": "MERGED",
      "merge_commit": {
        "hash": "cfb00c51a374",
        "links": {
          "self": {
            "href": "https://api.bitbucket.org/2.0/repositories/mcwarman-playground/playground/commit/cfb00c51a374"
          },
          "html": {
            "href": "https://bitbucket.org/mcwarman-playground/playground/commits/cfb00c51a374"
          }
        },
        "type": "commit"
      }
    }
    //...
  ],
  "pagelen": 10,
  "size": 1,
  "page": 1
}
```

</p>
</details> 

### Steps To Reproduce

```bash
git clone git@bitbucket.org:mcwarman-playground/playground.git
git cliff --current --context --bitbucket-repo "mcwarman-playground/playground" | jq .
```

vs API

```bash
curl https://api.bitbucket.org/2.0/repositories/mcwarman-playground/playground/pullrequests?state=MERGED | jq .
```

### Expected behavior

Pull requests should be matched on merge commit and update context with PR details.

### Screenshots / Logs

_No response_

### Software information

- Operating system: WSL - Debian GNU/Linux 12 (bookworm)
- Rust version: N/A
- Project version: 2.6.1


### Additional context

Rust is not a language I'm to familiar with but I tested this locally and it works. Happy to raise a PR.

```diff
diff --git a/git-cliff-core/src/remote/bitbucket.rs b/git-cliff-core/src/remote/bitbucket.rs
index f45a47c..d0f4cd2 100644
--- a/git-cliff-core/src/remote/bitbucket.rs
+++ b/git-cliff-core/src/remote/bitbucket.rs
@@ -117,7 +117,7 @@ pub struct BitbucketPullRequest {
        /// Pull request title.
        pub title:            Option<String>,
        /// Bitbucket Pull Request Merge Commit
-       pub merge_commit_sha: BitbucketPullRequestMergeCommit,
+       pub merge_commit: BitbucketPullRequestMergeCommit,
        /// Author of Pull Request
        pub author:           BitbucketCommitAuthor,
 }
@@ -136,7 +136,7 @@ impl RemotePullRequest for BitbucketPullRequest {
        }

        fn merge_commit(&self) -> Option<String> {
-               Some(self.merge_commit_sha.hash.clone())
+               Some(self.merge_commit.hash.clone())
        }
 }

diff --git a/git-cliff-core/src/remote/mod.rs b/git-cliff-core/src/remote/mod.rs
index 77d14e7..8b12ed1 100644
--- a/git-cliff-core/src/remote/mod.rs
+++ b/git-cliff-core/src/remote/mod.rs
@@ -306,9 +306,12 @@ macro_rules! update_release_metadata {
                                        if let Some(commit) =
                                                self.commits.iter_mut().find(|commit| commit.id == v.id())
                                        {
-                                               let pull_request = pull_requests
-                                                       .iter()
-                                                       .find(|pr| pr.merge_commit() == Some(v.id().clone()));
+                                               let sha_short =
+                                                       Some(v.id().clone().chars().take(12).collect());
+                                               let pull_request = pull_requests.iter().find(|pr| {
+                                                       pr.merge_commit() == Some(v.id().clone()) ||
+                                                               pr.merge_commit() == sha_short
+                                               });
                                                commit.$remote.username = v.username();
                                                commit.$remote.pr_number = pull_request.map(|v| v.number());
                                                commit.$remote.pr_title =
```

<details><summary>Details</summary>
<p>

```bash
~/code/src/github.com/orhun/git-cliff/target/debug/git-cliff --current --context | jq .
```

```json
[
  {
    //...
    "bitbucket": {
      "contributors": [
        {
          "username": "...",
          "pr_title": "feat(ci): add git-cliff",
          "pr_number": 1,
          "pr_labels": [],
          "is_first_time": true
        }
      ]
    }
  }
]
```

</p>
</details> 


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

Hello, thanks for reporting! 🐻 

This sounds reasonable - please go ahead and raise a PR! Also, see #763 #762 in case they are related in any way :)

Fix patch:
diff --git a/git-cliff-core/src/release.rs b/git-cliff-core/src/release.rs
index 3e4dd6f7be..fec293f51f 100644
--- a/git-cliff-core/src/release.rs
+++ b/git-cliff-core/src/release.rs
@@ -1424,4 +1424,291 @@ mod test {
 
 		Ok(())
 	}
+
+	#[cfg(feature = "bitbucket")]
+	#[test]
+	fn update_bitbucket_metadata() -> Result<()> {
+		use crate::remote::bitbucket::{
+			BitbucketCommit,
+			BitbucketCommitAuthor,
+			BitbucketPullRequest,
+			BitbucketPullRequestMergeCommit,
+		};
+
+		let mut release = Release {
+			version: None,
+			message: None,
+			extra: None,
+			commits: vec![
+				Commit::from(String::from(
+					"1d244937ee6ceb8e0314a4a201ba93a7a61f2071 add bitbucket \
+					 integration",
+				)),
+				Commit::from(String::from(
+					"21f6aa587fcb772de13f2fde0e92697c51f84162 fix bitbucket \
+					 integration",
+				)),
+				Commit::from(String::from(
+					"35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973 update metadata",
+				)),
+				Commit::from(String::from(
+					"4d3ffe4753b923f4d7807c490e650e6624a12074 do some stuff",
+				)),
+				Commit::from(String::from(
+					"5a55e92e5a62dc5bf9872ffb2566959fad98bd05 alright",
+				)),
+				Commit::from(String::from(
+					"6c34967147560ea09658776d4901709139b4ad66 should be fine",
+				)),
+			],
+			commit_id: None,
+			timestamp: 0,
+			previous: Some(Box::new(Release {
+				version: Some(String::from("1.0.0")),
+				..Default::default()
+			})),
+			repository: Some(String::from("/root/repo")),
+			#[cfg(feature = "github")]
+			github: RemoteReleaseMetadata {
+				contributors: vec![],
+			},
+			#[cfg(feature = "gitlab")]
+			gitlab: RemoteReleaseMetadata {
+				contributors: vec![],
+			},
+			#[cfg(feature = "gitea")]
+			gitea: RemoteReleaseMetadata {
+				contributors: vec![],
+			},
+			#[cfg(feature = "bitbucket")]
+			bitbucket: RemoteReleaseMetadata {
+				contributors: vec![],
+			},
+		};
+		release.update_bitbucket_metadata(
+			vec![
+				BitbucketCommit {
+					hash:   String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
+					author: Some(BitbucketCommitAuthor {
+						login: Some(String::from("orhun")),
+					}),
+				},
+				BitbucketCommit {
+					hash:   String::from("21f6aa587fcb772de13f2fde0e92697c51f84162"),
+					author: Some(BitbucketCommitAuthor {
+						login: Some(String::from("orhun")),
+					}),
+				},
+				BitbucketCommit {
+					hash:   String::from("35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973"),
+					author: Some(BitbucketCommitAuthor {
+						login: Some(String::from("nuhro")),
+					}),
+				},
+				BitbucketCommit {
+					hash:   String::from("4d3ffe4753b923f4d7807c490e650e6624a12074"),
+					author: Some(BitbucketCommitAuthor {
+						login: Some(String::from("awesome_contributor")),
+					}),
+				},
+				BitbucketCommit {
+					hash:   String::from("5a55e92e5a62dc5bf9872ffb2566959fad98bd05"),
+					author: Some(BitbucketCommitAuthor {
+						login: Some(String::from("orhun")),
+					}),
+				},
+				BitbucketCommit {
+					hash:   String::from("6c34967147560ea09658776d4901709139b4ad66"),
+					author: Some(BitbucketCommitAuthor {
+						login: Some(String::from("someone")),
+					}),
+				},
+				BitbucketCommit {
+					hash:   String::from("0c34967147560e809658776d4901709139b4ad68"),
+					author: Some(BitbucketCommitAuthor {
+						login: Some(String::from("idk")),
+					}),
+				},
+				BitbucketCommit {
+					hash:   String::from("kk34967147560e809658776d4901709139b4ad68"),
+					author: Some(BitbucketCommitAuthor {
+						login: Some(String::from("orhun")),
+					}),
+				},
+			]
+			.into_iter()
+			.map(|v| Box::new(v) as Box<dyn RemoteCommit>)
+			.collect(),
+			vec![Box::new(BitbucketPullRequest {
+				id:           1,
+				title:        Some(String::from("1")),
+				author:       BitbucketCommitAuthor {
+					login: Some(String::from("42")),
+				},
+				merge_commit: BitbucketPullRequestMergeCommit {
+					// Bitbucket merge commits returned in short format
+					hash: String::from("1d244937ee6c"),
+				},
+			})],
+		)?;
+		#[allow(deprecated)]
+		let expected_commits = vec![
+			Commit {
+				id: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
+				message: String::from("add bitbucket integration"),
+				bitbucket: RemoteContributor {
+					username:      Some(String::from("orhun")),
+					pr_title:      Some(String::from("1")),
+					pr_number:     Some(1),
+					pr_labels:     vec![],
+					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![],
+					is_first_time: false,
+				}),
+				..Default::default()
+			},
+			Commit {
+				id: String::from("21f6aa587fcb772de13f2fde0e92697c51f84162"),
+				message: String::from("fix bitbucket integration"),
+				bitbucket: RemoteContributor {
+					username:      Some(String::from("orhun")),
+					pr_title:      None,
+					pr_number:     None,
+					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 {
+				id: String::from("35d8c6b6329ecbcf131d7df02f93c3bbc5ba5973"),
+				message: String::from("update metadata"),
+				bitbucket: RemoteContributor {
+					username:      Some(String::from("nuhro")),
+					pr_title:      None,
+					pr_number:     None,
+					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 {
+				id: String::from("4d3ffe4753b923f4d7807c490e650e6624a12074"),
+				message: String::from("do some stuff"),
+				bitbucket: RemoteContributor {
+					username:      Some(String::from("awesome_contributor")),
+					pr_title:      None,
+					pr_number:     None,
+					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 {
+				id: String::from("5a55e92e5a62dc5bf9872ffb2566959fad98bd05"),
+				message: String::from("alright"),
+				bitbucket: RemoteContributor {
+					username:      Some(String::from("orhun")),
+					pr_title:      None,
+					pr_number:     None,
+					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 {
+				id: String::from("6c34967147560ea09658776d4901709139b4ad66"),
+				message: String::from("should be fine"),
+				bitbucket: RemoteContributor {
+					username:      Some(String::from("someone")),
+					pr_title:      None,
+					pr_number:     None,
+					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()
+			},
+		];
+		assert_eq!(expected_commits, release.commits);
+
+		release
+			.bitbucket
+			.contributors
+			.sort_by(|a, b| a.pr_number.cmp(&b.pr_number));
+
+		let expected_metadata = RemoteReleaseMetadata {
+			contributors: vec![
+				RemoteContributor {
+					username:      Some(String::from("nuhro")),
+					pr_title:      None,
+					pr_number:     None,
+					pr_labels:     vec![],
+					is_first_time: true,
+				},
+				RemoteContributor {
+					username:      Some(String::from("awesome_contributor")),
+					pr_title:      None,
+					pr_number:     None,
+					pr_labels:     vec![],
+					is_first_time: true,
+				},
+				RemoteContributor {
+					username:      Some(String::from("someone")),
+					pr_title:      None,
+					pr_number:     None,
+					pr_labels:     vec![],
+					is_first_time: true,
+				},
+				RemoteContributor {
+					username:      Some(String::from("orhun")),
+					pr_title:      Some(String::from("1")),
+					pr_number:     Some(1),
+					pr_labels:     vec![],
+					is_first_time: false,
+				},
+			],
+		};
+		assert_eq!(expected_metadata, release.bitbucket);
+
+		Ok(())
+	}
 }
diff --git a/git-cliff-core/src/remote/bitbucket.rs b/git-cliff-core/src/remote/bitbucket.rs
index f45a47cfe7..a3e3603d73 100644
--- a/git-cliff-core/src/remote/bitbucket.rs
+++ b/git-cliff-core/src/remote/bitbucket.rs
@@ -113,13 +113,13 @@ pub struct BitbucketPullRequestMergeCommit {
 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct BitbucketPullRequest {
 	/// Pull request number.
-	pub id:               i64,
+	pub id:           i64,
 	/// Pull request title.
-	pub title:            Option<String>,
+	pub title:        Option<String>,
 	/// Bitbucket Pull Request Merge Commit
-	pub merge_commit_sha: BitbucketPullRequestMergeCommit,
+	pub merge_commit: BitbucketPullRequestMergeCommit,
 	/// Author of Pull Request
-	pub author:           BitbucketCommitAuthor,
+	pub author:       BitbucketCommitAuthor,
 }
 
 impl RemotePullRequest for BitbucketPullRequest {
@@ -136,7 +136,7 @@ impl RemotePullRequest for BitbucketPullRequest {
 	}
 
 	fn merge_commit(&self) -> Option<String> {
-		Some(self.merge_commit_sha.hash.clone())
+		Some(self.merge_commit.hash.clone())
 	}
 }
 
diff --git a/git-cliff-core/src/remote/mod.rs b/git-cliff-core/src/remote/mod.rs
index 77d14e72b5..a79137b291 100644
--- a/git-cliff-core/src/remote/mod.rs
+++ b/git-cliff-core/src/remote/mod.rs
@@ -49,7 +49,6 @@ use serde::{
 	Serialize,
 };
 use std::fmt::Debug;
-use std::hash::Hash;
 use std::time::Duration;
 
 /// User agent for interacting with the GitHub API.
@@ -306,9 +305,12 @@ macro_rules! update_release_metadata {
 					if let Some(commit) =
 						self.commits.iter_mut().find(|commit| commit.id == v.id())
 					{
-						let pull_request = pull_requests
-							.iter()
-							.find(|pr| pr.merge_commit() == Some(v.id().clone()));
+						let sha_short =
+							Some(v.id().clone().chars().take(12).collect());
+						let pull_request = pull_requests.iter().find(|pr| {
+							pr.merge_commit() == Some(v.id().clone()) ||
+								pr.merge_commit() == sha_short
+						});
 						commit.$remote.username = v.username();
 						commit.$remote.pr_number = pull_request.map(|v| v.number());
 						commit.$remote.pr_title =


Test patch:
diff --git a/.github/fixtures/test-bitbucket-integration/cliff.toml b/.github/fixtures/test-bitbucket-integration/cliff.toml
index 9ad05d1aff..10a7ec2ad6 100644
--- a/.github/fixtures/test-bitbucket-integration/cliff.toml
+++ b/.github/fixtures/test-bitbucket-integration/cliff.toml
@@ -1,17 +1,23 @@
 [remote.bitbucket]
-owner = "orhunp"
+owner = "mcwarman-playground"
 repo = "git-cliff-readme-example"
 
 [changelog]
 # template for the changelog body
 # https://keats.github.io/tera/docs/#introduction
 body = """
+{%- macro remote_url() -%}
+  https://bitbucket.org/{{ remote.bitbucket.owner }}/{{ remote.bitbucket.repo }}
+{%- endmacro -%}
+
 ## What's Changed
 {%- if version %} in {{ version }}{%- endif -%}
 {% for commit in commits %}
   * {{ commit.message | split(pat="\n") | first | trim }}\
     {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}
-    {% if commit.remote.pr_number %} in #{{ commit.remote.pr_number }}{%- endif %}
+    {% if commit.remote.pr_number %} in \
+      [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull-requests/{{ commit.remote.pr_number }})\
+    {%- endif %}
 {%- endfor -%}
 
 {% if bitbucket.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
@@ -21,7 +27,7 @@ body = """
 {% for contributor in bitbucket.contributors | filter(attribute="is_first_time", value=true) %}
   * @{{ contributor.username }} made their first contribution
     {%- if contributor.pr_number %} in \
-      [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
+      [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull-requests/{{ contributor.pr_number }}) \
     {%- endif %}
 {%- endfor -%}
 {% raw %}\n\n{% endraw -%}
diff --git a/.github/fixtures/test-bitbucket-integration/commit.sh b/.github/fixtures/test-bitbucket-integration/commit.sh
index 5bb810071b..7bed941a15 100755
--- a/.github/fixtures/test-bitbucket-integration/commit.sh
+++ b/.github/fixtures/test-bitbucket-integration/commit.sh
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 set -e
 
-git remote add origin https://bitbucket.org/orhunp/git-cliff-readme-example
+git remote add origin https://bitbucket.org/mcwarman-playground/git-cliff-readme-example
 git pull origin master
 git fetch --tags
diff --git a/.github/fixtures/test-bitbucket-integration/expected.md b/.github/fixtures/test-bitbucket-integration/expected.md
index 0c72638ad1..84fbdee247 100644
--- a/.github/fixtures/test-bitbucket-integration/expected.md
+++ b/.github/fixtures/test-bitbucket-integration/expected.md
@@ -1,6 +1,7 @@
 ## What's Changed
 * feat(config): support multiple file formats by @orhun <orhun@archlinux.org>
 * feat(cache): use cache while fetching pages by @orhun <orhun@archlinux.org>
+* feat(ci): add pipeline config by @mcwarman <7236500+mcwarman@users.noreply.github.com> in [#1](https://bitbucket.org/mcwarman-playground/git-cliff-readme-example/pull-requests/1)
 
 ## What's Changed in v1.0.1
 * refactor(parser): expose string functions by @orhun <orhun@archlinux.org>