Skip to content

Commit

Permalink
Issues can link to PRs
Browse files Browse the repository at this point in the history
  • Loading branch information
willcrichton committed Aug 13, 2024
1 parent 1cc3883 commit 872de44
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 8 deletions.
82 changes: 78 additions & 4 deletions src/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ use octocrab::{
GitHubError, Octocrab,
};
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
use regex::Regex;
use serde_json::json;
use std::{env, fs, process::Command, sync::Arc, time::Duration};
use tokio::{time::timeout, try_join};
use tracing::warn;

use crate::utils;

pub struct GithubRepo {
user: String,
Expand All @@ -27,6 +31,11 @@ pub struct GithubRepo {
issues: Mutex<Option<Vec<Issue>>>,
}

pub enum PullSelector {
Branch(String),
Label(String),
}

impl GithubRepo {
pub fn new(user: &str, name: &str) -> Self {
GithubRepo {
Expand Down Expand Up @@ -58,7 +67,15 @@ impl GithubRepo {
}) => return Ok(()),
Err(e) => return Err(e.into()),
};
let (prs, issues) = (pr_page.take_items(), issue_page.take_items());
let (prs, mut issues) = (pr_page.take_items(), issue_page.take_items());

for pr in &prs {
println!("test: {:#?}", pr.labels);
}

// Pull requests are considered issues, so filter them out
issues.retain(|issue| issue.pull_request.is_none());

*self.prs.lock() = Some(prs);
*self.issues.lock() = Some(issues);
Ok(())
Expand Down Expand Up @@ -168,9 +185,16 @@ impl GithubRepo {
MutexGuard::map(self.prs.lock(), |opt| opt.as_mut().unwrap())
}

pub fn pr(&self, ref_field: &str) -> Option<MappedMutexGuard<'_, PullRequest>> {
pub fn pr(&self, selector: &PullSelector) -> Option<MappedMutexGuard<'_, PullRequest>> {
let prs = self.prs();
let idx = prs.iter().position(|pr| pr.head.ref_field == ref_field)?;
let idx = prs.iter().position(|pr| match selector {
PullSelector::Branch(branch) => &pr.head.ref_field == branch,
PullSelector::Label(label) => pr
.labels
.as_ref()
.map(|labels| labels.iter().any(|l| &l.name == label))
.unwrap_or(false),
})?;
Some(MappedMutexGuard::map(prs, |prs| &mut prs[idx]))
}

Expand Down Expand Up @@ -209,6 +233,20 @@ impl GithubRepo {
);
let self_pr = request.send().await?;

// TODO: lots of parallelism below we should exploit

let labels = match &base_pr.labels {
Some(labels) => labels
.iter()
.map(|label| label.name.clone())
.collect::<Vec<_>>(),
None => Vec::new(),
};
self
.issue_handler()
.add_labels(self_pr.number, &labels)
.await?;

let comment_pages = base
.pr_handler()
.list_comments(Some(base_pr.number))
Expand Down Expand Up @@ -244,11 +282,47 @@ impl GithubRepo {
Ok(())
}

fn process_issue_body(&self, body: &str) -> String {
let re = Regex::new(r"\{\{ (\S+) (\S+) \}\}").unwrap();
let mut new_body = body.to_string();
let substitutions = re.captures_iter(body).filter_map(|cap| {
let full_match = cap.get(0).unwrap();
let label = &cap[1];
let kind = &cap[2];
let number = match kind {
"pr" => {
let Some(pr) = self.pr(&PullSelector::Label(label.to_string())) else {
warn!("No PR with label {label}");
return None;
};
pr.number
}
"issue" => {
let Some(issue) = self.issue(label) else {
warn!("No issue with label {label}");
return None;
};
issue.number
}
_ => unimplemented!(),
};

Some((full_match.range(), format!("#{number}")))
});
utils::replace_many_ranges(&mut new_body, substitutions);

// todo!()
new_body
}

pub async fn copy_issue(&self, issue: &Issue) -> Result<()> {
let body = issue.body.as_ref().unwrap();
let body_processed = self.process_issue_body(body);

self
.issue_handler()
.create(&issue.title)
.body(issue.body.as_ref().unwrap())
.body(body_processed)
.labels(
issue
.labels
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod github;
mod quest;
mod stage;
mod ui;
mod utils;

fn main() {
ui::launch();
Expand Down
19 changes: 15 additions & 4 deletions src/quest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::{

use crate::{
git::GitRepo,
github::GithubRepo,
github::{GithubRepo, PullSelector},
stage::{Stage, StageConfig, StagePart, StagePartStatus},
};
use anyhow::{ensure, Context, Result};
Expand Down Expand Up @@ -298,7 +298,11 @@ impl Quest {

let head = self.origin_git.head_commit()?;

let pr = self.upstream.pr(target_branch).unwrap().clone();
let pr = self
.upstream
.pr(&PullSelector::Branch(target_branch.into()))
.unwrap()
.clone();
self.origin.copy_pr(&self.upstream, &pr, &head).await?;

Ok(())
Expand All @@ -319,6 +323,9 @@ impl Quest {
.await?;
}

// Need to refresh our state for issues that refer to the filed PR
self.infer_state_update().await?;

let issue = self.upstream.issue(&stage.config.label).unwrap().clone();
self.origin.copy_issue(&issue).await?;

Expand Down Expand Up @@ -349,13 +356,17 @@ impl Quest {

pub fn feature_pr_url(&self, stage_index: usize) -> Option<String> {
let stage = &self.stages[stage_index];
let pr = self.origin.pr(&stage.branch_name(StagePart::Starter))?;
let pr = self
.origin
.pr(&PullSelector::Branch(stage.branch_name(StagePart::Starter)))?;
Some(pr.html_url.as_ref().unwrap().to_string())
}

pub fn solution_pr_url(&self, stage_index: usize) -> Option<String> {
let stage = &self.stages[stage_index];
let pr = self.origin.pr(&stage.branch_name(StagePart::Solution))?;
let pr = self.origin.pr(&PullSelector::Branch(
stage.branch_name(StagePart::Solution),
))?;
Some(pr.html_url.as_ref().unwrap().to_string())
}
}
14 changes: 14 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use std::ops::Range;

pub fn replace_many_ranges(
s: &mut String,
ranges: impl IntoIterator<Item = (Range<usize>, impl AsRef<str>)>,
) {
let ranges = ranges.into_iter().collect::<Vec<_>>();
if !ranges.is_empty() {
debug_assert!((0..ranges.len() - 1).all(|i| ranges[i].0.end <= ranges[i + 1].0.start));
for (range, content) in ranges.into_iter().rev() {
s.replace_range(range, content.as_ref());
}
}
}

0 comments on commit 872de44

Please sign in to comment.