From c5a3879fdbd73b31ee9063e44ace78a11f635a58 Mon Sep 17 00:00:00 2001 From: Bevan Kay Date: Wed, 9 Apr 2025 22:55:56 +1000 Subject: [PATCH 1/3] bump: bump synced formula together --- Library/Homebrew/dev-cmd/bump-cask-pr.rb | 14 +- Library/Homebrew/dev-cmd/bump-formula-pr.rb | 571 ++++++++++-------- Library/Homebrew/dev-cmd/bump.rb | 22 +- .../sorbet/rbi/dsl/homebrew/dev_cmd/bump.rbi | 3 + .../dsl/homebrew/dev_cmd/bump_formula_pr.rbi | 3 + Library/Homebrew/utils/github.rb | 157 ++--- 6 files changed, 435 insertions(+), 335 deletions(-) diff --git a/Library/Homebrew/dev-cmd/bump-cask-pr.rb b/Library/Homebrew/dev-cmd/bump-cask-pr.rb index 8af2a1e0c3..361ad410c7 100644 --- a/Library/Homebrew/dev-cmd/bump-cask-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-cask-pr.rb @@ -160,13 +160,17 @@ module Homebrew run_cask_style(cask, old_contents) pr_info = { + commits: [{ + commit_message:, + old_contents:, + sourcefile_path: cask.sourcefile_path, + }], branch_name:, - commit_message:, - old_contents:, - pr_message: "Created with `brew bump-cask-pr`.", - sourcefile_path: cask.sourcefile_path, - tap: cask.tap, + pr_message: "Created with `brew bump-cask-pr`.", + tap: cask.tap, + pr_title: commit_message, } + GitHub.create_bump_pr(pr_info, args:) end diff --git a/Library/Homebrew/dev-cmd/bump-formula-pr.rb b/Library/Homebrew/dev-cmd/bump-formula-pr.rb index 30163d927a..b048ce5433 100644 --- a/Library/Homebrew/dev-cmd/bump-formula-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-formula-pr.rb @@ -81,13 +81,14 @@ module Homebrew description: "Include these additional Python packages when finding resources." comma_array "--python-exclude-packages=", description: "Exclude these Python packages when finding resources." - + comma_array "--bump-synced=", + hidden: true conflicts "--dry-run", "--write-only" conflicts "--no-audit", "--strict" conflicts "--no-audit", "--online" conflicts "--url", "--tag" - named_args :formula, max: 1, without_api: true + named_args :named_formula, max: 1, without_api: true end sig { override.void } @@ -103,18 +104,17 @@ module Homebrew # Use the user's browser, too. ENV["BROWSER"] = Homebrew::EnvConfig.browser - formula = args.named.to_formulae.first - new_url = args.url - raise FormulaUnspecifiedError if formula.blank? + named_formula = args.named.to_formulae.first + raise FormulaUnspecifiedError if named_formula.blank? - odie "This formula is disabled!" if formula.disabled? - odie "This formula is deprecated and does not build!" if formula.deprecation_reason == :does_not_build - tap = formula.tap + odie "This formula is disabled!" if named_formula.disabled? + odie "This formula is deprecated and does not build!" if named_formula.deprecation_reason == :does_not_build + tap = named_formula.tap odie "This formula is not in a tap!" if tap.blank? odie "This formula's tap is not a Git repository!" unless tap.git? - odie <<~EOS unless tap.allow_bump?(formula.name) - Whoops, the #{formula.name} formula has its version update + odie <<~EOS unless tap.allow_bump?(named_formula.name) + Whoops, the #{named_formula.name} formula has its version update pull requests automatically opened by BrewTestBot every ~3 hours! We'd still love your contributions, though, so try another one that's not in the autobump list: @@ -123,8 +123,8 @@ module Homebrew odie "You have too many PRs open: close or merge some first!" if GitHub.too_many_open_prs?(tap) - formula_spec = formula.stable - odie "#{formula}: no stable specification found!" if formula_spec.blank? + named_formula_spec = named_formula.stable + odie "#{named_formula}: no stable specification found!" if named_formula_spec.blank? # This will be run by `brew audit` later so run it first to not start # spamming during normal output. @@ -135,267 +135,330 @@ module Homebrew remote_branch = tap.git_repository.origin_branch_name previous_branch = "-" - check_pull_requests(formula, tap_remote_repo, state: "open") + check_pull_requests(named_formula, tap_remote_repo, state: "open") - new_version = args.version - check_new_version(formula, tap_remote_repo, version: new_version) if new_version.present? - - opoo "This formula has patches that may be resolved upstream." if formula.patchlist.present? - if formula.resources.any? { |resource| !resource.name.start_with?("homebrew-") } - opoo "This formula has resources that may need to be updated." - end - - old_mirrors = formula_spec.mirrors - new_mirrors ||= args.mirror - if new_url.present? && (new_mirror = determine_mirror(new_url)) - new_mirrors ||= [new_mirror] - check_for_mirrors(formula.name, old_mirrors, new_mirrors) - end - - old_hash = formula_spec.checksum&.hexdigest - new_hash = args.sha256 - new_tag = args.tag - new_revision = args.revision - old_url = T.must(formula_spec.url) - old_tag = formula_spec.specs[:tag] - old_formula_version = formula_version(formula) - old_version = old_formula_version.to_s - forced_version = new_version.present? - new_url_hash = if new_url.present? && new_hash.present? - check_new_version(formula, tap_remote_repo, url: new_url) if new_version.blank? - true - elsif new_tag.present? && new_revision.present? - check_new_version(formula, tap_remote_repo, url: old_url, tag: new_tag) if new_version.blank? - false - elsif old_hash.blank? - if new_tag.blank? && new_version.blank? && new_revision.blank? - raise UsageError, "#{formula}: no `--tag` or `--version` argument specified!" + all_formulae = [] + if args.bump_synced.present? + Array(args.bump_synced).each do |formula_name| + all_formulae << formula_name end - - if old_tag.present? - new_tag ||= old_tag.gsub(old_version, new_version) - if new_tag == old_tag - odie <<~EOS - You need to bump this formula manually since the new tag - and old tag are both #{new_tag}. - EOS - end - check_new_version(formula, tap_remote_repo, url: old_url, tag: new_tag) if new_version.blank? - resource_path, forced_version = fetch_resource_and_forced_version(formula, new_version, old_url, - tag: new_tag) - new_revision = Utils.popen_read("git", "-C", resource_path.to_s, "rev-parse", "-q", "--verify", "HEAD") - new_revision = new_revision.strip - elsif new_revision.blank? - odie "#{formula}: the current URL requires specifying a `--revision=` argument." - end - false - elsif new_url.blank? && new_version.blank? - raise UsageError, "#{formula}: no `--url` or `--version` argument specified!" else - return unless new_version.present? + all_formulae << args.named.first.to_s + end - new_url ||= PyPI.update_pypi_url(old_url, new_version) - if new_url.blank? - new_url = update_url(old_url, old_version, new_version) - if new_mirrors.blank? && old_mirrors.present? - new_mirrors = old_mirrors.map do |old_mirror| - update_url(old_mirror, old_version, new_version) + return if all_formulae.empty? + + commits = all_formulae.filter_map do |formula_name| + formula = Formula[formula_name] + raise FormulaUnspecifiedError if formula.blank? + + formula_spec = formula.stable + odie "#{formula}: no stable specification found!" if formula_spec.blank? + + formula_pr_message = "" + + new_url = args.url + new_version = args.version + check_new_version(formula, tap_remote_repo, version: new_version) if new_version.present? + + opoo "This formula has patches that may be resolved upstream." if formula.patchlist.present? + if formula.resources.any? { |resource| !resource.name.start_with?("homebrew-") } + opoo "This formula has resources that may need to be updated." + end + + old_mirrors = formula_spec.mirrors + new_mirrors ||= args.mirror + if new_url.present? && (new_mirror = determine_mirror(new_url)) + new_mirrors ||= [new_mirror] + check_for_mirrors(formula.name, old_mirrors, new_mirrors) + end + + old_hash = formula_spec.checksum&.hexdigest + new_hash = args.sha256 + new_tag = args.tag + new_revision = args.revision + old_url = T.must(formula_spec.url) + old_tag = formula_spec.specs[:tag] + old_formula_version = formula_version(formula) + old_version = old_formula_version.to_s + forced_version = new_version.present? + new_url_hash = if new_url.present? && new_hash.present? + check_new_version(formula, tap_remote_repo, url: new_url) if new_version.blank? + true + elsif new_tag.present? && new_revision.present? + check_new_version(formula, tap_remote_repo, url: old_url, tag: new_tag) if new_version.blank? + false + elsif old_hash.blank? + if new_tag.blank? && new_version.blank? && new_revision.blank? + raise UsageError, "#{formula}: no `--tag` or `--version` argument specified!" + end + + if old_tag.present? + new_tag ||= old_tag.gsub(old_version, new_version) + if new_tag == old_tag + odie <<~EOS + You need to bump this formula manually since the new tag + and old tag are both #{new_tag}. + EOS + end + check_new_version(formula, tap_remote_repo, url: old_url, tag: new_tag) if new_version.blank? + resource_path, forced_version = fetch_resource_and_forced_version(formula, new_version, old_url, + tag: new_tag) + new_revision = Utils.popen_read("git", "-C", resource_path.to_s, "rev-parse", "-q", "--verify", "HEAD") + new_revision = new_revision.strip + elsif new_revision.blank? + odie "#{formula}: the current URL requires specifying a `--revision=` argument." + end + false + elsif new_url.blank? && new_version.blank? + raise UsageError, "#{formula}: no `--url` or `--version` argument specified!" + else + next unless new_version.present? + + new_url ||= PyPI.update_pypi_url(old_url, new_version) + if new_url.blank? + new_url = update_url(old_url, old_version, new_version) + if new_mirrors.blank? && old_mirrors.present? + new_mirrors = old_mirrors.map do |old_mirror| + update_url(old_mirror, old_version, new_version) + end end end + if new_url == old_url + odie <<~EOS + You need to bump this formula manually since the new URL + and old URL are both: + #{new_url} + EOS + end + check_new_version(formula, tap_remote_repo, url: new_url) if new_version.blank? + resource_path, forced_version = fetch_resource_and_forced_version(formula, new_version, new_url) + Utils::Tar.validate_file(resource_path) + new_hash = resource_path.sha256 end - if new_url == old_url - odie <<~EOS - You need to bump this formula manually since the new URL - and old URL are both: - #{new_url} - EOS - end - check_new_version(formula, tap_remote_repo, url: new_url) if new_version.blank? - resource_path, forced_version = fetch_resource_and_forced_version(formula, new_version, new_url) - Utils::Tar.validate_file(resource_path) - new_hash = resource_path.sha256 - end - replacement_pairs = [] - if formula.revision.nonzero? - replacement_pairs << [ - /^ revision \d+\n(\n( head "))?/m, - "\\2", - ] - end - - replacement_pairs += formula_spec.mirrors.map do |mirror| - [ - / +mirror "#{Regexp.escape(mirror)}"\n/m, - "", - ] - end - - replacement_pairs += if new_url_hash.present? - [ - [ - /#{Regexp.escape(T.must(formula_spec.url))}/, - new_url, - ], - [ - old_hash, - new_hash, - ], - ] - elsif new_tag.present? - [ - [ - /tag:(\s+")#{formula_spec.specs[:tag]}(?=")/, - "tag:\\1#{new_tag}\\2", - ], - [ - formula_spec.specs[:revision], - new_revision, - ], - ] - elsif new_url.present? - [ - [ - /#{Regexp.escape(T.must(formula_spec.url))}/, - new_url, - ], - [ - formula_spec.specs[:revision], - new_revision, - ], - ] - else - [ - [ - formula_spec.specs[:revision], - new_revision, - ], - ] - end - - old_contents = formula.path.read - - if new_mirrors.present? && new_url.present? - replacement_pairs << [ - /^( +)(url "#{Regexp.escape(new_url)}"[^\n]*?\n)/m, - "\\1\\2\\1mirror \"#{new_mirrors.join("\"\n\\1mirror \"")}\"\n", - ] - end - - if forced_version && new_version != "0" - replacement_pairs << if old_contents.include?("version \"#{old_formula_version}\"") - [ - "version \"#{old_formula_version}\"", - "version \"#{new_version}\"", + replacement_pairs = [] + if formula.revision.nonzero? + replacement_pairs << [ + /^ revision \d+\n(\n( head "))?/m, + "\\2", ] - elsif new_mirrors.present? + end + + replacement_pairs += formula_spec.mirrors.map do |mirror| [ - /^( +)(mirror "#{Regexp.escape(new_mirrors.last)}"\n)/m, - "\\1\\2\\1version \"#{new_version}\"\n", + / +mirror "#{Regexp.escape(mirror)}"\n/m, + "", + ] + end + + replacement_pairs += if new_url_hash.present? + [ + [ + /#{Regexp.escape(T.must(formula_spec.url))}/, + new_url, + ], + [ + old_hash, + new_hash, + ], + ] + elsif new_tag.present? + [ + [ + /tag:(\s+")#{formula_spec.specs[:tag]}(?=")/, + "tag:\\1#{new_tag}\\2", + ], + [ + formula_spec.specs[:revision], + new_revision, + ], ] elsif new_url.present? [ - /^( +)(url "#{Regexp.escape(new_url)}"[^\n]*?\n)/m, - "\\1\\2\\1version \"#{new_version}\"\n", + [ + /#{Regexp.escape(T.must(formula_spec.url))}/, + new_url, + ], + [ + formula_spec.specs[:revision], + new_revision, + ], ] - elsif new_revision.present? + else [ - /^( {2})( +)(:revision => "#{new_revision}"\n)/m, - "\\1\\2\\3\\1version \"#{new_version}\"\n", + [ + formula_spec.specs[:revision], + new_revision, + ], ] end - elsif forced_version && new_version == "0" - replacement_pairs << [ - /^ version "[\w.\-+]+"\n/m, - "", - ] - end - new_contents = Utils::Inreplace.inreplace_pairs(formula.path, - replacement_pairs.uniq.compact, - read_only_run: args.dry_run?, - silent: args.quiet?) - new_formula_version = formula_version(formula, new_contents) + old_contents = formula.path.read - if new_formula_version < old_formula_version - formula.path.atomic_write(old_contents) unless args.dry_run? - odie <<~EOS - You need to bump this formula manually since changing the version - from #{old_formula_version} to #{new_formula_version} would be a downgrade. - EOS - elsif new_formula_version == old_formula_version - formula.path.atomic_write(old_contents) unless args.dry_run? - odie <<~EOS - You need to bump this formula manually since the new version - and old version are both #{new_formula_version}. - EOS - end - - alias_rename = alias_update_pair(formula, new_formula_version) - if alias_rename.present? - ohai "Renaming alias #{alias_rename.first} to #{alias_rename.last}" - alias_rename.map! { |a| tap.alias_dir/a } - end - - unless args.dry_run? - resources_checked = PyPI.update_python_resources! formula, - version: new_formula_version.to_s, - package_name: args.python_package_name, - extra_packages: args.python_extra_packages, - exclude_packages: args.python_exclude_packages, - install_dependencies: args.install_dependencies?, - silent: args.quiet?, - ignore_non_pypi_packages: true - - update_matching_version_resources! formula, - version: new_formula_version.to_s - end - - run_audit(formula, alias_rename, old_contents) - - pr_message = "Created with `brew bump-formula-pr`." - if resources_checked.nil? && formula.resources.any? do |resource| - resource.livecheck.formula != :parent && !resource.name.start_with?("homebrew-") - end - pr_message += <<~EOS - - - - [ ] `resource` blocks have been checked for updates. - EOS - end - - if new_url =~ %r{^https://github\.com/([\w-]+)/([\w-]+)/archive/refs/tags/(v?[.0-9]+)\.tar\.} - owner = Regexp.last_match(1) - repo = Regexp.last_match(2) - tag = Regexp.last_match(3) - github_release_data = begin - GitHub::API.open_rest("#{GitHub::API_URL}/repos/#{owner}/#{repo}/releases/tags/#{tag}") - rescue GitHub::API::HTTPNotFoundError - # If this is a 404: we can't do anything. - nil + if new_mirrors.present? && new_url.present? + replacement_pairs << [ + /^( +)(url "#{Regexp.escape(new_url)}"[^\n]*?\n)/m, + "\\1\\2\\1mirror \"#{new_mirrors.join("\"\n\\1mirror \"")}\"\n", + ] end - if github_release_data.present? - pre = "pre" if github_release_data["prerelease"].present? - pr_message += <<~XML -
- #{pre}release notes -
#{github_release_data["body"]}
-
- XML + if forced_version && new_version != "0" + replacement_pairs << if old_contents.include?("version \"#{old_formula_version}\"") + [ + "version \"#{old_formula_version}\"", + "version \"#{new_version}\"", + ] + elsif new_mirrors.present? + [ + /^( +)(mirror "#{Regexp.escape(new_mirrors.last)}"\n)/m, + "\\1\\2\\1version \"#{new_version}\"\n", + ] + elsif new_url.present? + [ + /^( +)(url "#{Regexp.escape(new_url)}"[^\n]*?\n)/m, + "\\1\\2\\1version \"#{new_version}\"\n", + ] + elsif new_revision.present? + [ + /^( {2})( +)(:revision => "#{new_revision}"\n)/m, + "\\1\\2\\3\\1version \"#{new_version}\"\n", + ] + end + elsif forced_version && new_version == "0" + replacement_pairs << [ + /^ version "[\w.\-+]+"\n/m, + "", + ] end + new_contents = Utils::Inreplace.inreplace_pairs(formula.path, + replacement_pairs.uniq.compact, + read_only_run: args.dry_run?, + silent: args.quiet?) + + new_formula_version = formula_version(formula, new_contents) + + if new_formula_version < old_formula_version + formula.path.atomic_write(old_contents) unless args.dry_run? + odie <<~EOS + You need to bump this formula manually since changing the version + from #{old_formula_version} to #{new_formula_version} would be a downgrade. + EOS + elsif new_formula_version == old_formula_version + formula.path.atomic_write(old_contents) unless args.dry_run? + odie <<~EOS + You need to bump this formula manually since the new version + and old version are both #{new_formula_version}. + EOS + end + + alias_rename = alias_update_pair(formula, new_formula_version) + if alias_rename.present? + ohai "Renaming alias #{alias_rename.first} to #{alias_rename.last}" + alias_rename.map! { |a| tap.alias_dir/a } + end + + unless args.dry_run? + resources_checked = PyPI.update_python_resources! formula, + version: new_formula_version.to_s, + package_name: args.python_package_name, + extra_packages: args.python_extra_packages, + exclude_packages: args.python_exclude_packages, + install_dependencies: args.install_dependencies?, + silent: args.quiet?, + ignore_non_pypi_packages: true + + update_matching_version_resources! formula, + version: new_formula_version.to_s + end + + if resources_checked.nil? && formula.resources.any? do |resource| + resource.livecheck.formula != :parent && !resource.name.start_with?("homebrew-") + end + formula_pr_message += <<~EOS + + + - [ ] `resource` blocks have been checked for updates. + EOS + end + + if new_url =~ %r{^https://github\.com/([\w-]+)/([\w-]+)/archive/refs/tags/(v?[.0-9]+)\.tar\.} + owner = Regexp.last_match(1) + repo = Regexp.last_match(2) + tag = Regexp.last_match(3) + github_release_data = begin + GitHub::API.open_rest("#{GitHub::API_URL}/repos/#{owner}/#{repo}/releases/tags/#{tag}") + rescue GitHub::API::HTTPNotFoundError + # If this is a 404: we can't do anything. + nil + end + + if github_release_data.present? + pre = "pre" if github_release_data["prerelease"].present? + formula_pr_message += <<~XML +
+ #{pre}release notes +
#{github_release_data["body"]}
+
+ XML + end + end + + { + sourcefile_path: formula.path, + old_contents:, + commit_message: "#{formula.name} #{args.version}", + additional_files: alias_rename, + formula_pr_message:, + formula_name: formula.name, + new_version: new_formula_version, + } + end + + commits.each do |commit| + commit_formula = Formula[commit[:formula_name]] + # For each formula, run `brew audit` to check for any issues. + audit_result = run_audit(commit_formula, commit[:additional_files], + skip_synced_versions: args.bump_synced.present?) + + next unless audit_result + + # If `brew audit` fails, revert the changes made to any formula. + commits.each do |revert| + revert_formula = Formula[revert[:formula_name]] + revert_formula.path.atomic_write(revert[:old_contents]) unless args.dry_run? + revert_alias_rename = revert[:additional_files] + if revert_alias_rename && (source = revert_alias_rename.first) && (destination = revert_alias_rename.last) + FileUtils.mv source, destination + end + end + + odie "`brew audit` failed for #{commit[:formula_name]}!" + end + + new_formula_version = T.must(commits.first)[:new_version] + pr_title = if args.bump_synced.nil? + "#{named_formula.name} #{new_formula_version}" + else + "#{Array(args.bump_synced).join(" ")} #{new_formula_version}" + end + + pr_message = "Created by `brew bump-formula-pr`." + commits.each do |commit| + next if commit[:formula_pr_message].empty? + + pr_message += "

