From 0574ba436bb193eb899c22cc36799367364469e7 Mon Sep 17 00:00:00 2001 From: Jonathan Chang Date: Mon, 30 Mar 2020 00:47:38 +1100 Subject: [PATCH] dev-cmd: add pr-pull command --- Library/Homebrew/dev-cmd/pr-pull.rb | 143 ++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 Library/Homebrew/dev-cmd/pr-pull.rb diff --git a/Library/Homebrew/dev-cmd/pr-pull.rb b/Library/Homebrew/dev-cmd/pr-pull.rb new file mode 100644 index 0000000000..fe15b54c27 --- /dev/null +++ b/Library/Homebrew/dev-cmd/pr-pull.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require "cli/parser" +require "utils/github" +require "tmpdir" +require "bintray" + +module Homebrew + module_function + + def pr_pull_args + Homebrew::CLI::Parser.new do + usage_banner <<~EOS + `pr-pull` + + Download and publish bottles, and apply the bottle commit from a + pull request with artifacts generated from GitHub Actions. + Requires write access to the repository. + EOS + switch "--no-publish", + description: "Download the bottles, apply the bottle commit, and "\ + "upload the bottles to Bintray, but don't publish them." + switch "--no-upload", + description: "Download the bottles and apply the bottle commit, "\ + "but don't upload to Bintray." + switch "--dry-run", "-n", + description: "Print what would be done rather than doing it." + switch "--clean", + description: "Do not amend the commits from pull requests." + switch "--branch-okay", + description: "Do not warn if pulling to a branch besides master (useful for testing)." + switch "--resolve", + description: "When a patch fails to apply, leave in progress and allow user to resolve, instead "\ + "of aborting." + flag "--workflow=", + description: "Retrieve artifacts from the specified workflow (default: tests.yml)." + flag "--artifact=", + description: "Download artifacts with the specified name (default: bottles)." + flag "--bintray-org=", + description: "Upload to the specified Bintray organisation." + switch :verbose + switch :debug + min_named 1 + end + end + + def signoff!(pr, path: ".", dry_run: false) + message = Utils.popen_read "git", "-C", path, "log", "-1", "--pretty=%B" + close_message = "Closes ##{pr}." + message += "\n#{close_message}" unless message.include? close_message + if dry_run + puts "git commit --amend --signoff -m $message" + else + safe_system "git", "-C", path, "commit", "--amend", "--signoff", "--allow-empty", "-q", "-m", message + end + end + + def cherry_pick_pr!(pr, path: ".", dry_run: false) + if dry_run + puts <<~EOS + git fetch --force origin +refs/pull/#{pr}/head + git merge-base HEAD FETCH_HEAD + git cherry-pick --ff --allow-empty $merge_base..FETCH_HEAD + EOS + else + safe_system "git", "-C", path, "fetch", "--quiet", "--force", "origin", "+refs/pull/#{pr}/head" + merge_base = Utils.popen_read("git", "-C", path, "merge-base", "HEAD", "FETCH_HEAD").strip + commit_count = Utils.popen_read("git", "-C", path, "rev-list", "#{merge_base}..FETCH_HEAD").lines.count + + # git cherry-pick unfortunately has no quiet option + ohai "Cherry-picking #{commit_count} commit#{"s" unless commit_count == 1} from ##{pr}" + cherry_pick_args = "git", "-C", path, "cherry-pick", "--ff", "--allow-empty", "#{merge_base}..FETCH_HEAD" + result = Homebrew.args.verbose? ? system(*cherry_pick_args) : quiet_system(*cherry_pick_args) + + unless result + if Homebrew.args.resolve? + odie "Cherry-pick failed: try to resolve it." + else + system "git", "-C", path, "cherry-pick", "--abort" + odie "Cherry-pick failed!" + end + end + end + end + + def check_branch(path, ref) + branch = Utils.popen_read("git", "-C", path, "symbolic-ref", "--short", "HEAD").strip + + return if branch == ref || args.clean? || args.branch_okay? + + opoo "Current branch is #{branch}: do you need to pull inside #{ref}?" + end + + def pr_pull + pr_pull_args.parse + + bintray_user = ENV["HOMEBREW_BINTRAY_USER"] + bintray_key = ENV["HOMEBREW_BINTRAY_KEY"] + ENV.clear_sensitive_environment! + + if bintray_user.blank? || bintray_key.blank? + odie "Missing HOMEBREW_BINTRAY_USER or HOMEBREW_BINTRAY_KEY variables!" if !args.dry_run? && !args.no_upload? + else + bintray = Bintray.new(user: bintray_user, key: bintray_key, org: args.bintray_org) + end + + workflow = args.workflow || "tests.yml" + artifact = args.artifact || "bottles" + + args.named.each do |arg| + arg = "#{CoreTap.instance.default_remote}/pull/#{arg}" if arg.to_i.positive? + url_match = arg.match HOMEBREW_PULL_OR_COMMIT_URL_REGEX + _, user, repo, pr = *url_match + tap = Tap.fetch(user, repo) if repo.match?(HOMEBREW_OFFICIAL_REPO_PREFIXES_REGEX) + odie "Not a GitHub pull request: #{arg}" unless pr + + check_branch tap.path, "master" + + ohai "Fetching #{tap} pull request ##{pr}" + Dir.mktmpdir pr do |dir| + cd dir do + GitHub.fetch_artifact(user, repo, pr, dir, workflow_id: workflow, artifact_name: artifact) + cherry_pick_pr! pr, path: tap.path, dry_run: args.dry_run? + signoff! pr, path: tap.path, dry_run: args.dry_run? unless args.clean? + + if args.dry_run? + puts "brew bottle --merge --write #{Dir["*.json"].join " "}" + else + quiet_system "#{HOMEBREW_PREFIX}/bin/brew", "bottle", "--merge", "--write", *Dir["*.json"] + end + + next if args.no_upload? + + if args.dry_run? + puts "Upload bottles described by these JSON files to Bintray:\n #{Dir["*.json"].join("\n ")}" + else + bintray.upload_bottle_json Dir["*.json"], publish_package: !args.no_publish? + end + end + end + end + end +end