git_cliff_core/remote/
github.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 GitHub...";
13
14pub const FINISHED_FETCHING_MSG: &str = "Done fetching GitHub data.";
16
17pub(crate) const TEMPLATE_VARIABLES: &[&str] =
19 &["github", "commit.github", "commit.remote"];
20
21#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub struct GitHubCommit {
24 pub sha: String,
26 pub author: Option<GitHubCommitAuthor>,
28 pub commit: Option<GitHubCommitDetails>,
30}
31
32#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub struct GitHubCommitDetails {
35 pub author: GitHubCommitDetailsAuthor,
37}
38
39#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
41pub struct GitHubCommitDetailsAuthor {
42 pub date: String,
44}
45
46impl RemoteCommit for GitHubCommit {
47 fn id(&self) -> String {
48 self.sha.clone()
49 }
50
51 fn username(&self) -> Option<String> {
52 self.author.clone().and_then(|v| v.login)
53 }
54
55 fn timestamp(&self) -> Option<i64> {
56 self.commit
57 .clone()
58 .map(|f| self.convert_to_unix_timestamp(f.author.date.clone().as_str()))
59 }
60}
61
62impl RemoteEntry for GitHubCommit {
63 fn url(
64 _id: i64,
65 api_url: &str,
66 remote: &Remote,
67 ref_name: Option<&str>,
68 page: i32,
69 ) -> String {
70 let mut url = format!(
71 "{}/repos/{}/{}/commits?per_page={MAX_PAGE_SIZE}&page={page}",
72 api_url, remote.owner, remote.repo
73 );
74
75 if let Some(ref_name) = ref_name {
76 url.push_str(&format!("&sha={}", ref_name));
77 }
78
79 url
80 }
81
82 fn buffer_size() -> usize {
83 10
84 }
85
86 fn early_exit(&self) -> bool {
87 false
88 }
89}
90
91#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
93pub struct GitHubCommitAuthor {
94 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 GitHubPullRequest {
109 pub number: i64,
111 pub title: Option<String>,
113 pub merge_commit_sha: Option<String>,
115 pub labels: Vec<PullRequestLabel>,
117}
118
119impl RemotePullRequest for GitHubPullRequest {
120 fn number(&self) -> i64 {
121 self.number
122 }
123
124 fn title(&self) -> Option<String> {
125 self.title.clone()
126 }
127
128 fn labels(&self) -> Vec<String> {
129 self.labels.iter().map(|v| v.name.clone()).collect()
130 }
131
132 fn merge_commit(&self) -> Option<String> {
133 self.merge_commit_sha.clone()
134 }
135}
136
137impl RemoteEntry for GitHubPullRequest {
138 fn url(
139 _id: i64,
140 api_url: &str,
141 remote: &Remote,
142 _ref_name: Option<&str>,
143 page: i32,
144 ) -> String {
145 format!(
146 "{}/repos/{}/{}/pulls?per_page={MAX_PAGE_SIZE}&page={page}&state=closed",
147 api_url, remote.owner, remote.repo
148 )
149 }
150
151 fn buffer_size() -> usize {
152 5
153 }
154
155 fn early_exit(&self) -> bool {
156 false
157 }
158}
159
160#[derive(Debug, Clone)]
162pub struct GitHubClient {
163 remote: Remote,
165 client: ClientWithMiddleware,
167}
168
169impl TryFrom<Remote> for GitHubClient {
171 type Error = Error;
172 fn try_from(remote: Remote) -> Result<Self> {
173 Ok(Self {
174 client: remote.create_client("application/vnd.github+json")?,
175 remote,
176 })
177 }
178}
179
180impl RemoteClient for GitHubClient {
181 const API_URL: &'static str = "https://api.github.com";
182 const API_URL_ENV: &'static str = "GITHUB_API_URL";
183
184 fn remote(&self) -> Remote {
185 self.remote.clone()
186 }
187
188 fn client(&self) -> ClientWithMiddleware {
189 self.client.clone()
190 }
191}
192
193impl GitHubClient {
194 pub async fn get_commits(
196 &self,
197 ref_name: Option<&str>,
198 ) -> Result<Vec<Box<dyn RemoteCommit>>> {
199 Ok(self
200 .fetch::<GitHubCommit>(0, ref_name)
201 .await?
202 .into_iter()
203 .map(|v| Box::new(v) as Box<dyn RemoteCommit>)
204 .collect())
205 }
206
207 pub async fn get_pull_requests(
209 &self,
210 ref_name: Option<&str>,
211 ) -> Result<Vec<Box<dyn RemotePullRequest>>> {
212 Ok(self
213 .fetch::<GitHubPullRequest>(0, ref_name)
214 .await?
215 .into_iter()
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 = GitHubCommit {
230 sha: String::from("1d244937ee6ceb8e0314a4a201ba93a7a61f2071"),
231 author: Some(GitHubCommitAuthor {
232 login: Some(String::from("orhun")),
233 }),
234 commit: Some(GitHubCommitDetails {
235 author: GitHubCommitDetailsAuthor {
236 date: String::from("2021-07-18T15:14:39+03:00"),
237 },
238 }),
239 };
240
241 assert_eq!(Some(1626610479), remote_commit.timestamp());
242 }
243}