diff --git a/.gitignore b/.gitignore index 088ba6b..fcd7bbc 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk +.vscode \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index a77141f..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "workbench.colorTheme": "Ayu Mirage Bordered" -} \ No newline at end of file diff --git a/src/api/search.rs b/src/api/search.rs index e00868e..cf5714d 100644 --- a/src/api/search.rs +++ b/src/api/search.rs @@ -44,7 +44,47 @@ pub async fn fetch_pull_requests_matching( &credentials, "https://api.github.com/search/issues", ) - .query(&[("q", format!("\"{}\" in:title,body", pattern))]); + .query(&[("q", format!("{} in:title", pattern))]); + + let items = request.send().await?.json::().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::())) + .await + .into_iter() + .map(|item| async { + let pr = item.unwrap(); + let pr = pr.fetch_reviews(credentials).await.unwrap(); + pr + }) + .collect(); + + Ok(join_all(responses).await) +} +pub async fn fetch_matching_pull_requests_from_repository( + pattern: &str, + repository: &str, + credentials: &Credentials, +) -> Result, Box> { + + let client = reqwest::Client::new(); + + let request = api::base_request( + &client, + &credentials, + "https://api.github.com/search/issues", + ) + .query(&[("q", format!("{} in:title repo:{}", pattern, repository))]); let items = request.send().await?.json::().await?.items; diff --git a/src/main.rs b/src/main.rs index 178ec09..20cc013 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,12 @@ fn clap<'a, 'b>() -> App<'a, 'b> { .required(true) .help("All pull requests containing this identifier in their title form a stack"); + let repository = Arg::with_name("repository") + .long("repository") + .short("r") + .takes_value(true) + .help("Remote repository to filter identifier search results by"); + let exclude = Arg::with_name("exclude") .long("excl") .short("e") @@ -29,6 +35,7 @@ fn clap<'a, 'b>() -> App<'a, 'b> { .setting(AppSettings::ArgRequiredElseHelp) .arg(identifier.clone()) .arg(exclude.clone()) + .arg(repository.clone()) .arg(Arg::with_name("prelude") .long("prelude") .short("p") @@ -39,7 +46,8 @@ fn clap<'a, 'b>() -> App<'a, 'b> { .about("Print a list of all pull requests in a stack to STDOUT") .setting(AppSettings::ArgRequiredElseHelp) .arg(exclude.clone()) - .arg(identifier.clone()); + .arg(identifier.clone()) + .arg(repository.clone()); let autorebase = SubCommand::with_name("autorebase") .about("Rebuild a stack based on changes to local branches and mirror these changes up to the remote") @@ -100,6 +108,24 @@ async fn build_pr_stack( Ok(stack) } +async fn build_pr_stack_for_repo( + pattern: &str, + repository: &str, + credentials: &Credentials, + exclude: Vec, +) -> Result> { + let prs = api::search::fetch_matching_pull_requests_from_repository(pattern, repository, &credentials).await?; + + let prs = prs + .into_iter() + .filter(|pr| !exclude.contains(&pr.number().to_string())) + .map(Rc::new) + .collect::>>(); + let graph = graph::build(&prs); + let stack = graph::log(&graph); + Ok(stack) +} + fn get_excluded(m: &ArgMatches) -> Vec { let excluded = m.values_of("exclude"); @@ -120,7 +146,19 @@ async fn main() -> Result<(), Box> { match matches.subcommand() { ("annotate", Some(m)) => { let identifier = m.value_of("identifier").unwrap(); - let stack = build_pr_stack(identifier, &credentials, get_excluded(m)).await?; + // If no repository is specified, use build_pr_stack. Otherwise, use + // build_pr_stack_for_repo. + let stack = if m.value_of("repository").is_none() { + build_pr_stack(identifier, &credentials, get_excluded(m)).await? + } else { + let repository = m.value_of("repository").unwrap(); + println!( + "Searching for {} identifier in {} repo", + style(identifier).bold(), + style(repository).bold() + ); + build_pr_stack_for_repo(identifier, repository, &credentials, get_excluded(m)).await? + }; let table = markdown::build_table(&stack, identifier, m.value_of("prelude")); for (pr, _) in stack.iter() { @@ -135,7 +173,20 @@ async fn main() -> Result<(), Box> { ("log", Some(m)) => { let identifier = m.value_of("identifier").unwrap(); - let stack = build_pr_stack(identifier, &credentials, get_excluded(m)).await?; + + // If no repository is specified, use build_pr_stack. Otherwise, use + // build_pr_stack_for_repo. + let stack = if m.value_of("repository").is_none() { + build_pr_stack(identifier, &credentials, get_excluded(m)).await? + } else { + let repository = m.value_of("repository").unwrap(); + println!( + "Searching for {} identifier in {} repo", + style(identifier).bold(), + style(repository).bold() + ); + build_pr_stack_for_repo(identifier, repository, &credentials, get_excluded(m)).await? + }; for (pr, maybe_parent) in stack { match maybe_parent {