subalfred_core/github/
substrate.rs

1//! Collection of Substrate GitHub related functions.
2
3// crates.io
4use futures::{StreamExt, stream};
5use githuber::api::commits;
6// subalfred
7use super::*;
8use crate::prelude::*;
9
10/// The specific labels that worth to watch.
11pub enum WatchedLabels {
12	/// [https://github.com/paritytech/substrate/labels/C5-high]
13	C5High,
14	/// [https://github.com/paritytech/substrate/labels/C7-critical]
15	C7Critical,
16	/// [https://github.com/paritytech/substrate/labels/D2-breaksapi]
17	D2BreaksApi,
18	/// [https://github.com/paritytech/substrate/labels/E0-runtime_migration]
19	E0RuntimeMigration,
20	/// [https://github.com/paritytech/substrate/labels/E1-database_migration]
21	E1DatabaseMigration,
22	/// [https://github.com/paritytech/substrate/labels/E3-host_functions]
23	E3HostFunctions,
24	/// [https://github.com/paritytech/substrate/labels/E4-node_first_update]
25	E4NodeFirstUpdate,
26	/// [https://github.com/paritytech/substrate/labels/F0-breaks_everything]
27	F0BreaksEverything,
28	/// [https://github.com/paritytech/substrate/labels/F1-breaks_authoring]
29	F1BreaksAuthoring,
30	/// [https://github.com/paritytech/substrate/labels/F2-breaks_consensus]
31	F2BreaksConsensus,
32	/// [https://github.com/paritytech/substrate/labels/F3-breaks_API]
33	F3BreaksApi,
34}
35impl WatchedLabels {
36	const fn as_str(&self) -> &'static str {
37		// subalfred
38		use WatchedLabels::*;
39
40		match self {
41			C5High => "C5-high",
42			C7Critical => "C7-critical",
43			D2BreaksApi => "D2-breaksapi",
44			E0RuntimeMigration => "E0-runtime_migration",
45			E1DatabaseMigration => "E1-database_migration",
46			E3HostFunctions => "E3-host_functions",
47			E4NodeFirstUpdate => "E4-node_first_update",
48			F0BreaksEverything => "F0-breaks_everything",
49			F1BreaksAuthoring => "F1-breaks_authoring",
50			F2BreaksConsensus => "F2-breaks_consensus",
51			F3BreaksApi => "F3-breaks_API",
52		}
53	}
54
55	/// Return all labels' name.
56	pub fn all() -> Vec<&'static str> {
57		// subalfred
58		use WatchedLabels::*;
59
60		vec![
61			C5High.as_str(),
62			C7Critical.as_str(),
63			D2BreaksApi.as_str(),
64			E0RuntimeMigration.as_str(),
65			E1DatabaseMigration.as_str(),
66			E3HostFunctions.as_str(),
67			E4NodeFirstUpdate.as_str(),
68			F0BreaksEverything.as_str(),
69			F1BreaksAuthoring.as_str(),
70			F2BreaksConsensus.as_str(),
71			F3BreaksApi.as_str(),
72		]
73	}
74}
75
76/// The pull requests with specific labels that worth to watch.
77#[derive(Debug, Default)]
78pub struct WatchedPullRequests {
79	c5_high: Vec<PullRequest>,
80	c7_critical: Vec<PullRequest>,
81	d2_breaks_api: Vec<PullRequest>,
82	e0_runtime_migration: Vec<PullRequest>,
83	e1_database_migration: Vec<PullRequest>,
84	e3_host_functions: Vec<PullRequest>,
85	e4_node_first_update: Vec<PullRequest>,
86	f0_breaks_everything: Vec<PullRequest>,
87	f1_breaks_authoring: Vec<PullRequest>,
88	f2_breaks_consensus: Vec<PullRequest>,
89	f3_breaks_api: Vec<PullRequest>,
90}
91impl WatchedPullRequests {
92	fn try_push(&mut self, pull_request: PullRequest) {
93		pull_request.labels.iter().for_each(|l| {
94			for (wl, ps) in WatchedLabels::all().into_iter().zip(self.all_mut()) {
95				if l.name.as_str() == wl {
96					ps.push(pull_request.clone());
97
98					break;
99				}
100			}
101		});
102	}
103
104	fn all_mut(&mut self) -> Vec<&mut Vec<PullRequest>> {
105		vec![
106			&mut self.c5_high,
107			&mut self.c7_critical,
108			&mut self.d2_breaks_api,
109			&mut self.e0_runtime_migration,
110			&mut self.e1_database_migration,
111			&mut self.e3_host_functions,
112			&mut self.e4_node_first_update,
113			&mut self.f0_breaks_everything,
114			&mut self.f1_breaks_authoring,
115			&mut self.f2_breaks_consensus,
116			&mut self.f3_breaks_api,
117		]
118	}
119
120	/// Make all fields into a [`Vec`].
121	pub fn all(self) -> Vec<Vec<PullRequest>> {
122		vec![
123			self.c5_high,
124			self.c7_critical,
125			self.d2_breaks_api,
126			self.e0_runtime_migration,
127			self.e1_database_migration,
128			self.e3_host_functions,
129			self.e4_node_first_update,
130			self.f0_breaks_everything,
131			self.f1_breaks_authoring,
132			self.f2_breaks_consensus,
133			self.f3_breaks_api,
134		]
135	}
136}
137impl From<Vec<PullRequest>> for WatchedPullRequests {
138	fn from(pull_requests: Vec<PullRequest>) -> Self {
139		let mut w = WatchedPullRequests::default();
140
141		pull_requests.into_iter().for_each(|p| w.try_push(p));
142
143		w
144	}
145}
146
147/// Track the updates.
148///
149/// Basically, it compares two commits and return the associated pull requests.
150pub async fn track_updates(owner: &str, repo: &str, basehead: &str) -> Result<Vec<PullRequest>> {
151	let api_client = ApiClient::new()?;
152	let mut request =
153		commits::compare_two_commits(owner, repo, basehead).per_page(ApiClient::PER_PAGE).page(1);
154	let mut commit_shas = Vec::new();
155
156	loop {
157		let response = api_client.request_auto_retry::<_, Commits>(&request).await;
158		let page = request
159			.page
160			.take()
161			.expect("[core::github] `page` has already been set in previous step; qed");
162		let commits_count = response.commits.len() as u8;
163
164		response.commits.into_iter().for_each(|commit| commit_shas.push(commit.sha));
165
166		if commits_count < ApiClient::PER_PAGE {
167			break;
168		}
169
170		request = request.page(page + 1);
171	}
172
173	let mut pull_requests = stream::iter(commit_shas)
174		.enumerate()
175		.map(|(i, commit_sha)| {
176			let api_client = api_client.clone();
177
178			async move {
179				(
180					i,
181					api_client
182						.request_auto_retry::<_, Vec<PullRequest>>(
183							&commits::list_pull_requests_associated_with_a_commit(
184								owner,
185								repo,
186								&commit_sha,
187							),
188						)
189						.await,
190				)
191			}
192		})
193		// TODO: configurable
194		.buffer_unordered(64)
195		.collect::<Vec<_>>()
196		.await;
197
198	pull_requests.sort_by_key(|(i, _)| *i);
199
200	Ok(pull_requests.into_iter().flat_map(|(_, pull_request)| pull_request).collect())
201}