Skip to content

Instantly share code, notes, and snippets.

@mmertsock
Last active November 17, 2021 22:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mmertsock/4c22d202d7961a23e77b230e8c1d7e1b to your computer and use it in GitHub Desktop.
Save mmertsock/4c22d202d7961a23e77b230e8c1d7e1b to your computer and use it in GitHub Desktop.
Get pull request info between commits
#!/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