Merge pull request #10370 from Rylan12/brew-release

Add brew release command
This commit is contained in:
Rylan Polster 2021-01-23 15:35:53 -05:00 committed by GitHub
commit 2c83ea7339
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 242 additions and 30 deletions

View File

@ -172,7 +172,7 @@ update-preinstall() {
if [[ "$HOMEBREW_COMMAND" = "install" || "$HOMEBREW_COMMAND" = "upgrade" || if [[ "$HOMEBREW_COMMAND" = "install" || "$HOMEBREW_COMMAND" = "upgrade" ||
"$HOMEBREW_COMMAND" = "bump-formula-pr" || "$HOMEBREW_COMMAND" = "bump-cask-pr" || "$HOMEBREW_COMMAND" = "bump-formula-pr" || "$HOMEBREW_COMMAND" = "bump-cask-pr" ||
"$HOMEBREW_COMMAND" = "bundle" || "$HOMEBREW_COMMAND" = "bundle" || "$HOMEBREW_COMMAND" = "release" ||
"$HOMEBREW_COMMAND" = "tap" && $HOMEBREW_ARG_COUNT -gt 1 || "$HOMEBREW_COMMAND" = "tap" && $HOMEBREW_ARG_COUNT -gt 1 ||
"$HOMEBREW_CASK_COMMAND" = "install" || "$HOMEBREW_CASK_COMMAND" = "upgrade" ]] "$HOMEBREW_CASK_COMMAND" = "install" || "$HOMEBREW_CASK_COMMAND" = "upgrade" ]]
then then

View File

@ -138,6 +138,12 @@ module Homebrew
sig { returns(T.nilable(T::Boolean)) } sig { returns(T.nilable(T::Boolean)) }
def reset_cache?; end def reset_cache?; end
sig { returns(T.nilable(T::Boolean)) }
def major?; end
sig { returns(T.nilable(T::Boolean)) }
def minor?; end
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
def tag; end def tag; end

View File

@ -2,6 +2,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cli/parser" require "cli/parser"
require "release_notes"
module Homebrew module Homebrew
extend T::Sig extend T::Sig
@ -31,6 +32,9 @@ module Homebrew
def release_notes def release_notes
args = release_notes_args.parse args = release_notes_args.parse
# TODO: (2.8) Deprecate this command now that the `brew release` command exists.
# odeprecated "`brew release-notes`"
previous_tag = args.named.first previous_tag = args.named.first
if previous_tag.present? if previous_tag.present?
@ -55,25 +59,9 @@ module Homebrew
odie "Ref #{ref} does not exist!" odie "Ref #{ref} does not exist!"
end end
output = Utils.popen_read( release_notes = ReleaseNotes.generate_release_notes previous_tag, end_ref, markdown: args.markdown?
"git", "-C", HOMEBREW_REPOSITORY, "log", "--pretty=format:'%s >> - %b%n'", "#{previous_tag}..#{end_ref}"
).lines.grep(/Merge pull request/)
output.map! do |s|
s.gsub(%r{.*Merge pull request #(\d+) from ([^/]+)/[^>]*(>>)*},
"https://github.com/Homebrew/brew/pull/\\1 (@\\2)")
end
if args.markdown?
output.map! do |s|
/(.*\d)+ \(@(.+)\) - (.*)/ =~ s
"- [#{Regexp.last_match(3)}](#{Regexp.last_match(1)}) (@#{Regexp.last_match(2)})"
end
end
$stderr.puts "Release notes between #{previous_tag} and #{end_ref}:" $stderr.puts "Release notes between #{previous_tag} and #{end_ref}:"
if args.markdown? && args.named.first puts release_notes
puts "Release notes for major and minor releases can be found in the [Homebrew blog](https://brew.sh/blog/)."
end
puts output
end end
end end

View File

@ -0,0 +1,88 @@
# typed: true
# frozen_string_literal: true
require "cli/parser"
require "release_notes"
module Homebrew
extend T::Sig
module_function
sig { returns(CLI::Parser) }
def release_args
Homebrew::CLI::Parser.new do
description <<~EOS
Create a new draft Homebrew/brew release with the appropriate version number and release notes.
By default, `brew release` will bump the patch version number. Pass
`--major` or `--minor` to bump the major or minor version numbers, respectively.
The command will fail if the previous major or minor release was made less than
one month ago.
Requires write access to the Homebrew/brew repository.
EOS
switch "--major",
description: "Create a major release."
switch "--minor",
description: "Create a minor release."
conflicts "--major", "--minor"
named_args :none
end
end
def release
args = release_args.parse
safe_system "git", "-C", HOMEBREW_REPOSITORY, "fetch", "origin" if Homebrew::EnvConfig.no_auto_update?
begin
latest_release = GitHub.get_latest_release "Homebrew", "brew"
rescue GitHub::HTTPNotFoundError
odie "No existing releases found!"
end
latest_version = Version.new latest_release["tag_name"]
if args.major? || args.minor?
one_month_ago = Date.today << 1
latest_major_minor_release = begin
GitHub.get_release "Homebrew", "brew", "#{latest_version.major_minor}.0"
rescue GitHub::HTTPNotFoundError
nil
end
if latest_major_minor_release.blank?
opoo "Unable to determine the release date of the latest major/minor release."
elsif Date.parse(latest_major_minor_release["published_at"]) > one_month_ago
odie "The latest major/minor release was less than one month ago."
end
end
new_version = if args.major?
Version.new "#{latest_version.major.to_i + 1}.0.0"
elsif args.minor?
Version.new "#{latest_version.major}.#{latest_version.minor.to_i + 1}.0"
else
Version.new "#{latest_version.major}.#{latest_version.minor}.#{latest_version.patch.to_i + 1}"
end.to_s
ohai "Creating draft release for version #{new_version}"
release_notes = if args.major? || args.minor?
"Release notes for this release can be found on the [Homebrew blog](https://brew.sh/blog/#{new_version}).\n"
else
""
end
release_notes += ReleaseNotes.generate_release_notes latest_version, "origin/HEAD", markdown: true
begin
release = GitHub.create_or_update_release "Homebrew", "brew", new_version, body: release_notes, draft: true
rescue *GitHub::API_ERRORS => e
odie "Unable to create release: #{e.message}!"
end
puts release["html_url"]
exec_browser release["html_url"]
end
end

View File

@ -0,0 +1,35 @@
# typed: true
# frozen_string_literal: true
# Helper functions for generating release notes.
#
# @api private
module ReleaseNotes
extend T::Sig
module_function
sig {
params(start_ref: T.any(String, Version), end_ref: T.any(String, Version), markdown: T.nilable(T::Boolean))
.returns(String)
}
def generate_release_notes(start_ref, end_ref, markdown: false)
log_output = Utils.popen_read(
"git", "-C", HOMEBREW_REPOSITORY, "log", "--pretty=format:'%s >> - %b%n'", "#{start_ref}..#{end_ref}"
).lines.grep(/Merge pull request/)
log_output.map! do |s|
s.gsub(%r{.*Merge pull request #(\d+) from ([^/]+)/[^>]*(>>)*},
"https://github.com/Homebrew/brew/pull/\\1 (@\\2)")
end
if markdown
log_output.map! do |s|
/(.*\d)+ \(@(.+)\) - (.*)/ =~ s
"- [#{Regexp.last_match(3)}](#{Regexp.last_match(1)}) (@#{Regexp.last_match(2)})\n"
end
end
log_output.join
end
end

View File

@ -0,0 +1,8 @@
# typed: false
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
describe "Homebrew.release_args" do
it_behaves_like "parseable arguments"
end

View File

@ -0,0 +1,33 @@
# typed: false
# frozen_string_literal: true
require "release_notes"
describe ReleaseNotes do
before do
HOMEBREW_REPOSITORY.cd do
system "git", "init"
system "git", "commit", "--allow-empty", "-m", "Initial commit"
system "git", "tag", "release-notes-testing"
system "git", "commit", "--allow-empty", "-m", "Merge pull request #1 from Homebrew/fix", "-m", "Do something"
system "git", "commit", "--allow-empty", "-m", "make a change"
system "git", "commit", "--allow-empty", "-m", "Merge pull request #2 from User/fix", "-m", "Do something else"
end
end
describe ".generate_release_notes" do
it "generates release notes" do
expect(described_class.generate_release_notes("release-notes-testing", "HEAD")).to eq <<~NOTES
https://github.com/Homebrew/brew/pull/2 (@User) - Do something else
https://github.com/Homebrew/brew/pull/1 (@Homebrew) - Do something
NOTES
end
it "generates markdown release notes" do
expect(described_class.generate_release_notes("release-notes-testing", "HEAD", markdown: true)).to eq <<~NOTES
- [Do something else](https://github.com/Homebrew/brew/pull/2) (@User)
- [Do something](https://github.com/Homebrew/brew/pull/1) (@Homebrew)
NOTES
end
end
end

View File

@ -482,7 +482,12 @@ module GitHub
open_api(url, request_method: :GET) open_api(url, request_method: :GET)
end end
def create_or_update_release(user, repo, tag, id: nil, name: nil, draft: false) def get_latest_release(user, repo)
url = "#{API_URL}/repos/#{user}/#{repo}/releases/latest"
open_api(url, request_method: :GET)
end
def create_or_update_release(user, repo, tag, id: nil, name: nil, body: nil, draft: false)
url = "#{API_URL}/repos/#{user}/#{repo}/releases" url = "#{API_URL}/repos/#{user}/#{repo}/releases"
method = if id method = if id
url += "/#{id}" url += "/#{id}"
@ -495,6 +500,7 @@ module GitHub
name: name || tag, name: name || tag,
draft: draft, draft: draft,
} }
data[:body] = body if body.present?
open_api(url, data: data, request_method: method, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES) open_api(url, data: data, request_method: method, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES)
end end

View File

@ -1663,6 +1663,23 @@ _brew_reinstall() {
__brew_complete_casks __brew_complete_casks
} }
_brew_release() {
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
-*)
__brewcomp "
--debug
--help
--major
--minor
--quiet
--verbose
"
return
;;
esac
}
_brew_release_notes() { _brew_release_notes() {
local cur="${COMP_WORDS[COMP_CWORD]}" local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in case "$cur" in
@ -2418,6 +2435,7 @@ _brew() {
prof) _brew_prof ;; prof) _brew_prof ;;
readall) _brew_readall ;; readall) _brew_readall ;;
reinstall) _brew_reinstall ;; reinstall) _brew_reinstall ;;
release) _brew_release ;;
release-notes) _brew_release_notes ;; release-notes) _brew_release_notes ;;
remove) _brew_remove ;; remove) _brew_remove ;;
rm) _brew_rm ;; rm) _brew_rm ;;

