build graph

This commit is contained in:
Timothy Andrew 2020-06-02 14:35:58 +05:30
parent 4345e3573f
commit cccab03886
No known key found for this signature in database
GPG Key ID: ABD64509E977B249
7 changed files with 220 additions and 20 deletions

90
Cargo.lock generated
View File

@ -116,6 +116,21 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "futures"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.5" version = "0.3.5"
@ -123,6 +138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink",
] ]
[[package]] [[package]]
@ -131,6 +147,35 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399"
[[package]]
name = "futures-executor"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789"
[[package]]
name = "futures-macro"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.5" version = "0.3.5"
@ -142,6 +187,9 @@ name = "futures-task"
version = "0.3.5" version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626"
dependencies = [
"once_cell",
]
[[package]] [[package]]
name = "futures-util" name = "futures-util"
@ -149,10 +197,18 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6"
dependencies = [ dependencies = [
"futures-channel",
"futures-core", "futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task", "futures-task",
"memchr",
"pin-project", "pin-project",
"pin-utils", "pin-utils",
"proc-macro-hack",
"proc-macro-nested",
"slab",
] ]
[[package]] [[package]]
@ -170,7 +226,9 @@ dependencies = [
name = "gh-stack" name = "gh-stack"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"futures",
"reqwest", "reqwest",
"serde",
"tokio", "tokio",
] ]
@ -472,6 +530,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "once_cell"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.29" version = "0.10.29"
@ -555,6 +619,18 @@ version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
[[package]]
name = "proc-macro-hack"
version = "0.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
[[package]]
name = "proc-macro-nested"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.18" version = "1.0.18"
@ -708,6 +784,20 @@ name = "serde"
version = "1.0.111" version = "1.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "serde_json" name = "serde_json"

View File

@ -9,3 +9,5 @@ edition = "2018"
[dependencies] [dependencies]
reqwest = { version = "0.10.6", features = ["json"] } reqwest = { version = "0.10.6", features = ["json"] }
tokio = { version = "0.2", features = ["full"] } tokio = { version = "0.2", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
futures = "0.3.5"

View File

@ -1 +1,13 @@
pub mod search; use crate::Credentials;
use reqwest::{Client, RequestBuilder};
use std::time::Duration;
pub mod search;
fn base_request(client: &Client, credentials: &Credentials, url: &str) -> RequestBuilder {
client
.get(url)
.timeout(Duration::from_secs(5))
.header("Authorization", format!("token {}", credentials.token))
.header("User-Agent", "timothyandrew/gh-stack")
}

View File

@ -1,18 +1,75 @@
use futures::future::join_all;
use serde::Deserialize;
use std::error::Error; use std::error::Error;
use crate::Credentials; use crate::{api, Credentials};
#[derive(Deserialize, Debug)]
pub struct SearchItem {
url: String,
title: String,
}
#[derive(Deserialize, Debug)]
pub struct PullRequestRef {
label: String,
r#ref: String,
sha: String,
}
#[derive(Deserialize, Debug)]
pub struct PullRequest {
id: usize,
head: PullRequestRef,
base: PullRequestRef,
title: String,
}
impl PullRequest {
pub fn head(&self) -> &str {
&self.head.label
}
pub fn base(&self) -> &str {
&self.base.label
}
}
#[derive(Deserialize, Debug)]
struct SearchResponse {
items: Vec<SearchItem>,
}
pub async fn fetch_pull_requests_matching( pub async fn fetch_pull_requests_matching(
pattern: &str, pattern: &str,
credentials: &Credentials, credentials: &Credentials,
) -> Result<(), Box<dyn Error>> { ) -> Result<Vec<PullRequest>, Box<dyn Error>> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let request = client
.get("https://api.github.com/search/issues") let request = api::base_request(
.query(&[("q", format!("{} in:title", pattern))]) &client,
.header("Authorization", format!("token {}", credentials.token)) &credentials,
.header("User-Agent", "timothyandrew/gh-stack"); "https://api.github.com/search/issues",
let response = request.send().await?.text().await?; )
println!("{}", response); .query(&[("q", format!("{} in:title", pattern))]);
Ok(())
} let items = request.send().await?.json::<SearchResponse>().await?.items;
let item_futures = items.into_iter().map(|item| {
api::base_request(&client, &credentials, &item.url.replace("issues", "pulls")).send()
});
// The `unwrap`s are required here because both `reqwest::send` and `reqwest::json` return a `Result` which has
// to be unwrapped after the future has been `await`ed on.
let items = join_all(item_futures)
.await
.into_iter()
.map(|item| item.unwrap());
let responses: Vec<_> = join_all(items.map(|item| item.json::<PullRequest>()))
.await
.into_iter()
.map(|item| item.unwrap())
.collect();
Ok(responses)
}

34
src/graph.rs Normal file
View File

@ -0,0 +1,34 @@
use std::collections::{HashMap, HashSet};
use crate::api::search::PullRequest;
pub fn build(prs: &[PullRequest]) {
let mut heads = HashSet::new();
let mut prs_by_base = HashMap::new();
for pr in prs.iter() {
heads.insert(pr.head());
let entry = prs_by_base.entry(pr.base()).or_insert(Vec::new());
entry.push(pr);
}
let roots: Vec<&PullRequest> = prs.iter().filter(|pr| !heads.contains(pr.base())).collect();
let results = resolve(&roots, &prs_by_base);
}
fn resolve<'a>(
roots: &Vec<&'a PullRequest>,
prs_by_base: &'a HashMap<&str, Vec<&PullRequest>>
) -> Vec<&'a PullRequest> {
let mut results = Vec::new();
for &root in roots.iter() {
results.push(root);
if let Some(children) = prs_by_base.get(root.head()) {
let mut children = resolve(children, prs_by_base);
results.append(&mut children);
}
}
results
}

View File

@ -1,5 +1,5 @@
pub mod api; pub mod api;
pub mod graph;
pub struct Credentials { pub struct Credentials {
// Personal access token // Personal access token

View File

@ -1,9 +1,10 @@
use std::error::Error;
use std::env;
use std::collections::HashMap; use std::collections::HashMap;
use std::env;
use std::error::Error;
use std::process; use std::process;
use gh_stack::api; use gh_stack::api;
use gh_stack::graph;
use gh_stack::Credentials; use gh_stack::Credentials;
#[tokio::main] #[tokio::main]
@ -17,10 +18,14 @@ async fn main() -> Result<(), Box<dyn Error>> {
} }
let pattern = args.last().unwrap(); let pattern = args.last().unwrap();
let token = env.get("GHSTACK_OAUTH_TOKEN").expect("You didn't pass `GHSTACK_OAUTH_TOKEN`"); let token = env
.get("GHSTACK_OAUTH_TOKEN")
.expect("You didn't pass `GHSTACK_OAUTH_TOKEN`");
let credentials = Credentials::new(token); let credentials = Credentials::new(token);
api::search::fetch_pull_requests_matching(&pattern, &credentials).await?;
let prs = api::search::fetch_pull_requests_matching(&pattern, &credentials).await?;
graph::build(&prs);
Ok(()) Ok(())
/* /*