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