Skip to content

Commit 21dc3b0

Browse files
authored
Merge pull request #2867 from ehuss/xtask-changelog
Add a script to help update the changelog
2 parents 78819f4 + 2a93606 commit 21dc3b0

File tree

3 files changed

+126
-2
lines changed

3 files changed

+126
-2
lines changed

CONTRIBUTING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,8 @@ Instructions for mdBook maintainers to publish a new release:
224224
1. Create a PR to update the version and update the CHANGELOG:
225225
1. Update the version in `Cargo.toml`
226226
2. Run `cargo xtask test-all` to verify that everything is passing, and to update `Cargo.lock`.
227-
3. Update `CHANGELOG.md` with any changes that users may be interested in.
227+
3. Run `cargo xtask changelog` to add a new entry to the changelog.
228+
1. This will add a list of all changes at the top. You will need to move those into the appropriate categories. Most changes that are generally not relevant to a user should be removed. Rewrite the descriptions so that a user can reasonably figure out what it means.
228229
4. Commit the changes, and open a PR.
229230
2. After the PR has been merged, create a release in GitHub. This can either be done in the GitHub web UI, or on the command-line:
230231
```bash

crates/xtask/src/changelog.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//! Helper to generate a changelog for a new release.
2+
3+
use super::Result;
4+
use std::fs;
5+
use std::process::Command;
6+
use std::process::exit;
7+
8+
const CHANGELOG_PATH: &str = "CHANGELOG.md";
9+
10+
pub(crate) fn changelog() -> Result<()> {
11+
let previous = get_previous()?;
12+
let current = get_current()?;
13+
if current == previous {
14+
eprintln!(
15+
"error: Current version is `{current}` which is the same as the \
16+
previous version in the changelog. Run `cargo set-version --bump <BUMP> first."
17+
);
18+
exit(1);
19+
}
20+
let prs = get_prs(&previous)?;
21+
update_changelog(&previous, &current, &prs)?;
22+
Ok(())
23+
}
24+
25+
fn get_previous() -> Result<String> {
26+
let contents = fs::read_to_string(CHANGELOG_PATH)?;
27+
let version = contents
28+
.lines()
29+
.filter_map(|line| line.strip_prefix("## mdBook "))
30+
.next()
31+
.expect("at least one entry")
32+
.to_owned();
33+
Ok(version)
34+
}
35+
36+
fn get_current() -> Result<String> {
37+
let contents = fs::read_to_string("Cargo.toml")?;
38+
let mut lines = contents
39+
.lines()
40+
.filter_map(|line| line.strip_prefix("version = "))
41+
.map(|version| &version[1..version.len() - 1]);
42+
let version = lines.next().expect("version should exist").to_owned();
43+
assert_eq!(lines.next(), None);
44+
Ok(version)
45+
}
46+
47+
fn get_prs(previous: &str) -> Result<Vec<(String, String)>> {
48+
println!("running `git fetch upstream`");
49+
let status = Command::new("git").args(["fetch", "upstream"]).status()?;
50+
if !status.success() {
51+
eprintln!("error: git fetch failed");
52+
exit(1);
53+
}
54+
println!("running `git log`");
55+
const SEPARATOR: &str = "---COMMIT_SEPARATOR---";
56+
let output = Command::new("git")
57+
.args([
58+
"log",
59+
"--first-parent",
60+
&format!("--pretty=format:%B%n{SEPARATOR}"),
61+
"upstream/master",
62+
&format!("v{previous}...upstream/HEAD"),
63+
])
64+
.output()?;
65+
if !output.status.success() {
66+
eprintln!("error: git log failed");
67+
exit(1);
68+
}
69+
let stdout = std::str::from_utf8(&output.stdout).unwrap();
70+
let prs = stdout
71+
.split(&format!("{SEPARATOR}\n"))
72+
.filter_map(|entry| {
73+
let mut lines = entry.lines();
74+
let first = match lines.next().unwrap().strip_prefix("Merge pull request #") {
75+
Some(f) => f,
76+
None => {
77+
println!("warning: merge line not found in {entry}");
78+
return None;
79+
}
80+
};
81+
let number = first.split_whitespace().next().unwrap();
82+
assert_eq!(lines.next(), Some(""));
83+
let title = lines.next().expect("title is set");
84+
assert_eq!(lines.next(), Some(""));
85+
Some((number.to_string(), title.to_string()))
86+
})
87+
.collect();
88+
Ok(prs)
89+
}
90+
91+
fn update_changelog(previous: &str, current: &str, prs: &[(String, String)]) -> Result<()> {
92+
let prs: String = prs
93+
.iter()
94+
.map(|(number, title)| {
95+
format!(
96+
"- {title}\n \
97+
[#{number}](https://github.com/rust-lang/mdBook/pull/{number})\n"
98+
)
99+
})
100+
.collect();
101+
let new = format!(
102+
"## mdBook {current}\n\
103+
[v{previous}...v{current}](https://github.com/rust-lang/mdBook/compare/v{previous}...v{current})\n\
104+
\n\
105+
{prs}\
106+
\n\
107+
### Added\n\
108+
\n\
109+
### Changed\n\
110+
\n\
111+
### Fixed\n\
112+
\n"
113+
);
114+
115+
let mut contents = fs::read_to_string(CHANGELOG_PATH)?;
116+
let insertion_point = contents.find("## ").unwrap();
117+
contents.insert_str(insertion_point, &new);
118+
fs::write(CHANGELOG_PATH, contents)?;
119+
Ok(())
120+
}

crates/xtask/src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ use std::error::Error;
55
use std::process::Command;
66
use std::process::exit;
77

8+
mod changelog;
9+
810
type Result<T> = std::result::Result<T, Box<dyn Error>>;
911

1012
fn main() -> Result<()> {
1113
macro_rules! commands {
12-
($($name:literal => $func:ident),* $(,)?) => {
14+
($($name:literal => $func:expr),* $(,)?) => {
1315
[$(($name, $func as fn() -> Result<()>)),*]
1416
};
1517
}
@@ -23,6 +25,7 @@ fn main() -> Result<()> {
2325
"semver-checks" => semver_checks,
2426
"eslint" => eslint,
2527
"gui" => gui,
28+
"changelog" => changelog::changelog,
2629
}
2730
.into_iter()
2831
.collect();

0 commit comments

Comments
 (0)