Skip to content

Commit 8cba9f7

Browse files
committed
github: add rake task to backport PR
you can simulate on manually: GITHUB_TOKEN=... rake backport:v1_19 Signed-off-by: Kentaro Hayashi <[email protected]>
1 parent b4dc14d commit 8cba9f7

File tree

4 files changed

+213
-0
lines changed

4 files changed

+213
-0
lines changed

.github/workflows/backport.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Benchmark
2+
3+
on:
4+
schedule:
5+
# Sun 10:00 (JST)
6+
- cron: '0 1 * * 0'
7+
workflow_dispatch:
8+
9+
permissions: read-all
10+
11+
concurrency:
12+
group: ${{ github.head_ref || github.sha }}-${{ github.workflow }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
test:
17+
runs-on: ubuntu-latest
18+
continue-on-error: false
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
ruby-version: ['3.4']
23+
task: ['backport:v1_16', 'backport:v1_19']
24+
25+
name: Backport PR on ${{ matrix.os }}
26+
steps:
27+
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
28+
- name: Set up Ruby
29+
uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0
30+
with:
31+
ruby-version: ${{ matrix.ruby-version }}
32+
- name: Install dependencies
33+
run: bundle install
34+
- name: Run Benchmark
35+
shell: bash
36+
run: |
37+
bundle exec rake ${{ matrix.task }}

Rakefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require 'rake/testtask'
66
require 'rake/clean'
77

88
require_relative 'tasks/benchmark'
9+
require_relative 'tasks/backport'
910

1011
task test: [:base_test]
1112

tasks/backport.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
require_relative 'backport/backporter'
2+
3+
namespace :backport do
4+
5+
desc "Backport PR to v1.16 branch"
6+
task :v1_16 do
7+
backporter = PullRequestBackporter.new
8+
commands = ['--branch', 'v1.16', '--log-level', 'debug']
9+
if ENV['DRY_RUN']
10+
commands << '--dry-run'
11+
end
12+
if ENV['GITHUB_REPOSITORY']
13+
commands << '--upstream'
14+
commands << ENV['GITHUB_REPOSITORY']
15+
end
16+
backporter.run(commands)
17+
end
18+
19+
desc "Backport PR to v1.19 branch"
20+
task :v1_19 do
21+
commands = ['--branch', 'v1.19', '--log-level', 'debug']
22+
if ENV['DRY_RUN']
23+
commands << '--dry-run'
24+
end
25+
if ENV['GITHUB_REPOSITORY']
26+
commands << '--upstream'
27+
commands << ENV['GITHUB_REPOSITORY']
28+
end
29+
backporter = PullRequestBackporter.new
30+
backporter.run(commands)
31+
end
32+
end

tasks/backport/backporter.rb

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
require 'open-uri'
2+
require 'json'
3+
require 'optparse'
4+
require 'logger'
5+
6+
class PullRequestBackporter
7+
8+
def initialize
9+
@logger = Logger.new(STDOUT)
10+
@options = {
11+
upstream: "kenhys/fluentd",
12+
branch: "v1.16",
13+
dry_run: false,
14+
log_level: Logger::Severity::INFO
15+
}
16+
end
17+
18+
def parse_command_line(argv)
19+
opt = OptionParser.new
20+
opt.on('--upstream REPOSITORY',
21+
'Specify upstream repository (e.g. fluent/fluentd)') {|v| @options[:upstream] = v }
22+
opt.on('--branch BRANCH') {|v| @options[:branch] = v }
23+
opt.on('--dry-run') {|v| @options[:dry_run] = true }
24+
opt.on('--log-level LOG_LEVEL (e.g. debug,info)') {|v|
25+
@options[:log_level] = case v
26+
when "error"
27+
Logger::Severity::ERROR
28+
when "warn"
29+
Logger::Severity::WARN
30+
when "debug"
31+
Logger::Severity::DEBUG
32+
when "info"
33+
Logger::Severity::INFO
34+
else
35+
puts "unknown log level: <#{v}>"
36+
exit 1
37+
end
38+
}
39+
opt.parse!(argv)
40+
end
41+
42+
def collect_backports
43+
backports = []
44+
5.times.each do |page|
45+
@logger.debug "Collecting backport information"
46+
URI.open("https://api.github.com/repos/fluent/fluentd/pulls?state=closed&per_page=100&page=#{page+1}",
47+
"Accept" => "application/vnd.github+json",
48+
"Authorization" => "Bearer #{ENV['GITHUB_TOKEN']}",
49+
"X-GitHub-Api-Version" => "2022-11-28") do |request|
50+
JSON.parse(request.read).each do |pull_request|
51+
unless pull_request["labels"].empty?
52+
labels = pull_request["labels"].collect { |label| label["name"] }
53+
unless labels.include?("backport to #{@options[:branch]}")
54+
next
55+
end
56+
if labels.include?("backported")
57+
@logger.info "[DONE] \##{pull_request['number']} #{pull_request['title']} LABELS: #{pull_request['labels'].collect { |label| label['name'] }}"
58+
next
59+
end
60+
@logger.info "* \##{pull_request['number']} #{pull_request['title']} LABELS: #{pull_request['labels'].collect { |label| label['name'] }}"
61+
# merged into this commit
62+
@logger.debug "MERGE_COMMIT_SHA: #{pull_request['merge_commit_sha']}"
63+
body = pull_request["body"].gsub(/\*\*Which issue\(s\) this PR fixes\*\*: \r\n/,
64+
"**Which issue(s) this PR fixes**: \r\nBackport \##{pull_request['number']}\r\n")
65+
backports << {
66+
number: pull_request["number"],
67+
merge_commit_sha: pull_request["merge_commit_sha"],
68+
title: "Backport(#{@options[:branch]}): #{pull_request['title']} (\##{pull_request['number']})",
69+
body: body
70+
}
71+
end
72+
end
73+
end
74+
end
75+
backports
76+
end
77+
78+
def create_pull_requests
79+
backports = collect_backports
80+
if backports.size == 0
81+
@logger.debug "No need to backport: #{backports.size} PR"
82+
return
83+
end
84+
85+
failed = []
86+
backports.each do |backport|
87+
@logger.info "Backport #{backport[:number]} #{backport[:title]}"
88+
if @options[:dry_run]
89+
@logger.info "DRY_RUN: PR was created: \##{backport[:number]} #{backport[:title]}"
90+
next
91+
end
92+
begin
93+
branch = "backport-#{backport[:number]}"
94+
@logger.debug "git switch --create #{branch} --track origin/#{@options[:branch]}"
95+
IO.popen(["git", "switch", "--create", branch, "--track", "origin/#{@options[:branch]}"]) do |io|
96+
@logger.debug io.read
97+
end
98+
@logger.info `git branch`
99+
@logger.info "cherry-pick for #{backport[:number]}"
100+
@logger.debug "git cherry-pick --signoff #{backport[:merge_commit_sha]}"
101+
IO.popen(["git", "cherry-pick", "--signoff", backport[:merge_commit_sha]]) do |io|
102+
@logger.debug io.read
103+
end
104+
if $? != 0
105+
@logger.warn "Give up cherry-pick for #{backport[:number]}"
106+
@logger.debug `git cherry-pick --abort`
107+
failed << backport
108+
next
109+
else
110+
@logger.info "Push branch: #{branch}"
111+
@logger.debug `git push origin #{branch}`
112+
end
113+
114+
@logger.debug "Create pull request: #{branch}"
115+
upstream_repo = "/repos/#{@options[:upstream]}/pulls"
116+
owner = @options[:upstream].split('/').first
117+
head = "#{owner}:#{branch}"
118+
IO.popen(["gh", "api", "--method", "POST",
119+
"-H", "Accept: application/vnd.github+json",
120+
"-H", "X-GitHub-Api-Version: 2022-11-28",
121+
upstream_repo,
122+
"-f", "title=#{backport[:title]}",
123+
"-f", "body=#{backport[:body]}",
124+
"-f", "head=#{head}",
125+
"-f", "base=#{@options[:branch]}"]) do |io|
126+
json = JSON.parse(io.read)
127+
@logger.info "PR was created: #{json['url']}"
128+
end
129+
rescue => e
130+
@logger.error "ERROR: #{backport[:number]} #{e.message}"
131+
end
132+
end
133+
failed.each do |backport|
134+
@logger.error "FAILED: #{backport[:number]} #{backport[:title]}"
135+
end
136+
end
137+
138+
def run(argv)
139+
parse_command_line(argv)
140+
@logger.info("Target upstream: #{@options[:upstream]} target branch: #{@options[:branch]}")
141+
create_pull_requests
142+
end
143+
end

0 commit comments

Comments
 (0)