-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathnotes-github-pr.rb
executable file
·147 lines (122 loc) · 4.64 KB
/
notes-github-pr.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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',
}
SVN_TO_EMAILS = YAML.safe_load(File.read(File.expand_path('../config/email.yml', __dir__)))
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-access-token-repo-status: only `repo.repo:status`
github = GitHub.new(File.read(File.expand_path('~git/config/github-access-token-repo-status')).chomp)
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) == '[email protected]'
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