Skip to content

Commit

Permalink
convert find_reviews() into a method on type RepoChangeset
Browse files Browse the repository at this point in the history
  • Loading branch information
majewsky committed Aug 15, 2024
1 parent 2acf77b commit 54d1d65
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 119 deletions.
122 changes: 116 additions & 6 deletions src/changes.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,129 @@
use std::sync::Arc;

use crate::util::parse_remote;
use anyhow::{anyhow, Context};
use octocrab::commits::PullRequestTarget;
use octocrab::models::commits::Commit;
use octocrab::models::pulls::Review;
use octocrab::models::pulls::ReviewState::Approved;
use octocrab::Octocrab;

#[derive(Clone, Debug)]
pub struct RepoChangeset {
pub name: String,
pub remote: String,
pub name: String,
pub remote: String,
pub base_commit: String,
pub head_commit: String,
pub changes: Vec<Changeset>,
pub changes: Vec<Changeset>,
}

impl RepoChangeset {
pub async fn analyze_commits(&mut self, octocrab: &Arc<Octocrab>) -> Result<(), anyhow::Error> {
let (repo_owner, repo_name) = parse_remote(&self.remote).context("while parsing remote")?;

let compare = octocrab
.commits(repo_owner.clone(), repo_name.clone())
.compare(&self.base_commit, &self.head_commit)
.send()
.await
.context(format!(
"failed to compare {}/compare/{}...{}",
self.remote.trim_end_matches(".git"),
&self.base_commit,
&self.head_commit
))?;

for commit in &compare.commits {
self.analyze_commit(octocrab, commit).await?;
}

Ok(())
}

async fn analyze_commit(&mut self, octocrab: &Arc<Octocrab>, commit: &Commit) -> Result<(), anyhow::Error> {
// TODO: it's not nice that we have to do this each time, this should be parsed once and
// stored inside of `self.remote`
let (repo_owner, repo_name) = parse_remote(&self.remote).context("while parsing remote")?;

let mut associated_prs_page = octocrab
.commits(repo_owner.clone(), repo_name.clone())
.associated_pull_requests(PullRequestTarget::Sha(commit.sha.clone()))
.send()
.await
.context("failed to get associated prs")?;
assert!(
associated_prs_page.next.is_none(),
"found more than one page for associated_prs"
);
let associated_prs = associated_prs_page.take_items();

let change_commit = CommitMetadata {
headline: commit
.commit
.message
.split('\n')
.next()
.unwrap_or("<empty commit message>")
.to_string(),
link: commit.html_url.clone(),
};

if associated_prs.is_empty() {
self.changes.push(Changeset {
commits: vec![change_commit],
pr_link: None,
approvals: Vec::new(),
});
return Ok(());
}

for associated_pr in &associated_prs {
println!("pr number: {:}", associated_pr.number);

let mut pr_reviews_page = octocrab
.pulls(repo_owner.clone(), repo_name.clone())
.list_reviews(associated_pr.number)
.send()
.await
.context("failed to get reviews")?;
assert!(
pr_reviews_page.next.is_none(),
"found more than one page for associated_prs"
);
let pr_reviews = pr_reviews_page.take_items();

let associated_pr_link = Some(
associated_pr
.html_url
.as_ref()
.ok_or(anyhow!("pr without an html link!?"))?
.to_string(),
);

if let Some(changeset) = self.changes.iter_mut().find(|cs| cs.pr_link == associated_pr_link) {
changeset.commits.push(change_commit.clone());
changeset.collect_approved_reviews(&pr_reviews);
continue;
}

let mut changeset = Changeset {
commits: vec![change_commit.clone()],
pr_link: associated_pr_link,
approvals: Vec::new(),
};

changeset.collect_approved_reviews(&pr_reviews);
self.changes.push(changeset);
}

Ok(())
}
}

#[derive(Clone, Debug)]
pub struct Changeset {
pub commits: Vec<CommitMetadata>,
pub pr_link: Option<String>,
pub commits: Vec<CommitMetadata>,
pub pr_link: Option<String>,
pub approvals: Vec<String>,
}

