Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion .github/workflows/check_misc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,28 @@ jobs:
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 100 # for notify-slack-commits
token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }}

# Run this step first (even before `make up` in the next step) to make the notification available before any other failure
- name: Notify commit to Slack
run: ruby tool/notify-slack-commits.rb "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master
env:
GITHUB_OLD_SHA: ${{ github.event.before }}
GITHUB_NEW_SHA: ${{ github.event.after }}
SLACK_WEBHOOK_URL_ALERTS: ${{ secrets.SLACK_WEBHOOK_URL_ALERTS }}
SLACK_WEBHOOK_URL_COMMITS: ${{ secrets.SLACK_WEBHOOK_URL_COMMITS }}
SLACK_WEBHOOK_URL_RUBY_JP: ${{ secrets.SLACK_WEBHOOK_URL_RUBY_JP }}
if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }}
continue-on-error: true # The next auto-style should always run

- uses: ./.github/actions/setup/directories
with:
makeup: true
# Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN
checkout: '' # false (ref: https://github.com/actions/runner/issues/2238)

# Run this step first to make sure auto-style commits are pushed
# Run this step early to make sure auto-style commits are pushed
- name: Auto-correct code styles
run: |
set -x
Expand Down Expand Up @@ -119,6 +132,14 @@ jobs:
name: ${{ steps.docs.outputs.htmlout }}
if: ${{ steps.docs.outcome == 'success' }}

