git_cliff_core/remote/
bitbucket.rs1use reqwest_middleware::ClientWithMiddleware;
2use serde::{Deserialize, Serialize};
3
4use super::*;
5use crate::config::Remote;
6use crate::error::*;
7
8pub const START_FETCHING_MSG: &str = "Retrieving data from Bitbucket...";
10
11pub const FINISHED_FETCHING_MSG: &str = "Done fetching Bitbucket data.";
13
14pub(crate) const TEMPLATE_VARIABLES: &[&str] = &["bitbucket", "commit.bitbucket", "commit.remote"];
16
17pub(crate) const BITBUCKET_MAX_PAGE_PRS: usize = 50;
19
20#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct BitbucketCommit {
23 pub hash: String,
25 pub date: String,
27 pub author: Option<BitbucketCommitAuthor>,
29}
30
31impl RemoteCommit for BitbucketCommit {
32 fn id(&self) -> String {
33 self.hash.clone()
34 }
35
36 fn username(&self) -> Option<String> {
37 self.author.clone().and_then(|v| v.login)
38 }
39
40 fn timestamp(&self) -> Option<i64> {
41 Some(self.convert_to_unix_timestamp(self.date.clone().as_str()))
42 }
43}
44
45impl RemoteEntry for BitbucketPagination<BitbucketCommit> {
47 fn url(_id: i64, api_url: &str, remote: &Remote, ref_name: Option<&str>, page: i32) -> String {
48 let commit_page = page + 1;
49 let mut url = format!(
50 "{}/{}/{}/commits?pagelen={MAX_PAGE_SIZE}&page={commit_page}",
51 api_url, remote.owner, remote.repo
52 );
53
54 if let Some(ref_name) = ref_name {
55 url.push_str(&format!("&include={}", ref_name));
56 }
57
58 url
59 }
60
61 fn buffer_size() -> usize {
62 10
63 }
64
65 fn early_exit(&self) -> bool {
66 self.values.is_empty()
67 }
68}
69
70#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
74pub struct BitbucketPagination<T> {
75 pub size: Option<i64>,
77 pub page: Option<i64>,
79 pub pagelen: Option<i64>,
82 pub next: Option<String>,
84 pub previous: Option<String>,
86 pub values: Vec<T>,
88}
89
90#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
92pub struct BitbucketCommitAuthor {
93 #[serde(rename = "raw")]
95 pub login: Option<String>,
96}
97
98#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub struct PullRequestLabel {
102 pub name: String,
104}
105
106#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
108pub struct BitbucketPullRequestMergeCommit {
109 pub hash: String,
111}
112
113#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub struct BitbucketPullRequest {
116 pub id: i64,
118 pub title: Option<String>,
120 pub merge_commit: BitbucketPullRequestMergeCommit,
122 pub author: BitbucketCommitAuthor,
124}
125
126impl RemotePullRequest for BitbucketPullRequest {
127 fn number(&self) -> i64 {
128 self.id
129 }
130
131 fn title(&self) -> Option<String> {
132 self.title.clone()
133 }
134
135 fn labels(&self) -> Vec<String> {
136 vec![]
137 }
138
139 fn merge_commit(&self) -> Option<String> {
140 Some(self.merge_commit.hash.clone())
141 }
142}
143
144impl RemoteEntry for BitbucketPagination<BitbucketPullRequest> {
146 fn url(_id: i64, api_url: &str, remote: &Remote, _ref_name: Option<&str>, page: i32) -> String {
147 let pr_page = page + 1;
148 format!(
149 "{}/{}/{}/pullrequests?&pagelen={BITBUCKET_MAX_PAGE_PRS}&page={pr_page}&state=MERGED",
150 api_url, remote.owner, remote.repo
151 )
152 }
153
154 fn buffer_size() -> usize {
155 5
156 }
157
158 fn early_exit(&self) -> bool {
159 self.values.is_empty()
160 }
161}
162
163#[derive(Debug, Clone)]
165pub struct BitbucketClient {
166 remote: Remote,
168 client: ClientWithMiddleware,
170}
171
172impl TryFrom<Remote> for BitbucketClient {
174 type Error = Error;
175 fn try_from(remote: Remote) -> Result<Self> {
176 Ok(Self {
177 client: remote.create_client("application/json")?,
178 remote,
179 })
180 }
181}
182
183impl RemoteClient for BitbucketClient {
184 const API_URL: &'static str = "https://api.bitbucket.org/2.0/repositories";
185 const API_URL_ENV: &'static str = "BITBUCKET_API_URL";
186
187 fn remote(&self) -> Remote {
188 self.remote.clone()
189 }
190
191 fn client(&self) -> ClientWithMiddleware {
192 self.client.clone()
193 }
194}
195
196impl BitbucketClient {
197 pub async fn get_commits(&self, ref_name: Option<&str>) -> Result<Vec<Box<dyn RemoteCommit>>> {
199 Ok(self
200 .fetch_with_early_exit::<BitbucketPagination<BitbucketCommit>>(0, ref_name)
201 .await?
202 .into_iter()
203 .flat_map(|v| v.values)
204 .map(|v| Box::new(v) as Box<dyn RemoteCommit>)
205 .collect())
206 }
207
208 pub async fn get_pull_requests(
210 &self,
211 ref_name: Option<&str>,
212 ) -> Result<Vec<Box<dyn RemotePullRequest>>> {
213 Ok(self
214 .fetch_with_early_exit::<BitbucketPagination<BitbucketPullRequest>>(0, ref_name)
215 .await?
216 .into_iter()
217 .flat_map(|v| v.values)
218 .map(|v| Box::new(v) as Box<dyn RemotePullRequest>)
219 .collect())
220 }
221}
222
223#[cfg(test)]
224mod test {
225 use pretty_assertions::assert_eq;
226
227 use super::*;
228 use crate::remote::RemoteCommit;
229
230 #[test]
231 fn timestamp() {
232 let remote_commit = BitbucketCommit {
233 hash: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
234 author: Some(BitbucketCommitAuthor {
235 login: Some(String::from("orhun")),
236 }),
237 date: String::from("2021-07-18T15:14:39+03:00"),
238 };
239
240 assert_eq!(Some(1626610479), remote_commit.timestamp());
241 }
242}