Skip to content

Instantly share code, notes, and snippets.

@dtolnay

dtolnay/main.rs Secret

Created November 24, 2019 09:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dtolnay/aa9c34993dc051a4f344d1b10e4487e8 to your computer and use it in GitHub Desktop.
Save dtolnay/aa9c34993dc051a4f344d1b10e4487e8 to your computer and use it in GitHub Desktop.
// [dependencies]
// anyhow = "1.0"
// reqwest = { version = "=0.10.0-alpha.2", features = ["blocking", "json"] }
// serde = { version = "1.0", features = ["derive"] }
// serde_json = "1.0"
use anyhow::Result;
use reqwest::blocking::Client;
use reqwest::header::AUTHORIZATION;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap as Map, BTreeSet as Set};
#[derive(Deserialize, Debug)]
struct Response<T> {
data: Data<T>,
}
#[derive(Deserialize, Debug)]
struct Data<T> {
repository: T,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Repository {
pull_requests: PullRequests,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct PullRequests {
page_info: PageInfo,
nodes: Vec<PullRequest>,
}
#[derive(Deserialize, Debug)]
struct PullRequest {
number: usize,
url: String,
files: Files,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Files {
page_info: PageInfo,
nodes: Vec<File>,
}
#[derive(Deserialize, Debug)]
struct File {
path: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct PageInfo {
has_next_page: bool,
end_cursor: String,
}
const QUERY: &str = r#"
{
repository(owner: "rust-lang", name: "rust") {
$content
}
}
"#;
const PAGE: &str = r#"
pullRequests(after: $cursor, first: 100, states: OPEN) {
pageInfo {
hasNextPage
endCursor
}
nodes {
number
url
files(first: 100) {
pageInfo {
hasNextPage
endCursor
}
nodes {
path
}
}
}
}
"#;
const PR: &str = r#"
pr$number: pullRequest(number: $number) {
number
url
files(after: $cursor, first: 100) {
pageInfo {
hasNextPage
endCursor
}
nodes {
path
}
}
}
"#;
#[derive(Serialize)]
struct Request {
query: String,
}
const ENDPOINT: &str = "https://api.github.com/graphql";
const APITOKEN: &str = concat!("bearer ", include_str!("githubtoken"));
fn main() -> Result<()> {
let client = Client::new();
let mut changed_files = Set::new();
let mut cursor = "null".to_owned();
let mut next_pages = Vec::new();
loop {
let request = Request {
query: QUERY.replace("$content", PAGE).replace("$cursor", &cursor),
};
let prs = client
.post(ENDPOINT)
.header(AUTHORIZATION, APITOKEN)
.json(&request)
.send()?
.json::<Response<Repository>>()?
.data
.repository
.pull_requests;
for pr in prs.nodes {
if pr.files.page_info.has_next_page {
next_pages.push((pr.number, pr.files.page_info.end_cursor));
}
for file in pr.files.nodes {
changed_files.insert(file.path);
}
}
if !prs.page_info.has_next_page {
break;
}
cursor = serde_json::to_string(&prs.page_info.end_cursor).unwrap();
}
while !next_pages.is_empty() {
let request = Request {
query: QUERY.replace(
"$content",
&next_pages
.into_iter()
.map(|(number, cursor)| {
PR.replace("$number", &number.to_string())
.replace("$cursor", &serde_json::to_string(&cursor).unwrap())
})
.collect::<String>(),
),
};
let prs = client
.post(ENDPOINT)
.header(AUTHORIZATION, APITOKEN)
.json(&request)
.send()?
.json::<Response<Map<String, PullRequest>>>()?
.data
.repository;
next_pages = Vec::new();
for (_, pr) in prs {
if pr.files.page_info.has_next_page {
next_pages.push((pr.number, pr.files.page_info.end_cursor));
}
for file in pr.files.nodes {
changed_files.insert(file.path);
}
}
}
for path in changed_files {
println!("{}", path);
}
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment