summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/xtask/src/release/changelog.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /src/tools/rust-analyzer/xtask/src/release/changelog.rs
parentInitial commit. (diff)
downloadrustc-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.rs171
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()) }
+}