Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
120 changes: 120 additions & 0 deletions crates/xtask/src/changelog.rs
Original file line number Diff line number Diff line change
@@ -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, &current, &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 changes: 4 additions & 1 deletion crates/xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()>)),*]
};
}
Expand All @@ -23,6 +25,7 @@ fn main() -> Result<()> {
"semver-checks" => semver_checks,
"eslint" => eslint,
"gui" => gui,
"changelog" => changelog::changelog,
}
.into_iter()
.collect();
Expand Down
Loading