Expand All @@ -35,5 +145,5 @@ impl Changeset {
#[derive(Clone, Debug)]
pub struct CommitMetadata {
pub headline: String,
pub link: String,
pub link: String,
}
123 changes: 10 additions & 113 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@

mod changes;
mod helm_config;
mod util;

use std::sync::Arc;
use std::{env, str};

use anyhow::{anyhow, Context};
use changes::{Changeset, CommitMetadata, RepoChangeset};
use changes::RepoChangeset;
use clap::builder::styling::Style;
use clap::{Parser, Subcommand};
use git2::Repository;
use helm_config::ImageRefs;
use lazy_static::lazy_static;
use octocrab::commits::PullRequestTarget;
use octocrab::Octocrab;
use url::Url;

const BOLD_UNDERLINE: Style = Style::new().bold().underline();
lazy_static! {
Expand Down Expand Up @@ -89,21 +87,21 @@ async fn main() -> Result<(), anyhow::Error> {
match &cli.command {
Commands::Repo { remote } => {
let repo = &mut RepoChangeset {
name: parse_remote(remote).context("while parsing remote")?.1,
remote: remote.clone(),
name: util::parse_remote(remote).context("while parsing remote")?.1,
remote: remote.clone(),
base_commit: cli.base,
head_commit: cli.head,
changes: Vec::new(),
changes: Vec::new(),
};
find_reviews(&octocrab, repo).await.context("while finding reviews")?;
repo.analyze_commits(&octocrab).await.context("while finding reviews")?;
print_changes(&[repo.clone()]);
},
Commands::HelmChart { workspace } => {
let mut changes =
find_values_yaml(workspace.clone(), &cli.base, &cli.head).context("while finding values.yaml files")?;

for repo in &mut changes {
find_reviews(&octocrab, repo)
repo.analyze_commits(&octocrab)
.await
.context("while collecting repo changes")?;
}
Expand All @@ -115,15 +113,6 @@ async fn main() -> Result<(), anyhow::Error> {
Ok(())
}

fn parse_remote(remote: &str) -> Result<(String, String), anyhow::Error> {
let remote_url = Url::parse(remote).context("can't parse remote")?;
let repo_parts: Vec<&str> = remote_url.path().trim_start_matches('/').split('/').collect();
let repo_owner = repo_parts[0].to_string();
let repo_name = repo_parts[1].trim_end_matches(".git").to_string();

Ok((repo_owner, repo_name))
}

fn find_values_yaml(workspace: String, base: &str, head: &str) -> Result<Vec<RepoChangeset>, anyhow::Error> {
let repo = Repository::open(workspace).context("failed to open repository")?;

Expand Down Expand Up @@ -170,12 +159,12 @@ fn find_values_yaml(workspace: String, base: &str, head: &str) -> Result<Vec<Rep
for (name, image) in &new_image_config.container_images {
for source in &image.sources {
changes.push(RepoChangeset {
name: name.clone(),
remote: source.repo.clone(),
name: name.clone(),
remote: source.repo.clone(),
// TODO: iterate over sources
base_commit: old_image_config.container_images[name].sources[0].commit.clone(),
head_commit: source.commit.clone(),
changes: Vec::new(),
changes: Vec::new(),
});
}
}
Expand All @@ -187,98 +176,6 @@ fn find_values_yaml(workspace: String, base: &str, head: &str) -> Result<Vec<Rep
Ok(changes)
}

// TODO: make member function of repo
async fn find_reviews(octocrab: &Arc<Octocrab>, repo: &mut RepoChangeset) -> Result<(), anyhow::Error> {
let (repo_owner, repo_name) = parse_remote(&repo.remote).context("while parsing remote")?;

let compare = octocrab
.commits(repo_owner.clone(), repo_name.clone())
.compare(&repo.base_commit, &repo.head_commit)
.send()
.await
.context(format!(
"failed to compare {}/compare/{}...{}",
repo.remote.trim_end_matches(".git"),
&repo.base_commit,
&repo.head_commit
))?;

for commit in &compare.commits {
let mut associated_prs_page = octocrab
.commits(repo_owner.clone(), repo_name.clone())
.associated_pull_requests(PullRequestTarget::Sha(commit.sha.clone()))
.send()
.await
.context("failed to get associated prs")?;
assert!(
associated_prs_page.next.is_none(),
"found more than one page for associated_prs"
);
let associated_prs = associated_prs_page.take_items();

let change_commit = CommitMetadata {
headline: commit
.commit
.message
.split('\n')
.next()
.unwrap_or("<empty commit message>")
.to_string(),
link: commit.html_url.clone(),
};

if associated_prs.is_empty() {
repo.changes.push(Changeset {
commits: vec![change_commit],
pr_link: None,
approvals: Vec::new(),
});
continue;
}

for associated_pr in &associated_prs {
println!("pr number: {:}", associated_pr.number);

let mut pr_reviews_page = octocrab
.pulls(repo_owner.clone(), repo_name.clone())
.list_reviews(associated_pr.number)
.send()
.await
.context("failed to get reviews")?;
assert!(
pr_reviews_page.next.is_none(),
"found more than one page for associated_prs"
);
let pr_reviews = pr_reviews_page.take_items();

let associated_pr_link = Some(
associated_pr
.html_url
.as_ref()
.ok_or(anyhow!("pr without an html link!?"))?
.to_string(),
);

if let Some(changeset) = repo.changes.iter_mut().find(|cs| cs.pr_link == associated_pr_link) {
changeset.commits.push(change_commit.clone());
changeset.collect_approved_reviews(&pr_reviews);
continue;
}

let mut changeset = Changeset {
commits: vec![change_commit.clone()],
pr_link: associated_pr_link,
approvals: Vec::new(),
};

changeset.collect_approved_reviews(&pr_reviews);
repo.changes.push(changeset);
}
}

Ok(())
}

fn print_changes(changes: &[RepoChangeset]) {
for change in changes {
println!(
Expand Down
11 changes: 11 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use anyhow::Context;
use url::Url;

pub fn parse_remote(remote: &str) -> Result<(String, String), anyhow::Error> {
let remote_url = Url::parse(remote).context("can't parse remote")?;
let repo_parts: Vec<&str> = remote_url.path().trim_start_matches('/').split('/').collect();
let repo_owner = repo_parts[0].to_string();
let repo_name = repo_parts[1].trim_end_matches(".git").to_string();

Ok((repo_owner, repo_name))
}

0 comments on commit 54d1d65

Please sign in to comment.