300 lines
9.1 KiB
Plaintext
300 lines
9.1 KiB
Plaintext
|
#!/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
|