- name: Push PR notes to GitHub
run: ruby tool/notes-github-pr.rb "$(pwd)" "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" refs/heads/master
env:
GITHUB_OLD_SHA: ${{ github.event.before }}
GITHUB_NEW_SHA: ${{ github.event.after }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: ${{ github.repository == 'ruby/ruby' && github.ref == 'refs/heads/master' && github.event_name == 'push' }}

- uses: ./.github/actions/slack
with:
SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot
Expand Down
2 changes: 1 addition & 1 deletion lib/mkmf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ def expand_command(commands, envs = libpath_env)

# disable ASAN leak reporting - conftest programs almost always don't bother
# to free their memory.
envs['ASAN_OPTIONS'] = "detect_leaks=0" unless ENV.key?('ASAN_OPTIONS')
envs['LSAN_OPTIONS'] = "detect_leaks=0" unless ENV.key?('LSAN_OPTIONS')

return envs, expand[commands]
end
Expand Down
147 changes: 147 additions & 0 deletions tool/notes-github-pr.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env ruby
# Add GitHub pull request reference / author info to git notes.

require 'net/http'
require 'uri'
require 'tmpdir'
require 'json'
require 'yaml'

# Conversion for people whose GitHub account name and SVN_ACCOUNT_NAME are different.
GITHUB_TO_SVN = {
'amatsuda' => 'a_matsuda',
'matzbot' => 'git',
'jeremyevans' => 'jeremy',
'znz' => 'kazu',
'k-tsj' => 'ktsj',
'nurse' => 'naruse',
'ioquatix' => 'samuel',
'suketa' => 'suke',
'unak' => 'usa',
}

EMAIL_YML_URL = 'https://raw.githubusercontent.com/ruby/git.ruby-lang.org/refs/heads/master/config/email.yml'
SVN_TO_EMAILS = YAML.safe_load(Net::HTTP.get_response(URI(EMAIL_YML_URL)).tap(&:value).body)

class GitHub
ENDPOINT = URI.parse('https://api.github.com')

def initialize(access_token)
@access_token = access_token
end

# https://developer.github.com/changes/2019-04-11-pulls-branches-for-commit/
def pulls(owner:, repo:, commit_sha:)
resp = get("/repos/#{owner}/#{repo}/commits/#{commit_sha}/pulls", accept: 'application/vnd.github.groot-preview+json')
JSON.parse(resp.body)
end

# https://developer.github.com/v3/pulls/#get-a-single-pull-request
def pull_request(owner:, repo:, number:)
resp = get("/repos/#{owner}/#{repo}/pulls/#{number}")
JSON.parse(resp.body)
end

# https://developer.github.com/v3/users/#get-a-single-user
def user(username:)
resp = get("/users/#{username}")
JSON.parse(resp.body)
end

private

def get(path, accept: 'application/vnd.github.v3+json')
Net::HTTP.start(ENDPOINT.host, ENDPOINT.port, use_ssl: ENDPOINT.scheme == 'https') do |http|
headers = { 'Accept': accept, 'Authorization': "bearer #{@access_token}" }
http.get(path, headers).tap(&:value)
end
end
end

module Git
class << self
def abbrev_ref(refname, repo_path:)
git('rev-parse', '--symbolic', '--abbrev-ref', refname, repo_path: repo_path).strip
end

def rev_list(arg, first_parent: false, repo_path: nil)
git('rev-list', *[('--first-parent' if first_parent)].compact, arg, repo_path: repo_path).lines.map(&:chomp)
end

def commit_message(sha)
git('log', '-1', '--pretty=format:%B', sha)
end

def notes_message(sha)
git('log', '-1', '--pretty=format:%N', sha)
end

def committer_name(sha)
git('log', '-1', '--pretty=format:%cn', sha)
end

def committer_email(sha)
git('log', '-1', '--pretty=format:%cE', sha)
end

private

def git(*cmd, repo_path: nil)
env = {}
if repo_path
env['GIT_DIR'] = repo_path
end
out = IO.popen(env, ['git', *cmd], &:read)
unless $?.success?
abort "Failed to execute: git #{cmd.join(' ')}\n#{out}"
end
out
end
end
end

github = GitHub.new(ENV.fetch('GITHUB_TOKEN'))

repo_path, *rest = ARGV
rest.each_slice(3).map do |oldrev, newrev, refname|
branch = Git.abbrev_ref(refname, repo_path: repo_path)
next if branch != 'master' # we use pull requests only for master branches

Dir.mktmpdir do |workdir|
# Clone a branch and fetch notes
depth = Git.rev_list("#{oldrev}..#{newrev}", repo_path: repo_path).size + 50
system('git', 'clone', "--depth=#{depth}", "--branch=#{branch}", "file://#{repo_path}", workdir)
Dir.chdir(workdir)
system('git', 'fetch', 'origin', 'refs/notes/commits:refs/notes/commits')

updated = false
Git.rev_list("#{oldrev}..#{newrev}", first_parent: true).each do |sha|
github.pulls(owner: 'ruby', repo: 'ruby', commit_sha: sha).each do |pull|
number = pull.fetch('number')
url = pull.fetch('html_url')
next unless url.start_with?('https://github.com/ruby/ruby/pull/')

# "Merged" notes for "Squash and merge"
message = Git.commit_message(sha)
notes = Git.notes_message(sha)
if !message.include?(url) && !message.match(/[ (]##{number}[) ]/) && !notes.include?(url)
system('git', 'notes', 'append', '-m', "Merged: #{url}", sha)
updated = true
end

# "Merged-By" notes for "Rebase and merge"
if Git.committer_name(sha) == 'GitHub' && Git.committer_email(sha) == 'noreply@github.com'
username = github.pull_request(owner: 'ruby', repo: 'ruby', number: number).fetch('merged_by').fetch('login')
email = github.user(username: username).fetch('email')
email ||= SVN_TO_EMAILS[GITHUB_TO_SVN.fetch(username, username)]&.first
system('git', 'notes', 'append', '-m', "Merged-By: #{username}#{(" <#{email}>" if email)}", sha)
updated = true
end
end
end

if updated
system('git', 'push', 'origin', 'refs/notes/commits')
end
end
end
87 changes: 87 additions & 0 deletions tool/notify-slack-commits.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env ruby

require "net/https"
require "open3"
require "json"
require "digest/md5"

SLACK_WEBHOOK_URLS = [
ENV.fetch("SLACK_WEBHOOK_URL_ALERTS").chomp, # ruby-lang#alerts
ENV.fetch("SLACK_WEBHOOK_URL_COMMITS").chomp, # ruby-lang#commits
ENV.fetch("SLACK_WEBHOOK_URL_RUBY_JP").chomp, # ruby-jp#ruby-commits
]
GRAVATAR_OVERRIDES = {
"nagachika@b2dd03c8-39d4-4d8f-98ff-823fe69b080e" => "https://avatars0.githubusercontent.com/u/21976",
"noreply@github.com" => "https://avatars1.githubusercontent.com/u/9919",
"nurse@users.noreply.github.com" => "https://avatars1.githubusercontent.com/u/13423",
"svn-admin@ruby-lang.org" => "https://avatars1.githubusercontent.com/u/29403229",
"svn@b2dd03c8-39d4-4d8f-98ff-823fe69b080e" => "https://avatars1.githubusercontent.com/u/29403229",
"usa@b2dd03c8-39d4-4d8f-98ff-823fe69b080e" => "https://avatars2.githubusercontent.com/u/17790",
"usa@ruby-lang.org" => "https://avatars2.githubusercontent.com/u/17790",
"yui-knk@ruby-lang.org" => "https://avatars0.githubusercontent.com/u/5356517",
"znz@users.noreply.github.com" => "https://avatars3.githubusercontent.com/u/11857",
}

def escape(s)
s.gsub(/[&<>]/, "&" => "&amp;", "<" => "&lt;", ">" => "&gt;")
end

ARGV.each_slice(3) do |oldrev, newrev, refname|
out, = Open3.capture2("git", "rev-parse", "--symbolic", "--abbrev-ref", refname)
branch = out.strip

out, = Open3.capture2("git", "log", "--pretty=format:%H\n%h\n%cn\n%ce\n%ct\n%B", "--abbrev=10", "-z", "#{oldrev}..#{newrev}")

attachments = []
out.split("\0").reverse_each do |s|
sha, sha_abbr, committer, committeremail, committertime, body = s.split("\n", 6)
subject, body = body.split("\n", 2)

# Append notes content to `body` if it's notes
if refname.match(%r[\Arefs/notes/\w+\z])
# `--diff-filter=AM -M` to exclude rename by git's directory optimization
object = IO.popen(["git", "diff", "--diff-filter=AM", "-M", "--name-only", "#{sha}^..#{sha}"], &:read).chomp
if md = object.match(/\A(?<prefix>\h{2})\/?(?<rest>\h{38})\z/)
body = [body, IO.popen(["git", "notes", "show", md[:prefix] + md[:rest]], &:read)].join
end
end

gravatar = GRAVATAR_OVERRIDES.fetch(committeremail) do
"https://www.gravatar.com/avatar/#{ Digest::MD5.hexdigest(committeremail.downcase) }"
end

attachments << {
title: "#{ sha_abbr } (#{ branch }): #{ escape(subject) }",
title_link: "https://github.com/ruby/ruby/commit/#{ sha }",
text: escape((body || "").strip),
footer: committer,
footer_icon: gravatar,
ts: committertime.to_i,
color: '#24282D',
}
end

# 100 attachments cannot be exceeded. 20 is recommended. https://api.slack.com/docs/message-attachments
attachments.each_slice(20).each do |attachments_group|
payload = { attachments: attachments_group }

#Net::HTTP.post(
# URI.parse(SLACK_WEBHOOK_URL),
# JSON.generate(payload),
# "Content-Type" => "application/json"
#)
responses = SLACK_WEBHOOK_URLS.map do |url|
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.start do
req = Net::HTTP::Post.new(uri.path)
req.set_form_data(payload: payload.to_json)
http.request(req)
end
end

results = responses.map { |resp| "#{resp.code} (#{resp.body})" }.join(', ')
puts "#{results} -- #{payload.to_json}"
end
end