-
Notifications
You must be signed in to change notification settings - Fork 110
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add workflow script for repository management
- Loading branch information
Showing
8 changed files
with
375 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,34 @@ | ||
name: 'Check review status' | ||
|
||
on: | ||
pull_request: {} | ||
|
||
jobs: | ||
check_review_status: | ||
runs-on: 'ubuntu-latest' | ||
permissions: | ||
pull-requests: read | ||
contents: read | ||
env: | ||
BASE_SHA: ${{ github.event.pull_request.base.sha }} | ||
HEAD_SHA: ${{ github.event.pull_request.head.sha }} | ||
PR_AUTHOR: ${{ github.event.pull_request.user.login }} | ||
GH_REPO: ${{ github.repository }} | ||
steps: | ||
- uses: actions/checkout@v4 | ||
with: | ||
filter: 'blob:none' | ||
fetch-depth: 0 | ||
ref: main | ||
- name: Changed gems and non gem files | ||
id: changes | ||
run: | | ||
ruby .github/workflows/pr_bot/changed_files.rb | ||
- name: Check review status | ||
env: | ||
CHANGED_GEMS: ${{ steps.changes.outputs.gems }} | ||
CHANGED_NON_GEMS: ${{ steps.changes.outputs.non_gems }} | ||
PR_NUMBER: ${{ github.event.pull_request.number }} | ||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
run: | | ||
ruby .github/workflows/pr_bot/check_review_status.rb "$CHANGED_GEMS" "$CHANGED_NON_GEMS" "$PR_NUMBER" |
This file contains 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,55 @@ | ||
name: 'Merge a PR on a comment' | ||
|
||
on: | ||
issue_comment: | ||
types: [created] | ||
|
||
jobs: | ||
merge: | ||
if: ${{ github.event.issue.pull_request && github.event.comment.body == '/merge' }} | ||
runs-on: 'ubuntu-latest' | ||
permissions: | ||
pull-requests: write | ||
contents: write | ||
steps: | ||
- uses: actions/checkout@v4 | ||
with: | ||
filter: 'blob:none' | ||
fetch-depth: 0 | ||
ref: main | ||
- name: Fetch PR information | ||
id: pr_info | ||
env: | ||
PR_NUMBER: ${{ github.event.issue.number }} | ||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
GH_REPO: ${{ github.repository }} | ||
run: | | ||
pr="$(gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/${GH_REPO}/pulls/${PR_NUMBER})" | ||
author="$(echo "$pr" | jq -r .user.login)" | ||
head_sha="$(echo "$pr" | jq -r .head.sha)" | ||
base_sha="$(echo "$pr" | jq -r .base.sha)" | ||
echo "author=$author" >> $GITHUB_OUTPUT | ||
echo "head_sha=$head_sha" >> $GITHUB_OUTPUT | ||
echo "base_sha=$base_sha" >> $GITHUB_OUTPUT | ||
- name: Changed gems and non gem files | ||
env: | ||
BASE_SHA: ${{ steps.pr_info.outputs.base_sha }} | ||
HEAD_SHA: ${{ steps.pr_info.outputs.head_sha }} | ||
id: changes | ||
run: | | ||
ruby .github/workflows/pr_bot/changed_files.rb | ||
- name: Merge the PR | ||
env: | ||
BASE_SHA: ${{ steps.pr_info.outputs.base_sha }} | ||
HEAD_SHA: ${{ steps.pr_info.outputs.head_sha }} | ||
PR_AUTHOR: ${{ steps.pr_info.outputs.author }} | ||
CHANGED_GEMS: ${{ steps.changes.outputs.gems }} | ||
|
||
COMMENTED_BY: ${{ github.event.issue.user.login }} | ||
PR_NUMBER: ${{ github.event.issue.number }} | ||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
GH_REPO: ${{ github.repository }} | ||
run: | | ||
ruby .github/workflows/pr_bot/check_merge_ability.rb "$CHANGED_GEMS" | ||
gh pr merge "$PR_NUMBER" --repo "$GH_REPO" --merge |
This file contains 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,8 @@ | ||
require_relative "./utils" | ||
|
||
paths = `git diff --name-only -z #{BASE_SHA} #{HEAD_SHA}`.split("\0") | ||
changed_gems = paths.select { _1.start_with?("gems/") }.map { _1.split("/")[1] }.uniq | ||
changed_non_gems = paths.reject { _1.start_with?("gems/") } | ||
|
||
output :gems, JSON.generate(changed_gems) | ||
output :non_gems, JSON.generate(changed_non_gems) |
This file contains 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,17 @@ | ||
require_relative "./utils" | ||
|
||
def can_merge?(commented_by, author, gem_reviewers) | ||
return true if commented_by == author | ||
return true if administorators.include?(commented_by) | ||
return true if gem_reviewers.include?(commented_by) | ||
return false | ||
end | ||
|
||
def all_gem_reviewers(changed_gems) | ||
changed_gems.flat_map { |gem| gem_reviewers(gem, BASE_SHA) } | ||
end | ||
|
||
reviewers = all_gem_reviewers(JSON.parse(ARGV[0])) | ||
return if can_merge?(ENV['COMMENTED_BY'], PR_AUTHOR, reviewers) | ||
|
||
raise "You do not have permission to merge this PR." |
This file contains 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,49 @@ | ||
require_relative "./utils" | ||
require "json" | ||
|
||
def gem_accepted?(gem, approvers) | ||
reviewers = gem_reviewers(gem, BASE_SHA) | ||
|
||
# If reviewers is empty, it means that anyone cannot approve this PR. | ||
# So, we can merge this PR without approval. | ||
return true if reviewers.empty? | ||
|
||
# If the author is a reviewer, they can merge this PR themselves. | ||
return true if reviewers.include?(PR_AUTHOR) | ||
|
||
not (reviewers & approvers).empty? | ||
end | ||
|
||
def non_gem_accepted?(approvers) | ||
admins = administorators.map { _1['login'] } | ||
|
||
not (approvers & admins).empty? | ||
end | ||
|
||
def main(changed_gems, changed_non_gems, pr_number) | ||
approvers = approvements(HEAD_SHA, pr_number).map { _1['user']['login'] } | ||
|
||
status = 0 | ||
|
||
# Check gem files | ||
not_approved_gems = changed_gems.reject { |gem| gem_accepted?(gem, approvers) } | ||
unless not_approved_gems.empty? | ||
puts "The following gems are not approved yet:" | ||
puts not_approved_gems.join("\n") | ||
status = 1 | ||
end | ||
|
||
# Check non gem files | ||
if !changed_non_gems.empty? && !non_gem_accepted?(approvers) | ||
puts "The following files are changed, but not approved by the admin yet:" | ||
puts changed_non_gems.join("\n") | ||
status = 1 | ||
end | ||
|
||
exit status | ||
end | ||
|
||
changed_gems = JSON.parse(ARGV[0]) | ||
changed_non_gems = JSON.parse(ARGV[1]) | ||
pr_number = ARGV[2] | ||
main(changed_gems, changed_non_gems, pr_number) |
This file contains 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,72 @@ | ||
require 'securerandom' | ||
require 'open3' | ||
require 'yaml' | ||
require "json" | ||
|
||
BASE_SHA = ENV['BASE_SHA'] | ||
HEAD_SHA = ENV['HEAD_SHA'] | ||
PR_AUTHOR = ENV['PR_AUTHOR'] | ||
GH_REPO = ENV['GH_REPO'] | ||
|
||
def output(key, value) | ||
File.open(ENV['GITHUB_OUTPUT'], 'a') do |f| | ||
delimiter = SecureRandom.hex(20) | ||
f.puts "#{key}<<#{delimiter}" | ||
f.puts value | ||
f.puts delimiter | ||
end | ||
puts "Set #{key}=#{value}" | ||
end | ||
|
||
class CommandError < StandardError; end | ||
|
||
def sh!(*cmd, **opt) | ||
Open3.capture3(*cmd, **opt).then do |out, err, status| | ||
raise CommandError, "Unexpected status #{status.exitstatus}\n\n#{err}" unless status.success? | ||
|
||
out | ||
end | ||
end | ||
|
||
def git(*cmd, **opt) | ||
sh! 'git', *cmd, **opt | ||
end | ||
|
||
def git?(*cmd, **opt) | ||
git(*cmd, **opt) | ||
rescue CommandError | ||
nil | ||
end | ||
|
||
def gem_reviewers(gem, sha) | ||
begin | ||
yaml = git 'cat-file', '-p', "#{sha}:gems/#{gem}/_reviewers.yaml" | ||
rescue CommandError | ||
return [] | ||
end | ||
|
||
content = YAML.safe_load(yaml) | ||
content['reviewers'] | ||
end | ||
|
||
def log(msg) | ||
puts msg | ||
end | ||
|
||
def gh_api!(*args) | ||
resp = sh! 'gh', 'api', | ||
'-H', "Accept: application/vnd.github+json", | ||
'-H' 'X-GitHub-Api-Version: 2022-11-28', *args | ||
|
||
JSON.parse(resp) | ||
end | ||
|
||
def approvements(sha, pr_number) | ||
reviews = gh_api! "/repos/#{GH_REPO}/pulls/#{pr_number}/reviews" | ||
reviews.select { _1['state'] == 'APPROVED' && _1['commit_id'] == sha } | ||
end | ||
|
||
def administorators | ||
users = gh_api! "/repos/#{GH_REPO}/collaborators" | ||
users.select { _1['permissions']['admin'] } | ||
end |
This file contains 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,97 @@ | ||
require_relative "./utils" | ||
|
||
ADMINS = %w[@pocke] | ||
|
||
changed_gems = JSON.parse(ARGV[0]) | ||
changed_non_gems = JSON.parse(ARGV[1]) | ||
|
||
msgs = [<<~MSG] | ||
@#{PR_AUTHOR} Thanks for your contribution! | ||
Please follow the instructions below for each change. | ||
MSG | ||
|
||
# TODO: when the reviewer includes the author | ||
|
||
changed_gems.each do |gem| | ||
exist_in_base = git? 'cat-file', '-e', "#{BASE_SHA}:gems/#{gem}" | ||
exist_in_head = git? 'cat-file', '-e', "#{HEAD_SHA}:gems/#{gem}" | ||
|
||
msg = "## `#{gem}`\n\n" | ||
|
||
case | ||
when !exist_in_base # new gem | ||
msg << <<~MSG | ||
This RBS files are newly added. | ||
You can merge this PR immediately if the CI passes. | ||
Just comment `/merge` to merge this PR. | ||
MSG | ||
when exist_in_head # updated gem | ||
reviewers = gem_reviewers(gem, BASE_SHA) | ||
# TODO: if reviewers do not respond | ||
msg << <<~MSG | ||
You changed RBS files for an existing gem. | ||
MSG | ||
|
||
if reviewers.empty? | ||
msg << <<~MSG | ||
This gem does not have reviewers. So you can merge this PR immediately if the CI passes. | ||
We recommend you add yourself to the reviewers for this gem. | ||
# TODO: add a link to the document | ||
MSG | ||
elsif reviewers.include?(PR_AUTHOR) | ||
msg << <<~MSG | ||
You can merge this PR yourself because you are a reviewer of this gem. | ||
Just comment `/merge` to merge this PR. | ||
You can also request a review from other reviewers if you want. | ||
MSG | ||
else | ||
msg << <<~MSG | ||
You need to get approval from the reviewers of this gem. | ||
#{reviewers.map { "@#{_1}" }.join(', ')}, please review and approve the changes. | ||
After that, the PR author or the reviewers can merge this PR. | ||
Just comment `/merge` to merge this PR. | ||
MSG | ||
end | ||
when !exist_in_head # removed gem | ||
reviewers = gem_reviewers(gem, BASE_SHA) | ||
|
||
msg << <<~MSG | ||
You removed RBS files for this gem. | ||
MSG | ||
|
||
if reviewers.empty? | ||
msg << <<~MSG | ||
You can merge this PR immediately if the CI passes. | ||
Just comment `/merge` to merge this PR. | ||
MSG | ||
else | ||
msg << <<~MSG | ||
You need to get approval from the reviewers of this gem. | ||
#{reviewers.map { "@#{_1}" }.join(', ')}, please review and approve the changes. | ||
After that, the PR author or the reviewers can merge this PR. | ||
Just comment `/merge` to merge this PR. | ||
MSG | ||
end | ||
else | ||
raise "unreachable" | ||
end | ||
|
||
msgs << msg | ||
end | ||
|
||
unless changed_non_gems.empty? | ||
msgs << <<~MSG | ||
You changed non-gem files. | ||
#{ADMINS.join(', ')}, please review and approve the changes. | ||
MSG | ||
end | ||
|
||
body = msgs.join("\n\n-----\n\n") | ||
|
||
output :welcome_comment_body, body |
This file contains 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,43 @@ | ||
name: 'Test PR comment' | ||
|
||
on: | ||
pull_request: | ||
types: [opened] | ||
|
||
jobs: | ||
create_comment: | ||
runs-on: 'ubuntu-latest' | ||
permissions: | ||
pull-requests: write | ||
contents: read | ||
env: | ||
BASE_SHA: ${{ github.event.pull_request.base.sha }} | ||
HEAD_SHA: ${{ github.event.pull_request.head.sha }} | ||
PR_AUTHOR: ${{ github.event.pull_request.user.login }} | ||
GH_REPO: ${{ github.repository }} | ||
steps: | ||
- uses: actions/checkout@v4 | ||
with: | ||
filter: 'blob:none' | ||
fetch-depth: 0 | ||
ref: main | ||
- name: Changed gems and non gem files | ||
id: changes | ||
run: | | ||
# TODO: checkout the main branch | ||
ruby .github/workflows/pr_bot/changed_files.rb | ||
- name: Prepare comment body | ||
id: comment-body | ||
env: | ||
CHANGED_GEMS: ${{ steps.changes.outputs.gems }} | ||
CHANGED_NON_GEMS: ${{ steps.changes.outputs.non_gems }} | ||
run: | | ||
ruby .github/workflows/pr_bot/welcome_comment_body.rb "$CHANGED_GEMS" "$CHANGED_NON_GEMS" | ||
- name: test message | ||
env: | ||
PR_NUMBER: ${{ github.event.pull_request.number }} | ||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
GH_REPO: ${{ github.repository }} | ||
COMMENT_BODY: ${{ steps.comment-body.outputs.welcome_comment_body }} | ||
run: | ||
gh pr comment "$PR_NUMBER" --body "$COMMENT_BODY" --repo "$GH_REPO" |