Skip to content

Commit

Permalink
Merge pull request #10370 from Rylan12/brew-release
Browse files Browse the repository at this point in the history
Add brew release command
  • Loading branch information
Rylan12 authored Jan 23, 2021
2 parents bf448de + e13dc90 commit 2c83ea7
Show file tree
Hide file tree
Showing 13 changed files with 242 additions and 30 deletions.
2 changes: 1 addition & 1 deletion Library/Homebrew/brew.sh
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ update-preinstall() {

if [[ "$HOMEBREW_COMMAND" = "install" || "$HOMEBREW_COMMAND" = "upgrade" ||
"$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_CASK_COMMAND" = "install" || "$HOMEBREW_CASK_COMMAND" = "upgrade" ]]
then
Expand Down
6 changes: 6 additions & 0 deletions Library/Homebrew/cli/args.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ module Homebrew
sig { returns(T.nilable(T::Boolean)) }
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)) }
def tag; end

Expand Down
24 changes: 6 additions & 18 deletions Library/Homebrew/dev-cmd/release-notes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# frozen_string_literal: true

require "cli/parser"
require "release_notes"

module Homebrew
extend T::Sig
Expand Down Expand Up @@ -31,6 +32,9 @@ def release_notes_args
def release_notes
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

if previous_tag.present?
Expand All @@ -55,25 +59,9 @@ def release_notes
odie "Ref #{ref} does not exist!"
end

output = Utils.popen_read(
"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
release_notes = ReleaseNotes.generate_release_notes previous_tag, end_ref, markdown: args.markdown?

$stderr.puts "Release notes between #{previous_tag} and #{end_ref}:"
if args.markdown? && args.named.first
puts "Release notes for major and minor releases can be found in the [Homebrew blog](https://brew.sh/blog/)."
end
puts output
puts release_notes
end
end
88 changes: 88 additions & 0 deletions Library/Homebrew/dev-cmd/release.rb
Original file line number Diff line number Diff line change
@@ -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
35 changes: 35 additions & 0 deletions Library/Homebrew/release_notes.rb
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions Library/Homebrew/test/dev-cmd/release_spec.rb
Original file line number Diff line number Diff line change
@@ -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
33 changes: 33 additions & 0 deletions Library/Homebrew/test/release_notes_spec.rb
Original file line number Diff line number Diff line change
@@ -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
8 changes: 7 additions & 1 deletion Library/Homebrew/utils/github.rb
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,12 @@ def get_release(user, repo, tag)
open_api(url, request_method: :GET)
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"
method = if id
url += "/#{id}"
Expand All @@ -495,6 +500,7 @@ def create_or_update_release(user, repo, tag, id: nil, name: nil, draft: false)
name: name || tag,
draft: draft,
}
data[:body] = body if body.present?
open_api(url, data: data, request_method: method, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES)
end

Expand Down
18 changes: 18 additions & 0 deletions completions/bash/brew
Original file line number Diff line number Diff line change
Expand Up @@ -1663,6 +1663,23 @@ _brew_reinstall() {
__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() {
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
Expand Down Expand Up @@ -2418,6 +2435,7 @@ _brew() {
prof) _brew_prof ;;
readall) _brew_readall ;;
reinstall) _brew_reinstall ;;
release) _brew_release ;;
release-notes) _brew_release_notes ;;
remove) _brew_remove ;;
rm) _brew_rm ;;
Expand Down
1 change: 1 addition & 0 deletions completions/internal_commands_list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pr-upload
prof
readall
reinstall
release
release-notes
remove
rm
Expand Down
16 changes: 16 additions & 0 deletions docs/Manpage.md
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,22 @@ Run Homebrew with a Ruby profiler, e.g. `brew prof readall`.
* `--stackprof`:
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`*]

Print the merged pull requests on Homebrew/brew between two Git refs.
Expand Down
16 changes: 6 additions & 10 deletions docs/Releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,12 @@ Homebrew release:
[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.
If so, fix and merge these changes.
2. After 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`
branch you can create a new Git tag. Ideally this should be signed with your
GPG key. This can then be pushed to GitHub.
3. Use `brew release-notes --markdown $PREVIOUS_TAG` to generate the release
notes for the release.
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)
2. Ensure that no code changes have happened for at least a couple of hours (ideally 24 hours)
and that you are confident there are no major regressions on the current `master`
branch.
3. Run `brew release` to create a new draft release. For major or minor version bumps,
pass `--major` or `--minor`, respectively.
4. Publish the draft release on [GitHub](https://github.com/Homebrew/brew/releases).

If this is a major or minor release (e.g. X.0.0 or X.Y.0) then there are a few more steps:

Expand Down
17 changes: 17 additions & 0 deletions manpages/brew.1
Original file line number Diff line number Diff line change
Expand Up @@ -1686,6 +1686,23 @@ Run Homebrew with a Ruby profiler, e\.g\. \fBbrew prof readall\fR\.
\fB\-\-stackprof\fR
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]"
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\.
.
Expand Down

0 comments on commit 2c83ea7

Please sign in to comment.