Last active
November 17, 2021 22:16
-
-
Save mmertsock/4c22d202d7961a23e77b230e8c1d7e1b to your computer and use it in GitHub Desktop.
Get pull request info between commits
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
#!/usr/bin/ruby | |
require 'net/http' | |
require 'uri' | |
require 'json' | |
require 'base64' | |
$compare_from = ARGV[0] | |
$compare_to = ARGV[1] | |
$output_format = ARGV[2] || "markdown" | |
usage = <<-USAGE | |
Usage: | |
$ get-release-info.rb <from> <to> [<format>] | |
<from> and <to> can be any tag, commit hash, etc., within the | |
repository configured in the global vars below as the "root repository". | |
<format> can be markdown or json, default is markdown. | |
Prints a list of pull requests. | |
Finds pull requests merged in the root repo between the two | |
commits specified in the command line. | |
Also looks for any Cartfile.resolved diffs in the root level | |
repository, and finds pull requests merged in the repos referenced | |
by that Cartfile. | |
Examples: | |
List PRs for MyAppName 2020.4, using comparison MyAppName-2020.3...main | |
$ get-release-info.rb MyAppName-2020.3 main | |
List PRs in past release MyOtherApp 2020.8: | |
$ get-release-info.rb MyOtherApp-2020.7 MyOtherApp-2020.8 | |
List PRs between two specific commits: | |
$ get-release-info.rb commit_sha_1 commit_sha_2 | |
Limitations: | |
- Only finds info about pull requests merged via GitHub | |
- May not work correctly for newly added Carthage dependencies | |
USAGE | |
# Specify the top level repo to begin searching for commits in: | |
$root_owner = "replace with github username or organization name" | |
$root_repo = "replace with github repository name" | |
$base_uri = "https://api.github.com/" | |
# Authenticate using a GitHub personal access token: | |
$github_username = "replace with your username" | |
$github_token = "replace with hex code" | |
def dump(obj) | |
puts(JSON.pretty_generate(obj)) | |
end | |
def fail(message) | |
$stderr.puts message | |
$stderr.puts "\n" | |
Kernel.exit(1) | |
end | |
def require_success(response, description) | |
case response | |
when Net::HTTPSuccess then | |
return | |
else | |
fail("Request failed for #{description}\nHTTP #{response.code}: #{response.body}") | |
end | |
end | |
def api_session() | |
uri = URI($base_uri) | |
http = Net::HTTP.new(uri.host, uri.port) | |
http.use_ssl = true | |
return http | |
end | |
def api_get_request(path) | |
uri = URI("#{$base_uri}#{path}") | |
request = Net::HTTP::Get.new(uri) | |
request["Accept"] = "application/vnd.github.v3+json" | |
request["User-Agent"] = $github_username | |
request["Authorization"] = "Basic #{Base64.strict_encode64("#{$github_username}:#{$github_token}")}" | |
return request | |
end | |
def api_get_json(http, path) | |
response = http.request(api_get_request(path)) | |
require_success(response, path) | |
return JSON.load(response.body) | |
end | |
def get_commit_list(http, owner, repo, from_id, to_id) | |
path = "repos/#{owner}/#{repo}/compare/#{from_id}...#{to_id}" | |
info = api_get_json(http, path) | |
if !info["commits"] | |
fail("Commit data not found for #{path}") | |
end | |
# info[commits] in chronological order | |
messages = info["commits"].map do |commit| | |
message = commit["commit"]["message"] | |
message[/^Merge pull request #([0-9]+) from .*$/, 1] | |
end | |
prs = messages.filter { |commit| commit != nil } | |
from_version = from_id | |
to_version = to_id | |
if !info["commits"].empty? | |
from_id = info["commits"].first["sha"] | |
to_id = info["commits"].last["sha"] | |
end | |
return { | |
"owner" => owner, | |
"repo" => repo, | |
"from_version" => from_version, | |
"to_version" => to_version, | |
"from_id" => from_id, | |
"to_id" => to_id, | |
"prs" => prs.uniq, | |
"cartfile" => info["files"].find { |item| item["filename"] == "Cartfile.resolved" } | |
} | |
end | |
def get_cartfile_item(item) | |
owner = item[/^.?github "([^\/]+)/, 1] | |
repo = item[/^.?github "[^\/]+\/([^"]+)"/, 1] | |
commit = item[/.*"([^"]+)"$/, 1] | |
if !owner | |
fail("Can't get owner from #{item}") | |
end | |
if !repo | |
fail("Can't get repo from #{item}") | |
end | |
if !commit | |
fail("Can't get commit from #{item}") | |
end | |
return { "owner" => owner, "repo" => repo, "commit" => commit } | |
end | |
def get_cartfile_diffs(http, info) | |
if !info || !info["patch"] | |
return [] | |
end | |
patch = info["patch"].split("@@").last.split("\n") | |
rems = patch.filter { |line| line =~ /^-.*$/ }.map { |line| get_cartfile_item(line) } | |
adds = patch.filter { |line| line =~ /^\+.*$/ }.map { |line| get_cartfile_item(line) } | |
info = {} | |
rems.each do |item| | |
id = item["repo"] | |
item["from_id"] = item["commit"] | |
info[id] = item | |
end | |
adds.each do |item| | |
id = item["repo"] | |
from_item = info[id] | |
if !from_item | |
item["is_new_dependency"] = true | |
item["from_id"] = item["commit"] | |
item["to_id"] = item["commit"] | |
info[id] = item | |
else | |
from_item["to_id"] = item["commit"] | |
end | |
end | |
info = info.map { |k, v| v } | |
info = info.filter { |item| item["is_new_dependency"] || (item["from_id"] != item["to_id"]) } | |
# array of :owner, :repo, :from_id, :to_id | |
return info | |
end | |
def get_pr_descriptions(http, info) | |
if $output_format != "json" | |
puts("Getting PR descriptions for #{info["repo"]}...") | |
end | |
prs = info["prs"].map do |pr| | |
path = "repos/#{info["owner"]}/#{info["repo"]}/pulls/#{pr}" | |
item = api_get_json(http, path) | |
{ "repo" => info["repo"], "number" => pr, "title" => item["title"], "url" => item["html_url"] } | |
end | |
return prs | |
end | |
def get_data() | |
info = { "root" => {}, "other_repos" => {} } | |
api_session().start do |http| | |
info["root"] = get_commit_list(http, $root_owner, $root_repo, $compare_from, $compare_to) | |
info["root"]["prs"] = get_pr_descriptions(http, info["root"]) | |
info["cartfile"] = get_cartfile_diffs(http, info["root"]["cartfile"]) | |
info["cartfile"].each do |item| | |
repo = get_commit_list(http, item["owner"], item["repo"], item["from_id"], item["to_id"]) | |
if item["is_new_dependency"] | |
repo["is_new_dependency"] = item["is_new_dependency"] | |
end | |
repo["prs"] = get_pr_descriptions(http, repo) | |
if repo["is_new_dependency"] || !repo["prs"].empty? | |
info["other_repos"][item["repo"]] = repo | |
end | |
end | |
end | |
return info | |
end | |
def markdown_output(info) | |
puts "\n\# Comparing #{$compare_from}…#{$compare_to}:" | |
puts "" | |
info["root"]["prs"].each do |pr| | |
puts "- #{pr["title"].strip} \##{pr["number"]}" | |
end | |
info["other_repos"].each do |k, repo| | |
if repo["is_new_dependency"] | |
puts "- **Added dependency:** #{repo["repo"]} #{repo["to_version"]}" | |
else | |
puts "- #{repo["repo"]} #{repo["from_version"]}…#{repo["to_version"]}:" | |
repo["prs"].each do |pr| | |
puts " - #{pr["title"].strip} #{pr["url"]}" | |
end | |
end | |
end | |
end | |
def json_output(info) | |
puts(JSON.pretty_generate(info)) | |
end | |
def run_script() | |
info = get_data() | |
case $output_format | |
when "json" then | |
json_output(info) | |
else | |
markdown_output(info) | |
end | |
end | |
if !$compare_from || !$compare_to | |
fail(usage) | |
end | |
if [$root_owner, $root_repo, $github_username, $github_token].any? { |item| item.start_with?("replace with ") } | |
fail("GitHub repo/credential variables not set.") | |
end | |
run_script |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment