ppovb5fc7/gazebo/tools/bitbucket_pullrequests

300 lines
9.1 KiB
Ruby
Executable File

#!/usr/bin/env ruby
#/ Usage: <progname> [options]...
#/ Get info on pull requests from gazebo's bitbucket repository
# based on http://www.alphadevx.com/a/88-Writing-a-REST-Client-in-Ruby
# to install dependencies on Ubuntu (tested with Precise, Quantal, and Raring):
#sudo apt-get install rubygems ruby-rest-client ruby-json
begin
require 'rubygems'
require 'rest_client'
require 'json'
require 'optparse'
require 'date'
rescue LoadError => e
puts "Error: " + e.message
gem = e.message.match(/ -- (.*)/)[1]
puts "Please install missing gem: $ gem install #{gem}"
exit
end
$stderr.sync = true
class BitbucketPullRequests
# Pull request summary
class Summary
attr_reader :id
attr_reader :source
attr_reader :destination
attr_reader :branch
attr_reader :createdOn
attr_reader :title
def initialize(jsonHash, options)
@options = options
@id = jsonHash["id"]
@source = " "*12
@destination = " "*12
@branch = ""
@title = ""
source = jsonHash["source"]["commit"]
destination = jsonHash["destination"]["commit"]
branch = jsonHash["source"]["branch"]
title = jsonHash["title"]
@source = source["hash"] if !source.nil?
@destination = destination["hash"] if !destination.nil?
@branch = branch["name"] if !branch.nil?
@title = title if !title.nil?
@createdOn = jsonHash["created_on"]
end
def to_s
title = ""
title += "\n" + @title + "\n" if @options["title"]
title +
@id.to_s.rjust(5, ' ') + " " +
DateTime.parse(@createdOn).strftime("%Y-%m-%d") + " " +
@source + " " +
@destination + " " +
@branch + "\n"
end
def date_and_string
[@createdOn, self.to_s]
end
end
# constructor
def initialize(options)
@url_pullrequests = 'https://bitbucket.org/api/2.0/repositories/osrf/gazebo/pullrequests'
@options = options
end
# helpers for RestClient.get calls
def getUrl(url)
puts url if @options["show-url"]
RestClient.get(url)
end
def getJson(url)
json = JSON.parse(getUrl(url).body)
if @options["verbose"]
puts JSON.pretty_generate(json)
end
json
end
# summary of open pull requests
def listPullRequests()
jsonHash = getJson(@url_pullrequests + "/?state=OPEN")
output = ""
# Hash of pull requests.
pullrequests = {}
jsonHash["values"].each { |pr|
date, str = Summary.new(pr, @options).date_and_string
pullrequests[date] = str
}
while jsonHash.has_key? "next"
jsonHash = getJson(jsonHash["next"])
jsonHash["values"].each { |pr|
date, str = Summary.new(pr, @options).date_and_string
pullrequests[date] = str
}
end
# Generate output sorted by creation time
pullrequests.keys.sort.each { |k| output += pullrequests[k] }
return output
end
# summary of one pull request
def getPullRequestSummary(id)
jsonHash = getJson(@url_pullrequests + "/" + id.to_s)
return Summary.new(jsonHash, @options)
end
###############################################
# Output a pull request summary based based on a branch and revision.
# @param[in] _range A two part array, where the first is a branch name
# and the second a revision.
def getPullRequestSummaryFromRange(range)
if range.nil?
puts "Invalid range for --summary-range option."
return
elsif range.size < 2
puts "Error: --summary-range option requires two comma " +
"separated arguments."
return
end
origin = range[0]
dest = range[1]
# get the list of summaries from the log. Need to be cleaned up
summaries_hg=`hg log -b #{origin} -P #{dest} | grep "(pull request.*)"`
summaries_hg.split("\n").each { |sum|
id = sum.scan(/#[0-9]*\)/).to_s()[/([0-9])+/]
jsonHash = getJson(@url_pullrequests + "/" + id)
puts "1. " + jsonHash["title"]
puts " * [Pull request #{id}](https://bitbucket.org/osrf/gazebo/pull-request/#{id})"
}
end
# diff of pull request
def getPullRequestDiff(id)
response = getUrl(@url_pullrequests + "/" + id.to_s + "/diff")
puts response if @options["verbose"]
return response
end
# list of files changed by pull request
def getPullRequestFiles(id)
getFilesFromDiff(getPullRequestDiff(id))
end
# extract list of files added from diff string
def getFilesFromDiff(diff)
files = []
diff.lines.map(&:chomp).each do |line|
if line.start_with? '+++ b/'
line["+++ b/"] = ""
# try to remove anything after a tab character
# this is needed by --check 0
begin
line[/\t.*$/] = ""
# IndexError is raised if no tab characters are found
rescue IndexError
end
files << line
end
end
return files
end
# get ids for open pull requests
def getOpenPullRequests()
jsonHash = getJson(@url_pullrequests + "/?state=OPEN")
ids = []
jsonHash["values"].each { |pr| ids << pr["id"].to_i }
while jsonHash.has_key? "next"
jsonHash = getJson(jsonHash["next"])
jsonHash["values"].each { |pr| ids << pr["id"].to_i }
end
return ids
end
# check files changed by the parent commit
def checkCurrent()
files = getFilesFromDiff(`hg log -r . --patch`)
changeset = `hg id`[0..11]
checkFiles(files, changeset)
end
# check changed files in pull request by id
def checkPullRequest(id, fork=true)
summary = getPullRequestSummary(id)
puts "checking pull request #{id}, branch #{summary.branch}"
files = getPullRequestFiles(id)
`hg log -r #{summary.destination} 2>&1`
if $? != 0
puts "Unknown revision #{summary.destination}, try: hg pull"
return
end
`hg log -r #{summary.source} 2>&1`
if $? != 0
puts "Unknown revision #{summary.source}, try: hg pull " +
"(it could also be a fork)"
return
end
ancestor=`hg log -r "ancestor(#{summary.source},#{summary.destination})" | head -1 | sed -e 's@.*:@@'`.chomp
if ancestor != summary.destination
puts "Need to merge branch #{summary.branch} with #{summary.destination}"
end
checkFiles(files, summary.source, fork)
end
def checkFiles(files, changeset, fork=true)
files_list = ""
files.each { |f| files_list += " " + f }
hg_root = `hg root`.chomp
if fork
# this will allow real-time console output
exec "echo #{files_list} | sh #{hg_root}/tools/code_check.sh --quick #{changeset}"
else
puts `echo #{files_list} | sh "#{hg_root}"/tools/code_check.sh --quick #{changeset}`
end
end
end
# default options
options = {}
options["list"] = false
options["summary"] = nil
options["check"] = false
options["check_id"] = nil
options["diff"] = nil
options["files"] = nil
options["range"] = nil
options["show-url"] = false
options["title"] = false
options["verbose"] = false
opt_parser = OptionParser.new do |o|
o.on("-l", "--list",
"List open pull requests with fields:\n" + " "*37 +
"[id] [source] [dest] [branch]") { |o| options["list"] = o }
o.on("-c", "--check [id]", Integer,
"Run code_check on files changed by pull request [id]\n" + " "*37 +
"if [id] is not supplied, check all open pull requests\n" + " "*37 +
"if [id] is 0, check files changed by parent commit") { |o| options["check_id"] = o; options["check"] = true }
o.on("-d", "--diff [id]", Integer,
"Show diff from pull request") { |o| options["diff"] = o }
o.on("-f", "--files [id]", Integer,
"Show changed files in a pull request") { |o| options["files"] = o }
o.on("--summary-range [changeset1],[changeset2]", Array,
"Display all summaries from pull request\n\t\t\t\t\tmerged between changeset1 and changeset2") { |o| options["range"] = o }
o.on("-s", "--summary [id]", Integer,
"Summarize a pull request with fields:\n" + " "*37 +
"[id] [source] [dest] [branch]") { |o| options["summary"] = o }
o.on("-t", "--title",
"Show pull request title with --list,\n\t\t\t\t\t--summary") { |o| options["title"] = o }
o.on("-u", "--show-url",
"Show urls accessed") { |o| options["show-url"] = o }
o.on("-v", "--verbose",
"Verbose output") { |o| options["verbose"] = o }
o.on("-h", "--help", "Display this help message") do
puts opt_parser
exit
end
end
opt_parser.parse!
client = BitbucketPullRequests.new(options)
if options["list"]
puts client.listPullRequests()
elsif !options["summary"].nil?
puts client.getPullRequestSummary(options["summary"])
elsif !options["range"].nil?
client.getPullRequestSummaryFromRange(options["range"])
elsif !options["diff"].nil?
puts client.getPullRequestDiff(options["diff"])
elsif !options["files"].nil?
puts client.getPullRequestFiles(options["files"])
elsif options["check"]
if options["check_id"].nil?
# check all open pull requests
client.getOpenPullRequests().each { |id|
client.checkPullRequest(id, false)
}
elsif options["check_id"] == 0
client.checkCurrent()
else
client.checkPullRequest(options["check_id"])
end
else
puts opt_parser
end