diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9266c5f11..4c4864fa32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -205,6 +205,7 @@ jobs: echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}" exit 1 fi + script/draft-release-notes "$version" "$channel" > target/release-notes.md - name: Generate license file run: script/generate-licenses @@ -248,7 +249,7 @@ jobs: target/aarch64-apple-darwin/release/Zed-aarch64.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg target/release/Zed.dmg - body: "" + body_file: target/release-notes.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/src/developing_zed__releases.md b/docs/src/developing_zed__releases.md index 0c3484769a..f57aa794a2 100644 --- a/docs/src/developing_zed__releases.md +++ b/docs/src/developing_zed__releases.md @@ -17,25 +17,31 @@ You will need write access to the Zed repository to do this: - Run `./script/bump-zed-minor-versions` and push the tags and branches as instructed. - Wait for the builds to appear at https://github.com/zed-industries/zed/releases (typically takes around 30 minutes) -- Copy the release notes from the previous Preview release(s) to the current Stable release. -- Write new release notes for Preview. `/script/get-preview-channel-changes` can help with this, but you'll need to edit and format the output to make it good. -- Download the artifacts for each release and test that you can run them locally. -- Publish the releases. +- While you're waiting: + - Start creating the new release notes for preview. You can start with the output of `./script/get-preview-channel-changes`. + - Start drafting the release tweets. +- Once the builds are ready: + - Copy the release notes from the previous Preview release(s) to the current Stable release. + - Download the artifacts for each release and test that you can run them locally. + - Publish the releases on GitHub. + - Tweet the tweets (Credentials are in 1password). ## Patch release process -If your PR fixes a panic or a crash, you should cherry-pick it to the current stable and preview branches. If your PR fixes a regression in recently released code, you should cherry-pick it to the appropriate branch. +If your PR fixes a panic or a crash, you should cherry-pick it to the current stable and preview branches. If your PR fixes a regression in recently released code, you should cherry-pick it to preview. You will need write access to the Zed repository to do this: -- Cherry pick them onto the correct branch. You can either do this manually, or leave a comment of the form `/cherry-pick v0.XXX.x` on the PR, and the GitHub bot should do it for you. -- Run `./script/trigger-release {preview|stable}` +- Send a PR containing your change to `main` as normal. +- Leave a comment on the PR `/cherry-pick v0.XXX.x`. Once your PR is merged, the Github bot will send a PR to the branch. + - In case of a merge conflict, you will have to cherry-pick manually and push the change to the `v0.XXX.x` branch. +- After the commits are cherry-picked onto the branch, run `./script/trigger-release {preview|stable}`. This will bump the version numbers, create a new release tag, and kick off a release build. - Wait for the builds to appear at https://github.com/zed-industries/zed/releases (typically takes around 30 minutes) -- Add release notes using the `Release notes:` section of each cherry-picked PR. +- Proof-read and edit the release notes as needed. - Download the artifacts for each release and test that you can run them locally. - Publish the release. ## Nightly release process -- Merge your changes to main -- Run `./script/trigger-release {nightly}` +In addition to the public releases, we also have a nightly build that we encourage employees to use. +Nightly is released by cron once a day, and can be shipped as often as you'd like. There are no release notes or announcements, so you can just merge your changes to main and run `./script/trigger-release nightly`. diff --git a/script/draft-release-notes b/script/draft-release-notes new file mode 100755 index 0000000000..b376044106 --- /dev/null +++ b/script/draft-release-notes @@ -0,0 +1,109 @@ +#!/usr/bin/env node --redirect-warnings=/dev/null + +const { execFileSync } = require("child_process"); + +main(); + +async function main() { + let version = process.argv[2]; + let channel = process.argv[3]; + let parts = version.split("."); + + if ( + process.argv.length != 4 || + parts.length != 3 || + parts.find((part) => isNaN(part)) != null || + (channel != "stable" && channel != "preview") + ) { + console.log("Usage: draft-release-notes {stable|preview}"); + process.exit(1); + } + + let priorVersion = [parts[0], parts[1], parts[2] - 1].join("."); + let suffix = ""; + + if (channel == "preview") { + suffix = "-pre"; + if (parts[2] == 0) { + priorVersion = [parts[0], parts[1] - 1, 0].join("."); + } + } else if (!tagExists("v${priorVersion}")) { + console.log("Copy the release notes from preview."); + process.exit(0); + } + + let [tag, priorTag] = [`v${version}${suffix}`, `v${priorVersion}${suffix}`]; + + const newCommits = getCommits(priorTag, tag); + + let releaseNotes = []; + let missing = []; + let skipped = []; + + for (const commit of newCommits) { + let link = "https://github.com/zed-industries/zed/pull/" + commit.pr; + let notes = commit.releaseNotes; + if (commit.pr == "") { + link = "https://github.com/zed-industries/zed/commits/" + commit.hash; + } else if (!notes.includes("zed-industries/zed/issues")) { + notes = notes + " ([#" + commit.pr + "](" + link + "))"; + } + + if (commit.releaseNotes == "") { + missing.push("- MISSING " + commit.firstLine + " " + link); + } else if (commit.releaseNotes.startsWith("- N/A")) { + skipped.push("- N/A " + commit.firstLine + " " + link); + } else { + releaseNotes.push(notes); + } + } + + console.log(releaseNotes.join("\n") + "\n"); + console.log(""); +} + +function getCommits(oldTag, newTag) { + const pullRequestNumbers = execFileSync( + "git", + ["log", `${oldTag}..${newTag}`, "--format=DIVIDER\n%H|||%B"], + { encoding: "utf8" }, + ) + .replace(/\r\n/g, "\n") + .split("DIVIDER\n") + .filter((commit) => commit.length > 0) + .map((commit) => { + let [hash, firstLine] = commit.split("\n")[0].split("|||"); + let cherryPick = firstLine.match(/\(cherry-pick #([0-9]+)\)/)?.[1] || ""; + let pr = firstLine.match(/\(#(\d+)\)$/)?.[1] || ""; + let releaseNotes = (commit.split(/Release notes:.*\n/i)[1] || "") + .split("\n\n")[0] + .trim() + .replace(/\n(?![\n-])/g, " "); + + if (releaseNotes.includes("")) { + releaseNotes = ""; + } + + return { + hash, + pr, + cherryPick, + releaseNotes, + firstLine, + }; + }); + + return pullRequestNumbers; +} + +function tagExists(tag) { + try { + execFileSync("git", ["rev-parse", "--verify", tag]); + return true; + } catch (e) { + return false; + } +}