#{commit[:formula_name]}

" if commits.length != 1 + pr_message += "#{commit[:formula_pr_message]}
" end pr_info = { - sourcefile_path: formula.path, - old_contents:, - additional_files: alias_rename, + commits:, remote:, remote_branch:, - branch_name: "bump-#{formula.name}-#{new_formula_version}", - commit_message: "#{formula.name} #{new_formula_version}", + branch_name: "bump-#{named_formula.name}-#{new_formula_version}", + pr_title:, previous_branch:, - tap: tap, + tap: tap, tap_remote_repo:, pr_message:, } @@ -604,11 +667,15 @@ module Homebrew [versioned_alias, "#{name}@#{new_alias_version}"] end - sig { params(formula: Formula, alias_rename: T.nilable(T::Array[String]), old_contents: String).void } - def run_audit(formula, alias_rename, old_contents) + sig { + params(formula: Formula, alias_rename: T.nilable(T::Array[String]), + skip_synced_versions: T::Boolean).returns(T::Boolean) + } + def run_audit(formula, alias_rename, skip_synced_versions: false) audit_args = ["--formula"] audit_args << "--strict" if args.strict? audit_args << "--online" if args.online? + audit_args << "--except=synced_versions_formulae" if skip_synced_versions if args.dry_run? if args.no_audit? ohai "Skipping `brew audit`" @@ -617,7 +684,7 @@ module Homebrew else ohai "brew audit #{formula.path.basename}" end - return + return true end if alias_rename && (source = alias_rename.first) && (destination = alias_rename.last) FileUtils.mv source, destination @@ -632,13 +699,7 @@ module Homebrew system HOMEBREW_BREW_FILE, "audit", formula.full_name failed_audit = !$CHILD_STATUS.success? end - return unless failed_audit - - formula.path.atomic_write(old_contents) - if alias_rename && (source = alias_rename.first) && (destination = alias_rename.last) - FileUtils.mv source, destination - end - odie "`brew audit` failed!" + failed_audit end end end diff --git a/Library/Homebrew/dev-cmd/bump.rb b/Library/Homebrew/dev-cmd/bump.rb index cfca2b2087..1edf55f811 100644 --- a/Library/Homebrew/dev-cmd/bump.rb +++ b/Library/Homebrew/dev-cmd/bump.rb @@ -53,6 +53,8 @@ module Homebrew description: "Open a pull request for the new version if none have been opened yet." flag "--start-with=", description: "Letter or word that the list of package results should alphabetically follow." + switch "--bump-synced", + description: "Bump additional formulae marked as synced with the given formulae." conflicts "--cask", "--formula" conflicts "--tap=", "--installed" @@ -470,10 +472,12 @@ module Homebrew EOS if formula_or_cask.is_a?(Formula) && formula_or_cask.synced_with_other_formulae? outdated_synced_formulae = synced_with(formula_or_cask, new_version.general) - puts <<~EOS if outdated_synced_formulae.present? - Version syncing: #{title_name} version should be kept in sync with - #{outdated_synced_formulae.join(", ")}. - EOS + if !args.bump_synced? && outdated_synced_formulae.present? + puts <<~EOS + Version syncing: #{title_name} version should be kept in sync with + #{outdated_synced_formulae.join(", ")}. + EOS + end end if !args.no_pull_requests? && (new_version.general != "unable to get versions") && @@ -519,7 +523,7 @@ module Homebrew "--version=#{new_version.general}" end - bump_cask_pr_args = [ + bump_pr_args = [ "bump-#{version_info.type}-pr", name, *version_args, @@ -527,9 +531,13 @@ module Homebrew "--message=Created by `brew bump`", ] - bump_cask_pr_args << "--no-fork" if args.no_fork? + bump_pr_args << "--no-fork" if args.no_fork? - system HOMEBREW_BREW_FILE, *bump_cask_pr_args + if args.bump_synced? && outdated_synced_formulae.present? + bump_pr_args << "--bump-synced=#{outdated_synced_formulae.join(",")}" + end + + system HOMEBREW_BREW_FILE, *bump_pr_args end sig { diff --git a/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/bump.rbi b/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/bump.rbi index 89a7d21c3f..0ee11bf0d7 100644 --- a/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/bump.rbi +++ b/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/bump.rbi @@ -14,6 +14,9 @@ class Homebrew::DevCmd::Bump::Args < Homebrew::CLI::Args sig { returns(T::Boolean) } def auto?; end + sig { returns(T::Boolean) } + def bump_synced?; end + sig { returns(T::Boolean) } def cask?; end diff --git a/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/bump_formula_pr.rbi b/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/bump_formula_pr.rbi index b0368c6db8..4ab9d748f2 100644 --- a/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/bump_formula_pr.rbi +++ b/Library/Homebrew/sorbet/rbi/dsl/homebrew/dev_cmd/bump_formula_pr.rbi @@ -11,6 +11,9 @@ class Homebrew::DevCmd::BumpFormulaPr end class Homebrew::DevCmd::BumpFormulaPr::Args < Homebrew::CLI::Args + sig { returns(T.nilable(String)) } + def bump_synced; end + sig { returns(T::Boolean) } def commit?; end diff --git a/Library/Homebrew/utils/github.rb b/Library/Homebrew/utils/github.rb index 38114448b4..feee56ccd9 100644 --- a/Library/Homebrew/utils/github.rb +++ b/Library/Homebrew/utils/github.rb @@ -706,96 +706,117 @@ module GitHub def self.create_bump_pr(info, args:) tap = info[:tap] - sourcefile_path = info[:sourcefile_path] - old_contents = info[:old_contents] - additional_files = info[:additional_files] || [] remote = info[:remote] || "origin" remote_branch = info[:remote_branch] || tap.git_repository.origin_branch_name branch = info[:branch_name] - commit_message = info[:commit_message] previous_branch = info[:previous_branch] || "-" tap_remote_repo = info[:tap_remote_repo] || tap.full_name pr_message = info[:pr_message] + pr_title = info[:pr_title] - sourcefile_path.parent.cd do + commits = info[:commits] + username = tap.user + + remote_url = Utils.popen_read("git", "remote", "get-url", "--push", "origin").chomp + + tap.path.cd do require "utils/popen" git_dir = Utils.popen_read("git", "rev-parse", "--git-dir").chomp shallow = !git_dir.empty? && File.exist?("#{git_dir}/shallow") - changed_files = [sourcefile_path] - changed_files += additional_files if additional_files.present? - - if args.dry_run? || (args.write_only? && !args.commit?) - remote_url = if args.no_fork? - Utils.popen_read("git", "remote", "get-url", "--push", "origin").chomp + unless args.commit? + if args.no_fork? + remote_url = Utils.popen_read("git", "remote", "get-url", "--push", "origin").chomp + add_auth_token_to_url!(remote_url) + username = tap.user else - fork_message = "try to fork repository with GitHub API" \ - "#{" into `#{args.fork_org}` organization" if args.fork_org}" - ohai fork_message - "FORK_URL" - end - ohai "git fetch --unshallow origin" if shallow - ohai "git add #{changed_files.join(" ")}" - ohai "git checkout --no-track -b #{branch} #{remote}/#{remote_branch}" - ohai "git commit --no-edit --verbose --message='#{commit_message}' " \ - "-- #{changed_files.join(" ")}" - ohai "git push --set-upstream #{remote_url} #{branch}:#{branch}" - ohai "git checkout --quiet #{previous_branch}" - ohai "create pull request with GitHub API (base branch: #{remote_branch})" - else - - unless args.commit? - if args.no_fork? - remote_url = Utils.popen_read("git", "remote", "get-url", "--push", "origin").chomp - add_auth_token_to_url!(remote_url) - username = tap.user - else - begin - remote_url, username = forked_repo_info!(tap_remote_repo, org: args.fork_org) - rescue *API::ERRORS => e - sourcefile_path.atomic_write(old_contents) - odie "Unable to fork: #{e.message}!" + begin + forked_repo_info!(tap_remote_repo, org: args.fork_org) + rescue *API::ERRORS => e + commits.each do |commit| + commit[:sourcefile_path].atomic_write(commit[:old_contents]) end + odie "Unable to fork: #{e.message}!" end - - safe_system "git", "fetch", "--unshallow", "origin" if shallow end - safe_system "git", "add", *changed_files - safe_system "git", "checkout", "--no-track", "-b", branch, "#{remote}/#{remote_branch}" unless args.commit? - Utils::Git.set_name_email! - safe_system "git", "commit", "--no-edit", "--verbose", - "--message=#{commit_message}", - "--", *changed_files - return if args.commit? + safe_system "git", "fetch", "--unshallow", "origin" if shallow + end + safe_system "git", "checkout", "--no-track", "-b", branch, "#{remote}/#{remote_branch}" unless args.commit? + Utils::Git.set_name_email! + end - system_command!("git", args: ["push", "--set-upstream", remote_url, "#{branch}:#{branch}"], - print_stdout: true) - safe_system "git", "checkout", "--quiet", previous_branch + commits.each do |commit| + sourcefile_path = commit[:sourcefile_path] + commit_message = commit[:commit_message] + additional_files = commit[:additional_files] || [] + + sourcefile_path.parent.cd do + require "utils/popen" + git_dir = Utils.popen_read("git", "rev-parse", "--git-dir").chomp + shallow = !git_dir.empty? && File.exist?("#{git_dir}/shallow") + changed_files = [sourcefile_path] + changed_files += additional_files if additional_files.present? + + if args.dry_run? || (args.write_only? && !args.commit?) + remote_url = if args.no_fork? + Utils.popen_read("git", "remote", "get-url", "--push", "origin").chomp + else + fork_message = "try to fork repository with GitHub API" \ + "#{" into `#{args.fork_org}` organization" if args.fork_org}" + ohai fork_message + "FORK_URL" + end + ohai "git checkout --no-track -b #{branch} #{remote}/#{remote_branch}" + ohai "git fetch --unshallow origin" if shallow + ohai "git add #{changed_files.join(" ")}" + ohai "git commit --no-edit --verbose --message='#{commit_message}' " \ + "-- #{changed_files.join(" ")}" + ohai "git push --set-upstream #{remote_url} #{branch}:#{branch}" + ohai "git checkout --quiet #{previous_branch}" + ohai "create pull request with GitHub API (base branch: #{remote_branch})" + else + safe_system "git", "add", *changed_files + Utils::Git.set_name_email! + safe_system "git", "commit", "--no-edit", "--verbose", + "--message=#{commit_message}", + "--", *changed_files + end + end + end + + return if args.commit? + + tap.path.cd do + system_command!("git", args: ["push", "--set-upstream", remote_url, "#{branch}:#{branch}"], + print_stdout: true) + safe_system "git", "checkout", "--quiet", previous_branch + pr_message = <<~EOS + #{pr_message} + EOS + user_message = args.message + if user_message pr_message = <<~EOS + #{user_message} + + --- + #{pr_message} EOS - user_message = args.message - if user_message - pr_message = <<~EOS - #{user_message} + end - --- - - #{pr_message} - EOS + begin + url = create_pull_request(tap_remote_repo, pr_title, + "#{username}:#{branch}", remote_branch, pr_message)["html_url"] + if args.no_browse? + puts url + else + exec_browser url end - - begin - url = create_pull_request(tap_remote_repo, commit_message, - "#{username}:#{branch}", remote_branch, pr_message)["html_url"] - if args.no_browse? - puts url - else - exec_browser url - end - rescue *API::ERRORS => e - odie "Unable to open pull request: #{e.message}!" + rescue *API::ERRORS => e + commits.each do |commit| + commit[:sourcefile_path].atomic_write(commit[:old_contents]) end + odie "Unable to open pull request: #{e.message}!" end end end From 2b132c080279a5d5fe9248b21880c7c0f05b662e Mon Sep 17 00:00:00 2001 From: Bevan Kay Date: Tue, 22 Apr 2025 20:30:29 +1000 Subject: [PATCH 2/3] dev-cmd/bump-formula-pr: fix reference --- Library/Homebrew/dev-cmd/bump-formula-pr.rb | 106 ++++++++++---------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/Library/Homebrew/dev-cmd/bump-formula-pr.rb b/Library/Homebrew/dev-cmd/bump-formula-pr.rb index b048ce5433..454cea3830 100644 --- a/Library/Homebrew/dev-cmd/bump-formula-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-formula-pr.rb @@ -88,7 +88,7 @@ module Homebrew conflicts "--no-audit", "--online" conflicts "--url", "--tag" - named_args :named_formula, max: 1, without_api: true + named_args :formula, max: 1, without_api: true end sig { override.void } @@ -104,17 +104,17 @@ module Homebrew # Use the user's browser, too. ENV["BROWSER"] = Homebrew::EnvConfig.browser - named_formula = args.named.to_formulae.first - raise FormulaUnspecifiedError if named_formula.blank? + formula = args.named.to_formulae.first + raise FormulaUnspecifiedError if formula.blank? - odie "This formula is disabled!" if named_formula.disabled? - odie "This formula is deprecated and does not build!" if named_formula.deprecation_reason == :does_not_build - tap = named_formula.tap + odie "This formula is disabled!" if formula.disabled? + odie "This formula is deprecated and does not build!" if formula.deprecation_reason == :does_not_build + tap = formula.tap odie "This formula is not in a tap!" if tap.blank? odie "This formula's tap is not a Git repository!" unless tap.git? - odie <<~EOS unless tap.allow_bump?(named_formula.name) - Whoops, the #{named_formula.name} formula has its version update + odie <<~EOS unless tap.allow_bump?(formula.name) + Whoops, the #{formula.name} formula has its version update pull requests automatically opened by BrewTestBot every ~3 hours! We'd still love your contributions, though, so try another one that's not in the autobump list: @@ -123,8 +123,8 @@ module Homebrew odie "You have too many PRs open: close or merge some first!" if GitHub.too_many_open_prs?(tap) - named_formula_spec = named_formula.stable - odie "#{named_formula}: no stable specification found!" if named_formula_spec.blank? + formula_spec = formula.stable + odie "#{formula}: no stable specification found!" if formula_spec.blank? # This will be run by `brew audit` later so run it first to not start # spamming during normal output. @@ -135,7 +135,7 @@ module Homebrew remote_branch = tap.git_repository.origin_branch_name previous_branch = "-" - check_pull_requests(named_formula, tap_remote_repo, state: "open") + check_pull_requests(formula, tap_remote_repo, state: "open") all_formulae = [] if args.bump_synced.present? @@ -149,44 +149,44 @@ module Homebrew return if all_formulae.empty? commits = all_formulae.filter_map do |formula_name| - formula = Formula[formula_name] - raise FormulaUnspecifiedError if formula.blank? + commit_formula = Formula[formula_name] + raise FormulaUnspecifiedError if commit_formula.blank? - formula_spec = formula.stable - odie "#{formula}: no stable specification found!" if formula_spec.blank? + commit_formula_spec = commit_formula.stable + odie "#{commit_formula}: no stable specification found!" if commit_formula_spec.blank? formula_pr_message = "" new_url = args.url new_version = args.version - check_new_version(formula, tap_remote_repo, version: new_version) if new_version.present? + check_new_version(commit_formula, tap_remote_repo, version: new_version) if new_version.present? - opoo "This formula has patches that may be resolved upstream." if formula.patchlist.present? - if formula.resources.any? { |resource| !resource.name.start_with?("homebrew-") } + opoo "This formula has patches that may be resolved upstream." if commit_formula.patchlist.present? + if commit_formula.resources.any? { |resource| !resource.name.start_with?("homebrew-") } opoo "This formula has resources that may need to be updated." end - old_mirrors = formula_spec.mirrors + old_mirrors = commit_formula_spec.mirrors new_mirrors ||= args.mirror if new_url.present? && (new_mirror = determine_mirror(new_url)) new_mirrors ||= [new_mirror] - check_for_mirrors(formula.name, old_mirrors, new_mirrors) + check_for_mirrors(commit_formula.name, old_mirrors, new_mirrors) end - old_hash = formula_spec.checksum&.hexdigest + old_hash = commit_formula_spec.checksum&.hexdigest new_hash = args.sha256 new_tag = args.tag new_revision = args.revision - old_url = T.must(formula_spec.url) - old_tag = formula_spec.specs[:tag] - old_formula_version = formula_version(formula) + old_url = T.must(commit_formula_spec.url) + old_tag = commit_formula_spec.specs[:tag] + old_formula_version = formula_version(commit_formula) old_version = old_formula_version.to_s forced_version = new_version.present? new_url_hash = if new_url.present? && new_hash.present? - check_new_version(formula, tap_remote_repo, url: new_url) if new_version.blank? + check_new_version(commit_formula, tap_remote_repo, url: new_url) if new_version.blank? true elsif new_tag.present? && new_revision.present? - check_new_version(formula, tap_remote_repo, url: old_url, tag: new_tag) if new_version.blank? + check_new_version(commit_formula, tap_remote_repo, url: old_url, tag: new_tag) if new_version.blank? false elsif old_hash.blank? if new_tag.blank? && new_version.blank? && new_revision.blank? @@ -201,17 +201,17 @@ module Homebrew and old tag are both #{new_tag}. EOS end - check_new_version(formula, tap_remote_repo, url: old_url, tag: new_tag) if new_version.blank? - resource_path, forced_version = fetch_resource_and_forced_version(formula, new_version, old_url, + check_new_version(commit_formula, tap_remote_repo, url: old_url, tag: new_tag) if new_version.blank? + resource_path, forced_version = fetch_resource_and_forced_version(commit_formula, new_version, old_url, tag: new_tag) new_revision = Utils.popen_read("git", "-C", resource_path.to_s, "rev-parse", "-q", "--verify", "HEAD") new_revision = new_revision.strip elsif new_revision.blank? - odie "#{formula}: the current URL requires specifying a `--revision=` argument." + odie "#{commit_formula}: the current URL requires specifying a `--revision=` argument." end false elsif new_url.blank? && new_version.blank? - raise UsageError, "#{formula}: no `--url` or `--version` argument specified!" + raise UsageError, "#{commit_formula}: no `--url` or `--version` argument specified!" else next unless new_version.present? @@ -231,21 +231,21 @@ module Homebrew #{new_url} EOS end - check_new_version(formula, tap_remote_repo, url: new_url) if new_version.blank? - resource_path, forced_version = fetch_resource_and_forced_version(formula, new_version, new_url) + check_new_version(commit_formula, tap_remote_repo, url: new_url) if new_version.blank? + resource_path, forced_version = fetch_resource_and_forced_version(commit_formula, new_version, new_url) Utils::Tar.validate_file(resource_path) new_hash = resource_path.sha256 end replacement_pairs = [] - if formula.revision.nonzero? + if commit_formula.revision.nonzero? replacement_pairs << [ /^ revision \d+\n(\n( head "))?/m, "\\2", ] end - replacement_pairs += formula_spec.mirrors.map do |mirror| + replacement_pairs += commit_formula_spec.mirrors.map do |mirror| [ / +mirror "#{Regexp.escape(mirror)}"\n/m, "", @@ -255,7 +255,7 @@ module Homebrew replacement_pairs += if new_url_hash.present? [ [ - /#{Regexp.escape(T.must(formula_spec.url))}/, + /#{Regexp.escape(T.must(commit_formula_spec.url))}/, new_url, ], [ @@ -266,35 +266,35 @@ module Homebrew elsif new_tag.present? [ [ - /tag:(\s+")#{formula_spec.specs[:tag]}(?=")/, + /tag:(\s+")#{commit_formula_spec.specs[:tag]}(?=")/, "tag:\\1#{new_tag}\\2", ], [ - formula_spec.specs[:revision], + commit_formula_spec.specs[:revision], new_revision, ], ] elsif new_url.present? [ [ - /#{Regexp.escape(T.must(formula_spec.url))}/, + /#{Regexp.escape(T.must(commit_formula_spec.url))}/, new_url, ], [ - formula_spec.specs[:revision], + commit_formula_spec.specs[:revision], new_revision, ], ] else [ [ - formula_spec.specs[:revision], + commit_formula_spec.specs[:revision], new_revision, ], ] end - old_contents = formula.path.read + old_contents = commit_formula.path.read if new_mirrors.present? && new_url.present? replacement_pairs << [ @@ -331,28 +331,28 @@ module Homebrew "", ] end - new_contents = Utils::Inreplace.inreplace_pairs(formula.path, + new_contents = Utils::Inreplace.inreplace_pairs(commit_formula.path, replacement_pairs.uniq.compact, read_only_run: args.dry_run?, silent: args.quiet?) - new_formula_version = formula_version(formula, new_contents) + new_formula_version = formula_version(commit_formula, new_contents) if new_formula_version < old_formula_version - formula.path.atomic_write(old_contents) unless args.dry_run? + commit_formula.path.atomic_write(old_contents) unless args.dry_run? odie <<~EOS You need to bump this formula manually since changing the version from #{old_formula_version} to #{new_formula_version} would be a downgrade. EOS elsif new_formula_version == old_formula_version - formula.path.atomic_write(old_contents) unless args.dry_run? + commit_formula.path.atomic_write(old_contents) unless args.dry_run? odie <<~EOS You need to bump this formula manually since the new version and old version are both #{new_formula_version}. EOS end - alias_rename = alias_update_pair(formula, new_formula_version) + alias_rename = alias_update_pair(commit_formula, new_formula_version) if alias_rename.present? ohai "Renaming alias #{alias_rename.first} to #{alias_rename.last}" alias_rename.map! { |a| tap.alias_dir/a } @@ -368,11 +368,11 @@ module Homebrew silent: args.quiet?, ignore_non_pypi_packages: true - update_matching_version_resources! formula, + update_matching_version_resources! commit_formula, version: new_formula_version.to_s end - if resources_checked.nil? && formula.resources.any? do |resource| + if resources_checked.nil? && commit_formula.resources.any? do |resource| resource.livecheck.formula != :parent && !resource.name.start_with?("homebrew-") end formula_pr_message += <<~EOS @@ -405,12 +405,12 @@ module Homebrew end { - sourcefile_path: formula.path, + sourcefile_path: commit_formula.path, old_contents:, - commit_message: "#{formula.name} #{args.version}", + commit_message: "#{commit_formula.name} #{args.version}", additional_files: alias_rename, formula_pr_message:, - formula_name: formula.name, + formula_name: commit_formula.name, new_version: new_formula_version, } end @@ -438,7 +438,7 @@ module Homebrew new_formula_version = T.must(commits.first)[:new_version] pr_title = if args.bump_synced.nil? - "#{named_formula.name} #{new_formula_version}" + "#{formula.name} #{new_formula_version}" else "#{Array(args.bump_synced).join(" ")} #{new_formula_version}" end @@ -455,7 +455,7 @@ module Homebrew commits:, remote:, remote_branch:, - branch_name: "bump-#{named_formula.name}-#{new_formula_version}", + branch_name: "bump-#{formula.name}-#{new_formula_version}", pr_title:, previous_branch:, tap: tap, From de8c088d15cbd43cfccb0552cc9b2b9b32707cbb Mon Sep 17 00:00:00 2001 From: Bevan Kay Date: Tue, 22 Apr 2025 21:37:54 +1000 Subject: [PATCH 3/3] fix opening PR from fork --- Library/Homebrew/utils/github.rb | 49 +++++++++++++------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/Library/Homebrew/utils/github.rb b/Library/Homebrew/utils/github.rb index feee56ccd9..bb549580a4 100644 --- a/Library/Homebrew/utils/github.rb +++ b/Library/Homebrew/utils/github.rb @@ -713,34 +713,33 @@ module GitHub tap_remote_repo = info[:tap_remote_repo] || tap.full_name pr_message = info[:pr_message] pr_title = info[:pr_title] - commits = info[:commits] - username = tap.user remote_url = Utils.popen_read("git", "remote", "get-url", "--push", "origin").chomp + username = tap.user tap.path.cd do + if args.no_fork? + remote_url = Utils.popen_read("git", "remote", "get-url", "--push", "origin").chomp + username = tap.user + add_auth_token_to_url!(remote_url) + else + begin + remote_url, username = forked_repo_info!(tap_remote_repo, org: args.fork_org) + rescue *API::ERRORS => e + commits.each do |commit| + commit[:sourcefile_path].atomic_write(commit[:old_contents]) + end + odie "Unable to fork: #{e.message}!" + end + end + + next if args.dry_run? + require "utils/popen" git_dir = Utils.popen_read("git", "rev-parse", "--git-dir").chomp shallow = !git_dir.empty? && File.exist?("#{git_dir}/shallow") - unless args.commit? - if args.no_fork? - remote_url = Utils.popen_read("git", "remote", "get-url", "--push", "origin").chomp - add_auth_token_to_url!(remote_url) - username = tap.user - else - begin - forked_repo_info!(tap_remote_repo, org: args.fork_org) - rescue *API::ERRORS => e - commits.each do |commit| - commit[:sourcefile_path].atomic_write(commit[:old_contents]) - end - odie "Unable to fork: #{e.message}!" - end - end - - safe_system "git", "fetch", "--unshallow", "origin" if shallow - end + safe_system "git", "fetch", "--unshallow", "origin" if !args.commit? && shallow safe_system "git", "checkout", "--no-track", "-b", branch, "#{remote}/#{remote_branch}" unless args.commit? Utils::Git.set_name_email! end @@ -758,14 +757,6 @@ module GitHub changed_files += additional_files if additional_files.present? if args.dry_run? || (args.write_only? && !args.commit?) - remote_url = if args.no_fork? - Utils.popen_read("git", "remote", "get-url", "--push", "origin").chomp - else - fork_message = "try to fork repository with GitHub API" \ - "#{" into `#{args.fork_org}` organization" if args.fork_org}" - ohai fork_message - "FORK_URL" - end ohai "git checkout --no-track -b #{branch} #{remote}/#{remote_branch}" ohai "git fetch --unshallow origin" if shallow ohai "git add #{changed_files.join(" ")}" @@ -784,7 +775,7 @@ module GitHub end end - return if args.commit? + return if args.commit? || args.dry_run? tap.path.cd do system_command!("git", args: ["push", "--set-upstream", remote_url, "#{branch}:#{branch}"],