/tools/bitbucket_pullrequests
Ruby | 300 lines | 242 code | 28 blank | 30 comment | 23 complexity | e91ea7db76ac5ac24061cbb14593327d MD5 | raw file
Possible License(s): Apache-2.0, GPL-2.0, LGPL-3.0, BSD-3-Clause, LGPL-2.1
- #!/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})"
- puts ""
- }
- 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