mirror of
https://github.com/rust-lang/mdBook.git
synced 2025-12-27 10:16:09 -05:00
Merge pull request #2867 from ehuss/xtask-changelog
Add a script to help update the changelog
This commit is contained in:
@@ -224,7 +224,8 @@ Instructions for mdBook maintainers to publish a new release:
|
||||
1. Create a PR to update the version and update the CHANGELOG:
|
||||
1. Update the version in `Cargo.toml`
|
||||
2. Run `cargo xtask test-all` to verify that everything is passing, and to update `Cargo.lock`.
|
||||
3. Update `CHANGELOG.md` with any changes that users may be interested in.
|
||||
3. Run `cargo xtask changelog` to add a new entry to the changelog.
|
||||
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.
|
||||
4. Commit the changes, and open a PR.
|
||||
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:
|
||||
```bash
|
||||
|
||||
120
crates/xtask/src/changelog.rs
Normal file
120
crates/xtask/src/changelog.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
//! Helper to generate a changelog for a new release.
|
||||
|
||||
use super::Result;
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use std::process::exit;
|
||||
|
||||
const CHANGELOG_PATH: &str = "CHANGELOG.md";
|
||||
|
||||
pub(crate) fn changelog() -> Result<()> {
|
||||
let previous = get_previous()?;
|
||||
let current = get_current()?;
|
||||
if current == previous {
|
||||
eprintln!(
|
||||
"error: Current version is `{current}` which is the same as the \
|
||||
previous version in the changelog. Run `cargo set-version --bump <BUMP> first."
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
let prs = get_prs(&previous)?;
|
||||
update_changelog(&previous, ¤t, &prs)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_previous() -> Result<String> {
|
||||
let contents = fs::read_to_string(CHANGELOG_PATH)?;
|
||||
let version = contents
|
||||
.lines()
|
||||
.filter_map(|line| line.strip_prefix("## mdBook "))
|
||||
.next()
|
||||
.expect("at least one entry")
|
||||
.to_owned();
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
fn get_current() -> Result<String> {
|
||||
let contents = fs::read_to_string("Cargo.toml")?;
|
||||
let mut lines = contents
|
||||
.lines()
|
||||
.filter_map(|line| line.strip_prefix("version = "))
|
||||
.map(|version| &version[1..version.len() - 1]);
|
||||
let version = lines.next().expect("version should exist").to_owned();
|
||||
assert_eq!(lines.next(), None);
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
fn get_prs(previous: &str) -> Result<Vec<(String, String)>> {
|
||||
println!("running `git fetch upstream`");
|
||||
let status = Command::new("git").args(["fetch", "upstream"]).status()?;
|
||||
if !status.success() {
|
||||
eprintln!("error: git fetch failed");
|
||||
exit(1);
|
||||
}
|
||||
println!("running `git log`");
|
||||
const SEPARATOR: &str = "---COMMIT_SEPARATOR---";
|
||||
let output = Command::new("git")
|
||||
.args([
|
||||
"log",
|
||||
"--first-parent",
|
||||
&format!("--pretty=format:%B%n{SEPARATOR}"),
|
||||
"upstream/master",
|
||||
&format!("v{previous}...upstream/HEAD"),
|
||||
])
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
eprintln!("error: git log failed");
|
||||
exit(1);
|
||||
}
|
||||
let stdout = std::str::from_utf8(&output.stdout).unwrap();
|
||||
let prs = stdout
|
||||
.split(&format!("{SEPARATOR}\n"))
|
||||
.filter_map(|entry| {
|
||||
let mut lines = entry.lines();
|
||||
let first = match lines.next().unwrap().strip_prefix("Merge pull request #") {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
println!("warning: merge line not found in {entry}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let number = first.split_whitespace().next().unwrap();
|
||||
assert_eq!(lines.next(), Some(""));
|
||||
let title = lines.next().expect("title is set");
|
||||
assert_eq!(lines.next(), Some(""));
|
||||
Some((number.to_string(), title.to_string()))
|
||||
})
|
||||
.collect();
|
||||
Ok(prs)
|
||||
}
|
||||
|
||||
fn update_changelog(previous: &str, current: &str, prs: &[(String, String)]) -> Result<()> {
|
||||
let prs: String = prs
|
||||
.iter()
|
||||
.map(|(number, title)| {
|
||||
format!(
|
||||
"- {title}\n \
|
||||
[#{number}](https://github.com/rust-lang/mdBook/pull/{number})\n"
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let new = format!(
|
||||
"## mdBook {current}\n\
|
||||
[v{previous}...v{current}](https://github.com/rust-lang/mdBook/compare/v{previous}...v{current})\n\
|
||||
\n\
|
||||
{prs}\
|
||||
\n\
|
||||
### Added\n\
|
||||
\n\
|
||||
### Changed\n\
|
||||
\n\
|
||||
### Fixed\n\
|
||||
\n"
|
||||
);
|
||||
|
||||
let mut contents = fs::read_to_string(CHANGELOG_PATH)?;
|
||||
let insertion_point = contents.find("## ").unwrap();
|
||||
contents.insert_str(insertion_point, &new);
|
||||
fs::write(CHANGELOG_PATH, contents)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -5,11 +5,13 @@ use std::error::Error;
|
||||
use std::process::Command;
|
||||
use std::process::exit;
|
||||
|
||||
mod changelog;
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
macro_rules! commands {
|
||||
($($name:literal => $func:ident),* $(,)?) => {
|
||||
($($name:literal => $func:expr),* $(,)?) => {
|
||||
[$(($name, $func as fn() -> Result<()>)),*]
|
||||
};
|
||||
}
|
||||
@@ -23,6 +25,7 @@ fn main() -> Result<()> {
|
||||
"semver-checks" => semver_checks,
|
||||
"eslint" => eslint,
|
||||
"gui" => gui,
|
||||
"changelog" => changelog::changelog,
|
||||
}
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
Reference in New Issue
Block a user