diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /src/tools/rust-analyzer/xtask/src/release/changelog.rs | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/tools/rust-analyzer/xtask/src/release/changelog.rs | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/xtask/src/release/changelog.rs b/src/tools/rust-analyzer/xtask/src/release/changelog.rs new file mode 100644 index 000000000..2647f7794 --- /dev/null +++ b/src/tools/rust-analyzer/xtask/src/release/changelog.rs @@ -0,0 +1,171 @@ +use std::fmt::Write; +use std::{env, iter}; + +use anyhow::bail; +use xshell::{cmd, Shell}; + +pub(crate) fn get_changelog( + sh: &Shell, + changelog_n: usize, + commit: &str, + prev_tag: &str, + today: &str, +) -> anyhow::Result<String> { + let token = match env::var("GITHUB_TOKEN") { + Ok(token) => token, + Err(_) => bail!("Please obtain a personal access token from https://github.com/settings/tokens and set the `GITHUB_TOKEN` environment variable."), + }; + + let git_log = cmd!(sh, "git log {prev_tag}..HEAD --reverse").read()?; + let mut features = String::new(); + let mut fixes = String::new(); + let mut internal = String::new(); + let mut others = String::new(); + for line in git_log.lines() { + let line = line.trim_start(); + if let Some(pr_num) = parse_pr_number(&line) { + let accept = "Accept: application/vnd.github.v3+json"; + let authorization = format!("Authorization: token {}", token); + let pr_url = "https://api.github.com/repos/rust-lang/rust-analyzer/issues"; + + // we don't use an HTTPS client or JSON parser to keep the build times low + let pr = pr_num.to_string(); + let pr_json = + cmd!(sh, "curl -s -H {accept} -H {authorization} {pr_url}/{pr}").read()?; + let pr_title = cmd!(sh, "jq .title").stdin(&pr_json).read()?; + let pr_title = unescape(&pr_title[1..pr_title.len() - 1]); + let pr_comment = cmd!(sh, "jq .body").stdin(pr_json).read()?; + + let comments_json = + cmd!(sh, "curl -s -H {accept} -H {authorization} {pr_url}/{pr}/comments").read()?; + let pr_comments = cmd!(sh, "jq .[].body").stdin(comments_json).read()?; + + let l = iter::once(pr_comment.as_str()) + .chain(pr_comments.lines()) + .rev() + .find_map(|it| { + let it = unescape(&it[1..it.len() - 1]); + it.lines().find_map(parse_changelog_line) + }) + .into_iter() + .next() + .unwrap_or_else(|| parse_title_line(&pr_title)); + let s = match l.kind { + PrKind::Feature => &mut features, + PrKind::Fix => &mut fixes, + PrKind::Internal => &mut internal, + PrKind::Other => &mut others, + PrKind::Skip => continue, + }; + writeln!(s, "* pr:{}[] {}", pr_num, l.message.as_deref().unwrap_or(&pr_title)).unwrap(); + } + } + + let contents = format!( + "\ += Changelog #{} +:sectanchors: +:page-layout: post + +Commit: commit:{}[] + +Release: release:{}[] + +== New Features + +{} + +== Fixes + +{} + +== Internal Improvements + +{} + +== Others + +{} +", + changelog_n, commit, today, features, fixes, internal, others + ); + Ok(contents) +} + +#[derive(Clone, Copy)] +enum PrKind { + Feature, + Fix, + Internal, + Other, + Skip, +} + +struct PrInfo { + message: Option<String>, + kind: PrKind, +} + +fn unescape(s: &str) -> String { + s.replace(r#"\""#, "").replace(r#"\n"#, "\n").replace(r#"\r"#, "") +} + +fn parse_pr_number(s: &str) -> Option<u32> { + const BORS_PREFIX: &str = "Merge #"; + const HOMU_PREFIX: &str = "Auto merge of #"; + if s.starts_with(BORS_PREFIX) { + let s = &s[BORS_PREFIX.len()..]; + s.parse().ok() + } else if s.starts_with(HOMU_PREFIX) { + let s = &s[HOMU_PREFIX.len()..]; + if let Some(space) = s.find(' ') { + s[..space].parse().ok() + } else { + None + } + } else { + None + } +} + +fn parse_changelog_line(s: &str) -> Option<PrInfo> { + let parts = s.splitn(3, ' ').collect::<Vec<_>>(); + if parts.len() < 2 || parts[0] != "changelog" { + return None; + } + let message = parts.get(2).map(|it| it.to_string()); + let kind = match parts[1].trim_end_matches(':') { + "feature" => PrKind::Feature, + "fix" => PrKind::Fix, + "internal" => PrKind::Internal, + "skip" => PrKind::Skip, + _ => { + let kind = PrKind::Other; + let message = format!("{} {}", parts[1], message.unwrap_or_default()); + return Some(PrInfo { kind, message: Some(message) }); + } + }; + let res = PrInfo { message, kind }; + Some(res) +} + +fn parse_title_line(s: &str) -> PrInfo { + let lower = s.to_ascii_lowercase(); + const PREFIXES: [(&str, PrKind); 5] = [ + ("feat: ", PrKind::Feature), + ("feature: ", PrKind::Feature), + ("fix: ", PrKind::Fix), + ("internal: ", PrKind::Internal), + ("minor: ", PrKind::Skip), + ]; + + for &(prefix, kind) in &PREFIXES { + if lower.starts_with(prefix) { + let message = match &kind { + PrKind::Skip => None, + _ => Some(s[prefix.len()..].to_string()), + }; + return PrInfo { message, kind }; + } + } + PrInfo { kind: PrKind::Other, message: Some(s.to_string()) } +} |