diff --git a/Cargo.lock b/Cargo.lock index e2fd3cf..ac975d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,6 +79,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + [[package]] name = "fnv" version = "1.0.7" @@ -227,6 +233,7 @@ name = "gh-stack" version = "0.1.0" dependencies = [ "futures", + "petgraph", "reqwest", "serde", "tokio", @@ -575,6 +582,16 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "petgraph" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project" version = "0.4.17" diff --git a/Cargo.toml b/Cargo.toml index e15f57b..9aa086e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ reqwest = { version = "0.10.6", features = ["json"] } tokio = { version = "0.2", features = ["full"] } serde = { version = "1.0", features = ["derive"] } futures = "0.3.5" +petgraph = "0.5" diff --git a/src/api/search.rs b/src/api/search.rs index 4830baa..c57c7da 100644 --- a/src/api/search.rs +++ b/src/api/search.rs @@ -2,26 +2,28 @@ use futures::future::join_all; use serde::Deserialize; use std::error::Error; -use crate::{api, Credentials}; +use crate::{api, markdown, Credentials}; -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct SearchItem { url: String, title: String, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct PullRequestRef { label: String, r#ref: String, sha: String, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct PullRequest { id: usize, + number: usize, head: PullRequestRef, base: PullRequestRef, + merges_into: Option>, title: String, } @@ -33,8 +35,22 @@ impl PullRequest { pub fn base(&self) -> &str { &self.base.label } + + pub fn set_merges_into(&mut self, into: PullRequest) { + // `clone` here to avoid an explosion of lifetime specifiers + self.merges_into = Some(Box::new(into)) + } } +// impl markdown::AsMarkdown for PullRequest { +// fn as_markdown_table_row(&self) -> String { +// match self.merges_into { +// Some(into) => format!("|#{}|{}|#{}|", self.number, self.title, into.number), +// None => format!("|#{}|{}|`develop`/feature branch|", self.number, self.title), +// } +// } +// } + #[derive(Deserialize, Debug)] struct SearchResponse { items: Vec, diff --git a/src/graph.rs b/src/graph.rs index e871e40..bcb6777 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -1,34 +1,23 @@ use std::collections::{HashMap, HashSet}; +use petgraph::visit::IntoNodeReferences; +use std::rc::Rc; +use std::cell::RefCell; +use petgraph::Graph; use crate::api::search::PullRequest; -pub fn build(prs: &[PullRequest]) { - let mut heads = HashSet::new(); - let mut prs_by_base = HashMap::new(); +pub fn build(prs: Vec>) -> Graph, usize> { + let mut tree = Graph::, usize>::new(); + let heads = prs.iter().map(|pr| pr.head()); + let handles: Vec<_> = prs.iter().map(|pr| tree.add_node(pr.clone())).collect(); + let handles_by_head: HashMap<_, _> = heads.zip(handles.iter()).collect(); - 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); + for (i, pr) in prs.iter().enumerate() { + let head_handle = handles[i]; + if let Some(&base_handle) = handles_by_head.get(pr.base()) { + tree.add_edge(head_handle, *base_handle, 1); } } - - results + + tree } diff --git a/src/lib.rs b/src/lib.rs index 841d60f..acb7cc6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ pub mod api; pub mod graph; +pub mod markdown; pub struct Credentials { // Personal access token diff --git a/src/main.rs b/src/main.rs index 7d2dd50..9b17b5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ use std::collections::HashMap; +use std::rc::Rc; use std::env; use std::error::Error; use std::process; use gh_stack::api; use gh_stack::graph; +use gh_stack::markdown; use gh_stack::Credentials; #[tokio::main] @@ -25,7 +27,11 @@ async fn main() -> Result<(), Box> { let credentials = Credentials::new(token); let prs = api::search::fetch_pull_requests_matching(&pattern, &credentials).await?; - graph::build(&prs); + let prs = prs.into_iter().map(|pr| Rc::new(pr)).collect(); + let tree = graph::build(prs); + println!("{:?}", tree); + + // markdown::build_table(&graph[..]); Ok(()) /* diff --git a/src/markdown.rs b/src/markdown.rs new file mode 100644 index 0000000..d6b16c1 --- /dev/null +++ b/src/markdown.rs @@ -0,0 +1,8 @@ +use crate::api::search::PullRequest; +use std::fmt::Display; + +pub trait AsMarkdown { + fn as_markdown_table_row(&self) -> String; +} + +pub fn build_table(graph: &[T]) {}