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(_id: i64, api_url: &str, remote: &Remote, page: i32) -> String {
52 let commit_page = page + 1;
53 format!(
54 "{}/{}/{}/commits?pagelen={MAX_PAGE_SIZE}&page={commit_page}",
55 api_url, remote.owner, remote.repo
56 )
57 }
58
59 fn buffer_size() -> usize {
60 10
61 }
62
63 fn early_exit(&self) -> bool {
64 self.values.is_empty()
65 }
66}
67
68#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct BitbucketPagination<T> {
73 pub size: Option<i64>,
75 pub page: Option<i64>,
77 pub pagelen: Option<i64>,
80 pub next: Option<String>,
82 pub previous: Option<String>,
84 pub values: Vec<T>,
86}
87
88#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
90pub struct BitbucketCommitAuthor {
91 #[serde(rename = "raw")]
93 pub login: Option<String>,
94}
95
96#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct PullRequestLabel {
100 pub name: String,
102}
103
104#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
106pub struct BitbucketPullRequestMergeCommit {
107 pub hash: String,
109}
110
111#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
113pub struct BitbucketPullRequest {
114 pub id: i64,
116 pub title: Option<String>,
118 pub merge_commit: BitbucketPullRequestMergeCommit,
120 pub author: BitbucketCommitAuthor,
122}
123
124impl RemotePullRequest for BitbucketPullRequest {
125 fn number(&self) -> i64 {
126 self.id
127 }
128
129 fn title(&self) -> Option<String> {
130 self.title.clone()
131 }
132
133 fn labels(&self) -> Vec<String> {
134 vec![]
135 }
136
137 fn merge_commit(&self) -> Option<String> {
138 Some(self.merge_commit.hash.clone())
139 }
140}
141
142impl RemoteEntry for BitbucketPagination<BitbucketPullRequest> {
144 fn url(_id: i64, api_url: &str, remote: &Remote, page: i32) -> String {
145 let pr_page = page + 1;
146 format!(
147 "{}/{}/{}/pullrequests?&pagelen={BITBUCKET_MAX_PAGE_PRS}&\
148 page={pr_page}&state=MERGED",
149 api_url, remote.owner, remote.repo
150 )
151 }
152
153 fn buffer_size() -> usize {
154 5
155 }
156
157 fn early_exit(&self) -> bool {
158 self.values.is_empty()
159 }
160}
161
162#[derive(Debug, Clone)]
164pub struct BitbucketClient {
165 remote: Remote,
167 client: ClientWithMiddleware,
169}
170
171impl TryFrom<Remote> for BitbucketClient {
173 type Error = Error;
174 fn try_from(remote: Remote) -> Result<Self> {
175 Ok(Self {
176 client: remote.create_client("application/json")?,
177 remote,
178 })
179 }
180}
181
182impl RemoteClient for BitbucketClient {
183 const API_URL: &'static str = "https://api.bitbucket.org/2.0/repositories";
184 const API_URL_ENV: &'static str = "BITBUCKET_API_URL";
185
186 fn remote(&self) -> Remote {
187 self.remote.clone()
188 }
189
190 fn client(&self) -> ClientWithMiddleware {
191 self.client.clone()
192 }
193}
194
195impl BitbucketClient {
196 pub async fn get_commits(&self) -> Result<Vec<Box<dyn RemoteCommit>>> {
198 Ok(self
199 .fetch_with_early_exit::<BitbucketPagination<BitbucketCommit>>(0)
200 .await?
201 .into_iter()
202 .flat_map(|v| v.values)
203 .map(|v| Box::new(v) as Box<dyn RemoteCommit>)
204 .collect())
205 }
206
207 pub async fn get_pull_requests(
209 &self,
210 ) -> Result<Vec<Box<dyn RemotePullRequest>>> {
211 Ok(self
212 .fetch_with_early_exit::<BitbucketPagination<BitbucketPullRequest>>(0)
213 .await?
214 .into_iter()
215 .flat_map(|v| v.values)
216 .map(|v| Box::new(v) as Box<dyn RemotePullRequest>)
217 .collect())
218 }
219}
220
221#[cfg(test)]
222mod test {
223 use super::*;
224 use crate::remote::RemoteCommit;
225 use pretty_assertions::assert_eq;
226
227 #[test]
228 fn timestamp() {
229 let remote_commit = BitbucketCommit {
230 hash: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
231 author: Some(BitbucketCommitAuthor {
232 login: Some(String::from("orhun")),
233 }),
234 date: String::from("2021-07-18T15:14:39+03:00"),
235 };
236
237 assert_eq!(Some(1626610479), remote_commit.timestamp());
238 }
239}