rootle_github/client/
history.rs1use super::{API, GitHubClient, urlencoding};
4use rootle_provider::ProviderResult;
5
6impl GitHubClient {
7 pub fn log(
11 &self,
12 owner: &str,
13 repo: &str,
14 path: Option<&str>,
15 ref_: Option<&str>,
16 limit: Option<usize>,
17 ) -> ProviderResult<(Vec<rootle_provider::LogEntry>, bool)> {
18 #[derive(serde::Deserialize)]
19 struct CommitItem {
20 sha: String,
21 commit: CommitDetail,
22 }
23 #[derive(serde::Deserialize)]
24 struct CommitDetail {
25 message: String,
26 author: CommitAuthor,
27 }
28 #[derive(serde::Deserialize)]
29 struct CommitAuthor {
30 name: String,
31 date: String,
32 }
33 let want = limit.unwrap_or(50).min(99);
34 let mut url = format!("{API}/repos/{owner}/{repo}/commits?per_page={}", want + 1);
35 if let Some(p) = path {
36 url.push_str(&format!("&path={}", urlencoding(p)));
37 }
38 if let Some(r) = ref_ {
39 url.push_str(&format!("&sha={}", urlencoding(r)));
40 }
41 let mut items: Vec<CommitItem> = self.get(&url)?;
42 let truncated = items.len() > want;
43 items.truncate(want);
44 Ok((
45 items
46 .into_iter()
47 .map(|c| rootle_provider::LogEntry {
48 sha: c.sha,
49 subject: c.commit.message.lines().next().unwrap_or("").to_string(),
50 author: c.commit.author.name,
51 date: c.commit.author.date,
52 })
53 .collect(),
54 truncated,
55 ))
56 }
57
58 pub fn commit(
62 &self,
63 owner: &str,
64 repo: &str,
65 sha: &str,
66 ) -> ProviderResult<crate::types::CommitResponse> {
67 const FILES_PER_PAGE: usize = 100;
68 const FILE_BUDGET: usize = rootle_provider::RENDER_BUDGET;
69 let mut url = reqwest::Url::parse(API).expect("GitHub API URL");
70 url.path_segments_mut()
71 .expect("HTTP URL supports path segments")
72 .extend(["repos", owner, repo, "commits", sha]);
73 url.query_pairs_mut()
74 .append_pair("per_page", &FILES_PER_PAGE.to_string());
75 let first = self.get_page::<crate::types::CommitResponse>(url.as_str())?;
76 let mut response = first.body;
77 let mut has_next = first.has_next;
78 let mut page = 1u32;
79 url.path_segments_mut()
82 .expect("HTTP URL supports path segments")
83 .pop()
84 .push(&response.sha);
85 while has_next && response.files.len() < FILE_BUDGET {
86 page += 1;
87 url.query_pairs_mut()
88 .clear()
89 .append_pair("per_page", &FILES_PER_PAGE.to_string())
90 .append_pair("page", &page.to_string());
91 let next = self.get_page::<crate::types::CommitResponse>(url.as_str())?;
92 if next.body.sha != response.sha {
93 return Err(rootle_provider::ProviderError::other(
94 "commit changed across file pages",
95 ));
96 }
97 response.files.extend(next.body.files);
98 has_next = next.has_next;
99 }
100 response.truncated = has_next || response.files.len() > FILE_BUDGET;
101 response.files.truncate(FILE_BUDGET);
102 Ok(response)
103 }
104}