Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add brew release command #10370

Merged
merged 4 commits into from Jan 23, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Library/Homebrew/brew.sh
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
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
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
@@ -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
Rylan12 marked this conversation as resolved.
Show resolved Hide resolved
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
@@ -0,0 +1,35 @@
# typed: true
# frozen_string_literal: true

# Helper functions for generating release notes.
#
# @api private
module ReleaseNotes
Rylan12 marked this conversation as resolved.
Show resolved Hide resolved
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
@@ -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
@@ -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
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
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
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
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
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
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