-
Notifications
You must be signed in to change notification settings - Fork 1.4k
github: add rake task to backport PR #5169
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
Open
kenhys
wants to merge
2
commits into
fluent:master
Choose a base branch
from
kenhys:backport-helper
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+246
−0
Open
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| name: Backport | ||
|
|
||
| on: | ||
| schedule: | ||
| # Sun 10:00 (JST) | ||
| - cron: '0 1 * * 0' | ||
| workflow_dispatch: | ||
|
|
||
| permissions: read-all | ||
|
|
||
| concurrency: | ||
| group: ${{ github.head_ref || github.sha }}-${{ github.workflow }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| test: | ||
| runs-on: ubuntu-latest | ||
| continue-on-error: false | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| ruby-version: ['3.4'] | ||
| task: ['backport:v1_16', 'backport:v1_19'] | ||
|
|
||
| name: Backport PR on ${{ matrix.os }} | ||
| steps: | ||
| - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 | ||
| - name: Set up Ruby | ||
| uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0 | ||
| with: | ||
| ruby-version: ${{ matrix.ruby-version }} | ||
| - name: Install dependencies | ||
| run: bundle install | ||
| - name: Run Benchmark | ||
| shell: bash | ||
| run: | | ||
| bundle exec rake ${{ matrix.task }} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| require_relative 'backport/backporter' | ||
|
|
||
| =begin | ||
|
|
||
| When you want to manually execute backporting, set the following | ||
| environment variables: | ||
|
|
||
| * GITHUB_REPOSITORY: fluent/fluentd | ||
| * GITHUB_TOKEN: ${PERSONAL_ACCESS_TOKEN} | ||
|
|
||
| Optional: | ||
|
|
||
| * REPOSITORY_REMOTE: origin | ||
| If you execute in forked repository, it might be 'upstream' | ||
|
|
||
| =end | ||
|
|
||
| def append_additional_arguments(commands) | ||
| if ENV['DRY_RUN'] | ||
| commands << '--dry-run' | ||
| end | ||
| if ENV['GITHUB_REPOSITORY'] | ||
| commands << '--upstream' | ||
| commands << ENV['GITHUB_REPOSITORY'] | ||
| end | ||
| if ENV['REPOSITORY_REMOTE'] | ||
| commands << '--remote' | ||
| commands << ENV['REPOSITORY_REMOTE'] | ||
| end | ||
| commands | ||
| end | ||
|
|
||
| namespace :backport do | ||
|
|
||
| desc "Backport PR to v1.16 branch" | ||
| task :v1_16 do | ||
| backporter = PullRequestBackporter.new | ||
| commands = ['--branch', 'v1.16', '--log-level', 'debug'] | ||
| commands = append_additional_arguments(commands) | ||
| backporter.run(commands) | ||
| end | ||
|
|
||
| desc "Backport PR to v1.19 branch" | ||
| task :v1_19 do | ||
| commands = ['--branch', 'v1.19', '--log-level', 'debug'] | ||
| commands = append_additional_arguments(commands) | ||
| backporter = PullRequestBackporter.new | ||
| backporter.run(commands) | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| require 'open-uri' | ||
| require 'json' | ||
| require 'optparse' | ||
| require 'logger' | ||
|
|
||
| class PullRequestBackporter | ||
|
|
||
| def initialize | ||
| @logger = Logger.new(STDOUT) | ||
| @options = { | ||
| upstream: "fluent/fluentd", | ||
| branch: "v1.16", | ||
| dry_run: false, | ||
| log_level: Logger::Severity::INFO, | ||
| remote: 'origin' | ||
| } | ||
| end | ||
|
|
||
| def current_branch | ||
| branch = IO.popen(["git", "branch", "--contains"]) do |io| | ||
| io.read | ||
| end | ||
| branch.split.last | ||
| end | ||
|
|
||
| def parse_command_line(argv) | ||
| opt = OptionParser.new | ||
| opt.on('--upstream REPOSITORY', | ||
| 'Specify upstream repository (e.g. fluent/fluentd)') {|v| @options[:upstream] = v } | ||
| opt.on('--branch BRANCH') {|v| @options[:branch] = v } | ||
| opt.on('--dry-run') {|v| @options[:dry_run] = true } | ||
| opt.on('--log-level LOG_LEVEL (e.g. debug,info)') {|v| | ||
| @options[:log_level] = case v | ||
| when "error" | ||
| Logger::Severity::ERROR | ||
| when "warn" | ||
| Logger::Severity::WARN | ||
| when "debug" | ||
| Logger::Severity::DEBUG | ||
| when "info" | ||
| Logger::Severity::INFO | ||
| else | ||
| puts "unknown log level: <#{v}>" | ||
| exit 1 | ||
| end | ||
| } | ||
| opt.on('--remote REMOTE') {|v| @options[:remote] = v } | ||
| opt.parse!(argv) | ||
| end | ||
|
|
||
| def collect_backports | ||
| backports = [] | ||
| pages = 5 | ||
| pages.times.each do |page| | ||
| @logger.debug "Collecting backport information (#{page + 1}/#{pages})" | ||
| URI.open("https://api.github.com/repos/fluent/fluentd/pulls?state=closed&per_page=100&page=#{page+1}", | ||
| "Accept" => "application/vnd.github+json", | ||
| "Authorization" => "Bearer #{ENV['GITHUB_TOKEN']}", | ||
| "X-GitHub-Api-Version" => "2022-11-28") do |request| | ||
| JSON.parse(request.read).each do |pull_request| | ||
| unless pull_request["labels"].empty? | ||
| labels = pull_request["labels"].collect { |label| label["name"] } | ||
| unless labels.include?("backport to #{@options[:branch]}") | ||
| next | ||
| end | ||
| if labels.include?("backported") | ||
| @logger.info "[DONE] \##{pull_request['number']} #{pull_request['title']} LABELS: #{pull_request['labels'].collect { |label| label['name'] }}" | ||
| next | ||
| end | ||
| @logger.info "* \##{pull_request['number']} #{pull_request['title']} LABELS: #{pull_request['labels'].collect { |label| label['name'] }}" | ||
| # merged into this commit | ||
| @logger.debug "MERGE_COMMIT_SHA: #{pull_request['merge_commit_sha']}" | ||
| body = pull_request["body"].gsub(/\*\*Which issue\(s\) this PR fixes\*\*: \r\n/, | ||
| "**Which issue(s) this PR fixes**: \r\nBackport \##{pull_request['number']}\r\n") | ||
| backports << { | ||
| number: pull_request["number"], | ||
| merge_commit_sha: pull_request["merge_commit_sha"], | ||
| title: "Backport(#{@options[:branch]}): #{pull_request['title']} (\##{pull_request['number']})", | ||
| body: body | ||
| } | ||
| end | ||
| end | ||
| end | ||
| end | ||
| backports | ||
| end | ||
|
|
||
| def create_pull_requests | ||
| backports = collect_backports | ||
| if backports.empty? | ||
| @logger.info "No need to backport pull requests" | ||
| return | ||
| end | ||
|
|
||
| failed = [] | ||
| original_branch = current_branch | ||
| backports.each do |backport| | ||
| @logger.info "Backport #{backport[:number]} #{backport[:title]}" | ||
| if @options[:dry_run] | ||
| @logger.info "DRY_RUN: PR was created: \##{backport[:number]} #{backport[:title]}" | ||
| next | ||
| end | ||
| begin | ||
| branch = "backport-to-#{@options[:branch]}/pr#{backport[:number]}" | ||
| @logger.debug "git switch --create #{branch} --track #{@options[:remote]}/#{@options[:branch]}" | ||
| IO.popen(["git", "switch", "--create", branch, "--track", "#{@options[:remote]}/#{@options[:branch]}"]) do |io| | ||
| @logger.debug io.read | ||
| end | ||
| @logger.info `git branch` | ||
| @logger.info "cherry-pick for #{backport[:number]}" | ||
| @logger.debug "git cherry-pick --signoff #{backport[:merge_commit_sha]}" | ||
| IO.popen(["git", "cherry-pick", "--signoff", backport[:merge_commit_sha]]) do |io| | ||
| @logger.debug io.read | ||
| end | ||
| if $? != 0 | ||
| @logger.warn "Give up cherry-pick for #{backport[:number]}" | ||
| @logger.debug `git cherry-pick --abort` | ||
| failed << backport | ||
| next | ||
| else | ||
| @logger.info "Push branch: #{branch}" | ||
| @logger.debug `git push origin #{branch}` | ||
| end | ||
|
|
||
| upstream_repo = "/repos/#{@options[:upstream]}/pulls" | ||
| owner = @options[:upstream].split('/').first | ||
| head = "#{owner}:#{branch}" | ||
| @logger.debug "Create pull request repo: #{upstream_repo} head: #{head} base: #{@options[:branch]}" | ||
| IO.popen(["gh", "api", "--method", "POST", | ||
| "-H", "Accept: application/vnd.github+json", | ||
| "-H", "X-GitHub-Api-Version: 2022-11-28", | ||
| upstream_repo, | ||
| "-f", "title=#{backport[:title]}", | ||
| "-f", "body=#{backport[:body]}", | ||
| "-f", "head=#{head}", | ||
| "-f", "base=#{@options[:branch]}"]) do |io| | ||
| json = JSON.parse(io.read) | ||
| @logger.info "PR was created: #{json['url']}" | ||
| end | ||
| rescue => e | ||
| @logger.error "ERROR: #{backport[:number]} #{e.message}" | ||
| ensure | ||
| IO.popen(["git", "checkout", original_branch]) do |io| | ||
| @logger.debug io.read | ||
| end | ||
| end | ||
| end | ||
| failed.each do |backport| | ||
| @logger.error "FAILED: #{backport[:number]} #{backport[:title]}" | ||
| end | ||
| end | ||
|
|
||
| def run(argv) | ||
| parse_command_line(argv) | ||
| @logger.info("Target upstream: #{@options[:upstream]} target branch: #{@options[:branch]}") | ||
| create_pull_requests | ||
| end | ||
| end | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.