diff --git a/Cargo.lock b/Cargo.lock index 2fbfe717c..c460d6783 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "anyhow" version = "1.0.32" @@ -177,6 +186,21 @@ dependencies = [ "time", ] +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "cloudabi" version = "0.1.0" @@ -186,6 +210,25 @@ dependencies = [ "bitflags", ] +[[package]] +name = "comrak" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d325e4f2ffff52ca77d995bb675494d5364aa332499d5f7c7fbb28c25e671f6" +dependencies = [ + "clap", + "entities", + "lazy_static", + "pest", + "pest_derive", + "regex", + "shell-words", + "twoway", + "typed-arena", + "unicode_categories", + "xdg", +] + [[package]] name = "core-foundation" version = "0.7.0" @@ -274,6 +317,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "entities" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" + [[package]] name = "env_logger" version = "0.7.1" @@ -1489,6 +1538,12 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "shell-words" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fa3938c99da4914afedd13bf3d79bcb6c277d1b2c398d23257a304d9e1b074" + [[package]] name = "siphasher" version = "0.3.3" @@ -1551,6 +1606,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "subtle" version = "2.2.3" @@ -1606,6 +1667,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thread_local" version = "1.0.1" @@ -1752,6 +1822,7 @@ dependencies = [ "anyhow", "async-trait", "chrono", + "comrak", "dotenv", "env_logger", "futures", @@ -1786,6 +1857,22 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "twoway" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc" +dependencies = [ + "memchr", + "unchecked-index", +] + +[[package]] +name = "typed-arena" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d" + [[package]] name = "typenum" version = "1.12.0" @@ -1798,6 +1885,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "unchecked-index" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" + [[package]] name = "unicase" version = "2.6.0" @@ -1837,6 +1930,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "url" version = "2.1.1" @@ -1864,6 +1963,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.2" @@ -2042,3 +2147,9 @@ dependencies = [ "winapi 0.2.8", "winapi-build", ] + +[[package]] +name = "xdg" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" diff --git a/Cargo.toml b/Cargo.toml index 2445a3b8f..509d1c2e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ postgres-native-tls = "0.3" native-tls = "0.2" serde_path_to_error = "0.1.2" octocrab = "0.5" +comrak = "0.8.2" [dependencies.serde] version = "1" diff --git a/src/changelogs/mod.rs b/src/changelogs/mod.rs new file mode 100644 index 000000000..2af827548 --- /dev/null +++ b/src/changelogs/mod.rs @@ -0,0 +1,46 @@ +mod rustc; + +use comrak::{nodes::AstNode, Arena, ComrakOptions, ComrakRenderOptions}; +use std::collections::HashMap; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] +pub(crate) enum ChangelogFormat { + Rustc, +} + +pub(crate) struct Changelog { + versions: HashMap, +} + +impl Changelog { + pub(crate) fn parse(format: ChangelogFormat, content: &str) -> anyhow::Result { + match format { + ChangelogFormat::Rustc => rustc::RustcFormat::new(&Arena::new()).parse(content), + } + } + + pub(crate) fn version(&self, version: &str) -> Option<&str> { + self.versions.get(version).map(|s| s.as_str()) + } +} + +fn render_for_github_releases<'a>(document: &'a AstNode<'a>) -> anyhow::Result { + let mut content = Vec::new(); + comrak::format_commonmark( + document, + &ComrakOptions { + render: ComrakRenderOptions { + // Prevent column width line breaks from appearing in the generated release + // notes. GitHub Releases insert
s for every line break in the markdown, + // mangling the output. + width: std::usize::MAX, + + ..ComrakRenderOptions::default() + }, + ..ComrakOptions::default() + }, + &mut content, + )?; + Ok(String::from_utf8(content)?) +} diff --git a/src/changelogs/rustc.rs b/src/changelogs/rustc.rs new file mode 100644 index 000000000..42dc86e54 --- /dev/null +++ b/src/changelogs/rustc.rs @@ -0,0 +1,151 @@ +use super::Changelog; +use comrak::{ + nodes::{Ast, AstNode, NodeHeading, NodeValue}, + Arena, ComrakOptions, +}; +use std::cell::RefCell; +use std::collections::HashMap; + +pub(super) struct RustcFormat<'a> { + arena: &'a Arena>, + current_h1: Option, + result: Changelog, +} + +impl<'a> RustcFormat<'a> { + pub(super) fn new(arena: &'a Arena>) -> Self { + RustcFormat { + arena, + current_h1: None, + result: Changelog { + versions: HashMap::new(), + }, + } + } + + pub(super) fn parse(mut self, content: &str) -> anyhow::Result { + let ast = comrak::parse_document(&self.arena, &content, &ComrakOptions::default()); + + let mut section_ast = Vec::new(); + for child in ast.children() { + let child_data = child.data.borrow(); + + if let NodeValue::Heading(NodeHeading { level: 1, .. }) = child_data.value { + if let Some(h1) = self.current_h1.take() { + self.store_version(h1, section_ast)?; + } + + self.current_h1 = Some(String::from_utf8(child_data.content.clone())?); + section_ast = Vec::new(); + } else { + section_ast.push(child); + } + } + if let Some(h1) = self.current_h1.take() { + self.store_version(h1, section_ast)?; + } + + Ok(self.result) + } + + fn store_version(&mut self, h1: String, body: Vec<&'a AstNode<'a>>) -> anyhow::Result<()> { + // Create a document with only the contents of this section + let document = self + .arena + .alloc(AstNode::new(RefCell::new(Ast::new(NodeValue::Document)))); + for child in &body { + document.append(child); + } + + let content = super::render_for_github_releases(document)?; + + if let Some(version) = h1.split(' ').nth(1) { + self.result.versions.insert(version.to_string(), content); + } else { + println!("skipped version, invalid header: {}", h1); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const CONTENT: &str = "\ +Version 1.45.2 (2020-08-03) +========================== + +* [Fix bindings in tuple struct patterns][74954] +* [Link in another section][69033] +* Very very very very very very very very very very very long line that has some + linebreaks here and there + +[74954]: https://github.com/rust-lang/rust/issues/74954 + +Version 1.45.1 (2020-07-30) +========================== + +* [Fix const propagation with references.][73613] +* [rustfmt accepts rustfmt_skip in cfg_attr again.][73078] + +[73613]: https://github.com/rust-lang/rust/pull/73613 +[73078]: https://github.com/rust-lang/rust/issues/73078 + +Version 1.44.0 (2020-06-04) +========================== + +Language +-------- +- [You can now use `async/.await` with `#[no_std]` enabled.][69033] + +**Syntax-only changes** + +- [Expansion-driven outline module parsing][69838] +```rust +#[cfg(FALSE)] +mod foo { + mod bar { + mod baz; // `foo/bar/baz.rs` doesn't exist, but no error! + } +} +``` + +These are still rejected semantically, so you will likely receive an error but +these changes can be seen and parsed by macros and conditional compilation. + +Internal Only +------------- +These changes provide no direct user facing benefits, but represent significant +improvements to the internals and overall performance of rustc and +related tools. + +- [dep_graph Avoid allocating a set on when the number reads are small.][69778] + +[69033]: https://github.com/rust-lang/rust/pull/69033/ +[69838]: https://github.com/rust-lang/rust/pull/69838/ +[69778]: https://github.com/rust-lang/rust/pull/69778/ +"; + + const EXPECTED_1_45_2: &str = "\ +- [Fix bindings in tuple struct patterns](https://github.com/rust-lang/rust/issues/74954) +- [Link in another section](https://github.com/rust-lang/rust/pull/69033/) +- Very very very very very very very very very very very long line that has some linebreaks here and there +"; + + #[test] + fn test_changelog_parsing() -> anyhow::Result<()> { + let arena = Arena::new(); + let parsed = RustcFormat::new(&arena).parse(CONTENT)?; + + // Ensure the right markdown is generated from each version + let version_1_45_2 = parsed.version("1.45.2").expect("missing version 1.45.2"); + assert_eq!(EXPECTED_1_45_2, version_1_45_2); + + let version_1_44_0 = parsed.version("1.44.0").expect("missing version 1.44.0"); + assert!(version_1_44_0.contains("Avoid allocating a set")); + + Ok(()) + } +} diff --git a/src/config.rs b/src/config.rs index 6654539cb..fdc6cee44 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use crate::changelogs::ChangelogFormat; use crate::github::GithubClient; use std::collections::{HashMap, HashSet}; use std::fmt; @@ -25,6 +26,7 @@ pub(crate) struct Config { pub(crate) glacier: Option, pub(crate) autolabel: Option, pub(crate) notify_zulip: Option, + pub(crate) github_releases: Option, } #[derive(PartialEq, Eq, Debug, serde::Deserialize)] @@ -150,6 +152,15 @@ pub(crate) async fn get(gh: &GithubClient, repo: &str) -> Result, Co } } +#[derive(PartialEq, Eq, Debug, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] +pub(crate) struct GitHubReleasesConfig { + pub(crate) format: ChangelogFormat, + pub(crate) project_name: String, + pub(crate) changelog_path: String, + pub(crate) changelog_branch: String, +} + fn get_cached_config(repo: &str) -> Option, ConfigurationError>> { let cache = CONFIG_CACHE.read().unwrap(); cache.get(repo).and_then(|(config, fetch_time)| { @@ -272,6 +283,7 @@ mod tests { glacier: None, autolabel: None, notify_zulip: None, + github_releases: None, } ); } diff --git a/src/github.rs b/src/github.rs index 6e262cee6..af951bef3 100644 --- a/src/github.rs +++ b/src/github.rs @@ -847,61 +847,99 @@ pub enum QueryKind { Count, } +#[derive(Debug, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum CreateKind { + Branch, + Tag, +} + +#[derive(Debug, serde::Deserialize)] +pub struct CreateEvent { + pub ref_type: CreateKind, + repository: Repository, + sender: User, +} + +#[derive(Debug, serde::Deserialize)] +pub struct PushEvent { + #[serde(rename = "ref")] + pub git_ref: String, + repository: Repository, + sender: User, +} + #[derive(Debug)] pub enum Event { + Create(CreateEvent), IssueComment(IssueCommentEvent), Issue(IssuesEvent), + Push(PushEvent), } impl Event { pub fn repo_name(&self) -> &str { match self { + Event::Create(event) => &event.repository.full_name, Event::IssueComment(event) => &event.repository.full_name, Event::Issue(event) => &event.repository.full_name, + Event::Push(event) => &event.repository.full_name, } } pub fn issue(&self) -> Option<&Issue> { match self { + Event::Create(_) => None, Event::IssueComment(event) => Some(&event.issue), Event::Issue(event) => Some(&event.issue), + Event::Push(_) => None, } } /// This will both extract from IssueComment events but also Issue events pub fn comment_body(&self) -> Option<&str> { match self { + Event::Create(_) => None, Event::Issue(e) => Some(&e.issue.body), Event::IssueComment(e) => Some(&e.comment.body), + Event::Push(_) => None, } } /// This will both extract from IssueComment events but also Issue events pub fn comment_from(&self) -> Option<&str> { match self { + Event::Create(_) => None, Event::Issue(e) => Some(&e.changes.as_ref()?.body.from), Event::IssueComment(e) => Some(&e.changes.as_ref()?.body.from), + Event::Push(_) => None, } } pub fn html_url(&self) -> Option<&str> { match self { + Event::Create(_) => None, Event::Issue(e) => Some(&e.issue.html_url), Event::IssueComment(e) => Some(&e.comment.html_url), + Event::Push(_) => None, } } pub fn user(&self) -> &User { match self { + Event::Create(e) => &e.sender, Event::Issue(e) => &e.issue.user, Event::IssueComment(e) => &e.comment.user, + Event::Push(e) => &e.sender, } } - pub fn time(&self) -> chrono::DateTime { + pub fn time(&self) -> Option> { match self { - Event::Issue(e) => e.issue.created_at.into(), - Event::IssueComment(e) => e.comment.updated_at.into(), + Event::Create(_) => None, + Event::Issue(e) => Some(e.issue.created_at.into()), + Event::IssueComment(e) => Some(e.comment.updated_at.into()), + Event::Push(_) => None, } } } diff --git a/src/handlers.rs b/src/handlers.rs index f4261df59..fdf0be3b1 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -25,6 +25,7 @@ impl fmt::Display for HandlerError { mod assign; mod autolabel; +mod github_releases; mod glacier; mod major_change; mod milestone_prs; @@ -72,6 +73,20 @@ pub async fn handle(ctx: &Context, event: &Event) -> Vec { ); } + if let Some(ghr_config) = config + .as_ref() + .ok() + .and_then(|c| c.github_releases.as_ref()) + { + if let Err(e) = github_releases::handle(ctx, event, ghr_config).await { + log::error!( + "failed to process event {:?} with github_releases handler: {:?}", + event, + e + ); + } + } + errors } @@ -134,6 +149,10 @@ macro_rules! command_handlers { log::debug!("skipping event, comment was {:?}", e.action); return; } + Event::Push(_) | Event::Create(_) => { + log::debug!("skipping unsupported event"); + return; + } } let input = Input::new(&body, &ctx.username); diff --git a/src/handlers/github_releases.rs b/src/handlers/github_releases.rs new file mode 100644 index 000000000..936bfeed3 --- /dev/null +++ b/src/handlers/github_releases.rs @@ -0,0 +1,167 @@ +use crate::{ + changelogs::Changelog, + config::GitHubReleasesConfig, + github::{CreateEvent, CreateKind, Event}, + handlers::Context, +}; +use anyhow::Context as _; +use octocrab::Page; +use std::{collections::HashMap, time::Duration}; + +pub(super) async fn handle( + ctx: &Context, + event: &Event, + config: &GitHubReleasesConfig, +) -> anyhow::Result<()> { + // Only allow commit pushed to the changelog branch or tags being created. + match event { + Event::Push(push) if push.git_ref == format!("refs/heads/{}", config.changelog_branch) => {} + Event::Create(CreateEvent { + ref_type: CreateKind::Tag, + .. + }) => {} + _ => return Ok(()), + } + + log::info!("handling github releases"); + + log::debug!("loading the changelog"); + let content = load_changelog(ctx, event, config).await.with_context(|| { + format!( + "failed to load changelog file {} from repo {} in branch {}", + config.changelog_path, + event.repo_name(), + config.changelog_branch + ) + })?; + let changelog = Changelog::parse(config.format, &content)?; + + log::debug!("loading the git tags"); + let tags = load_paginated( + ctx, + &format!("repos/{}/git/matching-refs/tags", event.repo_name()), + |git_ref: &GitRef| { + git_ref + .name + .strip_prefix("refs/tags/") + .unwrap_or(git_ref.name.as_str()) + .to_string() + }, + ) + .await?; + + log::debug!("loading the existing releases"); + let releases = load_paginated( + ctx, + &format!("repos/{}/releases", event.repo_name()), + |release: &Release| release.tag_name.clone(), + ) + .await?; + + for tag in tags.keys() { + if let Some(expected_body) = changelog.version(tag) { + let expected_name = format!("{} {}", config.project_name, tag); + + if let Some(release) = releases.get(tag) { + if release.name != expected_name || release.body != expected_body { + log::info!("updating release {} on {}", tag, event.repo_name()); + let _: serde_json::Value = ctx + .octocrab + .patch( + &release.url, + Some(&serde_json::json!({ + "name": expected_name, + "body": expected_body, + })), + ) + .await?; + } else { + // Avoid waiting for the delay below. + continue; + } + } else { + log::info!("creating release {} on {}", tag, event.repo_name()); + let _: serde_json::Value = ctx + .octocrab + .post( + format!("repos/{}/releases", event.repo_name()), + Some(&serde_json::json!({ + "tag_name": tag, + "name": expected_name, + "body": expected_body, + })), + ) + .await?; + } + + log::debug!("sleeping for one second to avoid hitting any rate limit"); + tokio::time::delay_for(Duration::from_secs(1)).await; + } else { + log::trace!( + "skipping tag {} since it doesn't have a changelog entry", + tag + ); + } + } + + Ok(()) +} + +async fn load_changelog( + ctx: &Context, + event: &Event, + config: &GitHubReleasesConfig, +) -> anyhow::Result { + let resp = ctx + .github + .raw_file( + event.repo_name(), + &config.changelog_branch, + &config.changelog_path, + ) + .await? + .ok_or_else(|| anyhow::Error::msg("missing file"))?; + + Ok(String::from_utf8(resp)?) +} + +async fn load_paginated(ctx: &Context, url: &str, key: F) -> anyhow::Result> +where + T: serde::de::DeserializeOwned, + R: Eq + PartialEq + std::hash::Hash, + F: Fn(&T) -> R, +{ + let mut current_page: Page = ctx.octocrab.get::, _, ()>(url, None).await?; + + let mut items = current_page + .take_items() + .into_iter() + .map(|val| (key(&val), val)) + .collect::>(); + + while let Some(mut new_page) = ctx.octocrab.get_page::(¤t_page.next).await? { + items.extend( + new_page + .take_items() + .into_iter() + .map(|val| (key(&val), val)), + ); + current_page = new_page; + } + + Ok(items) +} + +#[derive(Debug, serde::Deserialize)] +struct GitRef { + #[serde(rename = "ref")] + name: String, +} + +#[derive(Debug, serde::Deserialize)] +struct Release { + url: String, + tag_name: String, + name: String, + body: String, +} diff --git a/src/handlers/notification.rs b/src/handlers/notification.rs index 398f4f501..e02e6f5e3 100644 --- a/src/handlers/notification.rs +++ b/src/handlers/notification.rs @@ -33,6 +33,7 @@ pub async fn handle(ctx: &Context, event: &Event) -> anyhow::Result<()> { let short_description = match event { Event::Issue(e) => e.issue.title.clone(), Event::IssueComment(e) => format!("Comment on {}", e.issue.title), + Event::Push(_) | Event::Create(_) => return Ok(()), }; let mut caps = parser::get_mentions(body) @@ -110,7 +111,7 @@ pub async fn handle(ctx: &Context, event: &Event) -> anyhow::Result<()> { user_id: user.id.unwrap(), origin_url: event.html_url().unwrap().to_owned(), origin_html: body.to_owned(), - time: event.time(), + time: event.time().unwrap(), short_description: Some(short_description.clone()), team_name: team_name.clone(), }, diff --git a/src/lib.rs b/src/lib.rs index ea09fc9b8..f90576c54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ use std::fmt; pub mod actions; pub mod agenda; +mod changelogs; pub mod config; pub mod db; pub mod github; @@ -29,6 +30,8 @@ pub enum EventName { PullRequestReviewComment, IssueComment, Issue, + Push, + Create, Other, } @@ -41,6 +44,8 @@ impl std::str::FromStr for EventName { "issue_comment" => EventName::IssueComment, "pull_request" => EventName::PullRequest, "issues" => EventName::Issue, + "push" => EventName::Push, + "create" => EventName::Create, _ => EventName::Other, }) } @@ -57,6 +62,8 @@ impl fmt::Display for EventName { EventName::IssueComment => "issue_comment", EventName::Issue => "issues", EventName::PullRequest => "pull_request", + EventName::Push => "push", + EventName::Create => "create", EventName::Other => "other", } ) @@ -150,6 +157,24 @@ pub async fn webhook( github::Event::Issue(payload) } + EventName::Push => { + let payload = deserialize_payload::(&payload) + .with_context(|| format!("{:?} failed to deserialize", event)) + .map_err(anyhow::Error::from)?; + + log::info!("handling push event {:?}", payload); + + github::Event::Push(payload) + } + EventName::Create => { + let payload = deserialize_payload::(&payload) + .with_context(|| format!("{:?} failed to deserialize", event)) + .map_err(anyhow::Error::from)?; + + log::info!("handling create event {:?}", payload); + + github::Event::Create(payload) + } // Other events need not be handled EventName::Other => { return Ok(false);