Source

bitbucket-client-tools / bin / bb

Full commit
#!/usr/bin/env ruby
#/ Usage: <progname> [options]...
#/ Get info about bitbucket repositories
# 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 ruby1.9.1-dev rubygems
#sudo gem install rest-client json
require 'rubygems'
require 'rest_client'
require 'json'
require 'optparse'

$stderr.sync = true

class BitbucketTools
  # Pull request summary
  class Summary
    attr_reader :id
    attr_reader :source
    attr_reader :destination
    attr_reader :branch

    def initialize(jsonHash)
      @id          = jsonHash["id"]
      @source      = jsonHash["source"]["commit"]["hash"]
      @destination = jsonHash["destination"]["commit"]["hash"]
      @branch      = jsonHash["source"]["branch"]["name"]
    end

    def to_s
      @id.to_s.rjust(5, ' ') + "    " +
      @source + "    " +
      @destination + "    " +
      @branch + "\n"
    end
  end

  # constructor
  def initialize options
    @url_api_prefix = 'https://bitbucket.org/api/2.0/repositories'
    @options = options

    # Try to identify hg repository
    hg_paths = `hg paths | grep bitbucket | head -1`
    if hg_paths.include? 'bitbucket.org'
      @repository = hg_paths.sub( %r{.*bitbucket.org/}, '').chomp
    else
      puts "Could not identify a bitbucket repository"
      exit
    end

    @url_prefix = @url_api_prefix + '/' + @repository
    @url_pullrequests = @url_prefix + "/pullrequests"
  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 = ""
    jsonHash["values"].each { |pr| output += Summary.new(pr).to_s }
    while jsonHash.has_key? "next"
      jsonHash = getJson(jsonHash["next"])
      jsonHash["values"].each { |pr| output += Summary.new(pr).to_s }
    end
    return output
  end

  # summary of one pull request
  def getPullRequestSummary(id)
    jsonHash = getJson(@url_pullrequests + "/" + id.to_s)
    return Summary.new(jsonHash)
  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)
    files = []
    diff = getPullRequestDiff(id)
    diff.lines.map(&:chomp).each do |line|
      if line.start_with? '+++ b/'
        line["+++ b/"] = ""
        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 changed files in pull request by id
  def checkPullRequest(id, fork=true)
    summary = getPullRequestSummary(id)
    puts "checking pull request #{id}, branch #{summary.branch}"
    hg_root = `hg root`.chomp
    `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
    #files = getPullRequestFiles(id)
    #files_list = ""
    #files.each { |f| files_list += " " + f }
    #if fork
    #  # this will allow real-time console output
    #  exec "echo #{files_list} | sh #{hg_root}/tools/code_check.sh --quick #{summary.source}"
    #else
    #  puts `echo #{files_list} | sh "#{hg_root}"/tools/code_check.sh --quick #{summary.source}`
    #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["show-url"] = 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,
       "")  { |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("-s", "--summary [id]", Integer,
       "Summarize a pull request with fields:\n" + " "*37 +
       "[id] [source] [dest] [branch]")  { |o| options["summary"] = 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 = BitbucketTools.new(options)
if options["list"]
  puts client.listPullRequests()
elsif !options["summary"].nil?
  puts client.getPullRequestSummary(options["summary"])
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)
    }
  else
    client.checkPullRequest(options["check_id"])
  end
else
  puts opt_parser
end