mirror of
https://github.com/Homebrew/brew.git
synced 2026-08-12 22:29:27 +04:00
- Make every statically-poked method public and call it directly, keeping `public_send` only for dynamically-named public methods. - Read and write state through public `attr_*` accessors instead of instance variable reflection. - Enable `Homebrew/NoSendInTests` and `Homebrew/NoInstanceVariableAccessInTests` now the test suite is clean, so neither pattern creeps back in. - Keep rare justified disables, e.g. `Module#remove_const` is private core Ruby and raw memoisation state has no accessor.
169 lines
6.1 KiB
Ruby
169 lines
6.1 KiB
Ruby
# typed: strict
|
|
# frozen_string_literal: true
|
|
|
|
require "system_command"
|
|
require "tap"
|
|
require "utils/formatter"
|
|
require "utils/git"
|
|
require "utils/github"
|
|
require "utils/output"
|
|
require "utils/popen"
|
|
|
|
module Homebrew
|
|
# @api internal
|
|
module Bump
|
|
extend SystemCommand::Mixin
|
|
extend Utils::Output::Mixin
|
|
|
|
class Commit < T::Struct
|
|
const :sourcefile_path, Pathname
|
|
const :old_contents, String
|
|
const :commit_message, String
|
|
const :additional_files, T::Array[Pathname], default: []
|
|
end
|
|
|
|
class BumpInfo < T::Struct
|
|
const :package_tap, Tap
|
|
const :branch_name, String
|
|
const :pr_title, String
|
|
const :pr_message, String
|
|
const :commits, T::Array[Commit]
|
|
end
|
|
|
|
sig { params(command: String, user_message: T.nilable(String)).returns(String) }
|
|
def self.pr_message(command, user_message:)
|
|
pr_message = ""
|
|
if user_message.present?
|
|
pr_message += <<~EOS
|
|
#{user_message}
|
|
|
|
---
|
|
|
|
EOS
|
|
end
|
|
pr_message += "Created with `brew #{command}`."
|
|
pr_message
|
|
end
|
|
|
|
sig { params(info: BumpInfo, dry_run: T::Boolean, no_fork: T::Boolean, fork_org: T.nilable(String), commit: T::Boolean).returns(T.nilable(String)) }
|
|
def self.create_pr(info, dry_run: false, no_fork: false, fork_org: nil, commit: false)
|
|
tap = info.package_tap
|
|
branch = info.branch_name
|
|
pr_message = info.pr_message
|
|
pr_title = info.pr_title
|
|
commits = info.commits
|
|
|
|
tap_remote_repo = tap.remote_repository
|
|
raise ArgumentError, "The tap #{tap.name} does not have a remote repository!" unless tap_remote_repo
|
|
|
|
remote_branch = tap.git_repository.origin_branch_name
|
|
raise "The tap #{tap.name} does not have a default branch!" if remote_branch.blank?
|
|
|
|
remote_url = T.let(nil, T.nilable(String))
|
|
username = T.let(nil, T.nilable(String))
|
|
|
|
tap.path.cd do
|
|
if 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: fork_org)
|
|
rescue *GitHub::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 dry_run
|
|
|
|
git_dir = Utils.popen_read("git", "rev-parse", "--git-dir").chomp
|
|
shallow = !git_dir.empty? && File.exist?("#{git_dir}/shallow")
|
|
safe_system "git", "fetch", "--unshallow", "origin" if !commit && shallow
|
|
safe_system "git", "checkout", "--no-track", "-b", branch, "origin/#{remote_branch}" unless commit
|
|
Utils::Git.set_name_email!
|
|
end
|
|
|
|
commits.each do |commit|
|
|
sourcefile_path = commit.sourcefile_path
|
|
commit_message = commit.commit_message
|
|
additional_files = commit.additional_files
|
|
|
|
sourcefile_path.parent.cd do
|
|
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 dry_run
|
|
ohai "git checkout --no-track -b #{branch} origin/#{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 #{redacted_url(remote_url)} #{branch}:#{branch}"
|
|
ohai "git checkout --quiet -"
|
|
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 commit || dry_run
|
|
return unless remote_url
|
|
|
|
tap.path.cd do
|
|
system_command!("git", args: ["push", "--set-upstream", remote_url, "#{branch}:#{branch}"],
|
|
print_stdout: true)
|
|
safe_system "git", "checkout", "--quiet", "-"
|
|
|
|
begin
|
|
return GitHub.create_pull_request(tap_remote_repo, pr_title,
|
|
"#{username}:#{branch}", remote_branch, pr_message)["html_url"]
|
|
rescue *GitHub::API::ERRORS => e
|
|
commits.each do |commit|
|
|
commit.sourcefile_path.atomic_write(commit.old_contents)
|
|
end
|
|
odie "Unable to open pull request for #{tap_remote_repo}: #{e.message}!"
|
|
end
|
|
end
|
|
end
|
|
|
|
sig { params(url: String).returns(String) }
|
|
private_class_method def self.add_auth_token_to_url!(url)
|
|
if GitHub::API.credentials_type == :env_token
|
|
url.sub!(%r{^https://github\.com/}, "https://x-access-token:#{GitHub::API.credentials}@github.com/")
|
|
end
|
|
url
|
|
end
|
|
|
|
# Redact any token `add_auth_token_to_url!` embedded, so dry-run output doesn't leak it.
|
|
sig { params(url: T.nilable(String)).returns(String) }
|
|
def self.redacted_url(url)
|
|
Formatter.redact_secrets(url.to_s, [GitHub::API.credentials].compact)
|
|
end
|
|
|
|
sig { params(tap_remote_repo: String, org: T.nilable(String)).returns([String, String]) }
|
|
private_class_method def self.forked_repo_info!(tap_remote_repo, org: nil)
|
|
response = GitHub.create_fork(tap_remote_repo, org:)
|
|
# GitHub API responds immediately but fork takes a few seconds to be ready.
|
|
sleep 1 until GitHub.fork_exists?(tap_remote_repo, org:)
|
|
remote_url = if system("git", "config", "--local", "--get-regexp", "remote..*.url", "git@github.com:.*")
|
|
response.fetch("ssh_url")
|
|
else
|
|
add_auth_token_to_url!(response.fetch("clone_url"))
|
|
end
|
|
username = response.fetch("owner").fetch("login")
|
|
[remote_url, username]
|
|
end
|
|
end
|
|
end
|