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