View File

@ -72,6 +72,7 @@ pr-upload
prof prof
readall readall
reinstall reinstall
release
release-notes release-notes
remove remove
rm rm

View File

@ -1208,6 +1208,22 @@ Run Homebrew with a Ruby profiler, e.g. `brew prof readall`.
* `--stackprof`: * `--stackprof`:
Use `stackprof` instead of `ruby-prof` (the default). Use `stackprof` instead of `ruby-prof` (the default).
### `release` [*`--major`*] [*`--minor`*]
Create a new draft Homebrew/brew release with the appropriate version number and release notes.
By default, `brew release` will bump the patch version number. Pass
`--major` or `--minor` to bump the major or minor version numbers, respectively.
The command will fail if the previous major or minor release was made less than
one month ago.
Requires write access to the Homebrew/brew repository.
* `--major`:
Create a major release.
* `--minor`:
Create a minor release.
### `release-notes` [*`options`*] [*`previous_tag`*] [*`end_ref`*] ### `release-notes` [*`options`*] [*`previous_tag`*] [*`end_ref`*]
Print the merged pull requests on Homebrew/brew between two Git refs. Print the merged pull requests on Homebrew/brew between two Git refs.

View File

@ -11,16 +11,12 @@ Homebrew release:
[Homebrew/discussions (forum)](https://github.com/homebrew/discussions/discussions) to see if there is [Homebrew/discussions (forum)](https://github.com/homebrew/discussions/discussions) to see if there is
anything pressing that needs to be fixed or merged before the next release. anything pressing that needs to be fixed or merged before the next release.
If so, fix and merge these changes. If so, fix and merge these changes.
2. After no code changes have happened for at least a couple of hours (ideally 24 hours) 2. Ensure that no code changes have happened for at least a couple of hours (ideally 24 hours)
and you are confident there's no major regressions on the current `master` and that you are confident there are no major regressions on the current `master`
branch you can create a new Git tag. Ideally this should be signed with your branch.
GPG key. This can then be pushed to GitHub. 3. Run `brew release` to create a new draft release. For major or minor version bumps,
3. Use `brew release-notes --markdown $PREVIOUS_TAG` to generate the release pass `--major` or `--minor`, respectively.
notes for the release. 4. Publish the draft release on [GitHub](https://github.com/Homebrew/brew/releases).
4. [Create a new release on GitHub](https://github.com/Homebrew/brew/releases/new)
based on the new tag.
You can watch a video of the above process [on YouTube](https://youtu.be/dQCpLaXOf6k)
If this is a major or minor release (e.g. X.0.0 or X.Y.0) then there are a few more steps: If this is a major or minor release (e.g. X.0.0 or X.Y.0) then there are a few more steps:

View File

@ -1686,6 +1686,23 @@ Run Homebrew with a Ruby profiler, e\.g\. \fBbrew prof readall\fR\.
\fB\-\-stackprof\fR \fB\-\-stackprof\fR
Use \fBstackprof\fR instead of \fBruby\-prof\fR (the default)\. Use \fBstackprof\fR instead of \fBruby\-prof\fR (the default)\.
. .
.SS "\fBrelease\fR [\fI\-\-major\fR] [\fI\-\-minor\fR]"
Create a new draft Homebrew/brew release with the appropriate version number and release notes\.
.
.P
By default, \fBbrew release\fR will bump the patch version number\. Pass \fB\-\-major\fR or \fB\-\-minor\fR to bump the major or minor version numbers, respectively\. The command will fail if the previous major or minor release was made less than one month ago\.
.
.P
Requires write access to the Homebrew/brew repository\.
.
.TP
\fB\-\-major\fR
Create a major release\.
.
.TP
\fB\-\-minor\fR
Create a minor release\.
.
.SS "\fBrelease\-notes\fR [\fIoptions\fR] [\fIprevious_tag\fR] [\fIend_ref\fR]" .SS "\fBrelease\-notes\fR [\fIoptions\fR] [\fIprevious_tag\fR] [\fIend_ref\fR]"
Print the merged pull requests on Homebrew/brew between two Git refs\. If no \fIprevious_tag\fR is provided it defaults to the latest tag\. If no \fIend_ref\fR is provided it defaults to \fBorigin/master\fR\. Print the merged pull requests on Homebrew/brew between two Git refs\. If no \fIprevious_tag\fR is provided it defaults to the latest tag\. If no \fIend_ref\fR is provided it defaults to \fBorigin/master\fR\.
. .