PageRenderTime 83ms CodeModel.GetById 18ms RepoModel.GetById 2ms app.codeStats 0ms

/tools/bitbucket_pullrequests

https://bitbucket.org/osrf/gazebo
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
  1. #!/usr/bin/env ruby
  2. #/ Usage: <progname> [options]...
  3. #/ Get info on pull requests from gazebo's bitbucket repository
  4. # based on http://www.alphadevx.com/a/88-Writing-a-REST-Client-in-Ruby
  5. # to install dependencies on Ubuntu (tested with Precise, Quantal, and Raring):
  6. #sudo apt-get install rubygems ruby-rest-client ruby-json
  7. begin
  8. require 'rubygems'
  9. require 'rest_client'
  10. require 'json'
  11. require 'optparse'
  12. require 'date'
  13. rescue LoadError => e
  14. puts "Error: " + e.message
  15. gem = e.message.match(/ -- (.*)/)[1]
  16. puts "Please install missing gem: $ gem install #{gem}"
  17. exit
  18. end
  19. $stderr.sync = true
  20. class BitbucketPullRequests
  21. # Pull request summary
  22. class Summary
  23. attr_reader :id
  24. attr_reader :source
  25. attr_reader :destination
  26. attr_reader :branch
  27. attr_reader :createdOn
  28. attr_reader :title
  29. def initialize(jsonHash, options)
  30. @options = options
  31. @id = jsonHash["id"]
  32. @source = " "*12
  33. @destination = " "*12
  34. @branch = ""
  35. @title = ""
  36. source = jsonHash["source"]["commit"]
  37. destination = jsonHash["destination"]["commit"]
  38. branch = jsonHash["source"]["branch"]
  39. title = jsonHash["title"]
  40. @source = source["hash"] if !source.nil?
  41. @destination = destination["hash"] if !destination.nil?
  42. @branch = branch["name"] if !branch.nil?
  43. @title = title if !title.nil?
  44. @createdOn = jsonHash["created_on"]
  45. end
  46. def to_s
  47. title = ""
  48. title += "\n" + @title + "\n" if @options["title"]
  49. title +
  50. @id.to_s.rjust(5, ' ') + " " +
  51. DateTime.parse(@createdOn).strftime("%Y-%m-%d") + " " +
  52. @source + " " +
  53. @destination + " " +
  54. @branch + "\n"
  55. end
  56. def date_and_string
  57. [@createdOn, self.to_s]
  58. end
  59. end
  60. # constructor
  61. def initialize(options)
  62. @url_pullrequests = 'https://bitbucket.org/api/2.0/repositories/osrf/gazebo/pullrequests'
  63. @options = options
  64. end
  65. # helpers for RestClient.get calls
  66. def getUrl(url)
  67. puts url if @options["show-url"]
  68. RestClient.get(url)
  69. end
  70. def getJson(url)
  71. json = JSON.parse(getUrl(url).body)
  72. if @options["verbose"]
  73. puts JSON.pretty_generate(json)
  74. end
  75. json
  76. end
  77. # summary of open pull requests
  78. def listPullRequests()
  79. jsonHash = getJson(@url_pullrequests + "/?state=OPEN")
  80. output = ""
  81. # Hash of pull requests.
  82. pullrequests = {}
  83. jsonHash["values"].each { |pr|
  84. date, str = Summary.new(pr, @options).date_and_string
  85. pullrequests[date] = str
  86. }
  87. while jsonHash.has_key? "next"
  88. jsonHash = getJson(jsonHash["next"])
  89. jsonHash["values"].each { |pr|
  90. date, str = Summary.new(pr, @options).date_and_string
  91. pullrequests[date] = str
  92. }
  93. end
  94. # Generate output sorted by creation time
  95. pullrequests.keys.sort.each { |k| output += pullrequests[k] }
  96. return output
  97. end
  98. # summary of one pull request
  99. def getPullRequestSummary(id)
  100. jsonHash = getJson(@url_pullrequests + "/" + id.to_s)
  101. return Summary.new(jsonHash, @options)
  102. end
  103. ###############################################
  104. # Output a pull request summary based based on a branch and revision.
  105. # @param[in] _range A two part array, where the first is a branch name
  106. # and the second a revision.
  107. def getPullRequestSummaryFromRange(range)
  108. if range.nil?
  109. puts "Invalid range for --summary-range option."
  110. return
  111. elsif range.size < 2
  112. puts "Error: --summary-range option requires two comma " +
  113. "separated arguments."
  114. return
  115. end
  116. origin = range[0]
  117. dest = range[1]
  118. # get the list of summaries from the log. Need to be cleaned up
  119. summaries_hg=`hg log -b #{origin} -P #{dest} | grep "(pull request.*)"`
  120. summaries_hg.split("\n").each { |sum|
  121. id = sum.scan(/#[0-9]*\)/).to_s()[/([0-9])+/]
  122. jsonHash = getJson(@url_pullrequests + "/" + id)
  123. puts "1. " + jsonHash["title"]
  124. puts " * [Pull request #{id}](https://bitbucket.org/osrf/gazebo/pull-request/#{id})"
  125. puts ""
  126. }
  127. end
  128. # diff of pull request
  129. def getPullRequestDiff(id)
  130. response = getUrl(@url_pullrequests + "/" + id.to_s + "/diff")
  131. puts response if @options["verbose"]
  132. return response
  133. end
  134. # list of files changed by pull request
  135. def getPullRequestFiles(id)
  136. getFilesFromDiff(getPullRequestDiff(id))
  137. end
  138. # extract list of files added from diff string
  139. def getFilesFromDiff(diff)
  140. files = []
  141. diff.lines.map(&:chomp).each do |line|
  142. if line.start_with? '+++ b/'
  143. line["+++ b/"] = ""
  144. # try to remove anything after a tab character
  145. # this is needed by --check 0
  146. begin
  147. line[/\t.*$/] = ""
  148. # IndexError is raised if no tab characters are found
  149. rescue IndexError
  150. end
  151. files << line
  152. end
  153. end
  154. return files
  155. end
  156. # get ids for open pull requests
  157. def getOpenPullRequests()
  158. jsonHash = getJson(@url_pullrequests + "/?state=OPEN")
  159. ids = []
  160. jsonHash["values"].each { |pr| ids << pr["id"].to_i }
  161. while jsonHash.has_key? "next"
  162. jsonHash = getJson(jsonHash["next"])
  163. jsonHash["values"].each { |pr| ids << pr["id"].to_i }
  164. end
  165. return ids
  166. end
  167. # check files changed by the parent commit
  168. def checkCurrent()
  169. files = getFilesFromDiff(`hg log -r . --patch`)
  170. changeset = `hg id`[0..11]
  171. checkFiles(files, changeset)
  172. end
  173. # check changed files in pull request by id
  174. def checkPullRequest(id, fork=true)
  175. summary = getPullRequestSummary(id)
  176. puts "checking pull request #{id}, branch #{summary.branch}"
  177. files = getPullRequestFiles(id)
  178. `hg log -r #{summary.destination} 2>&1`
  179. if $? != 0
  180. puts "Unknown revision #{summary.destination}, try: hg pull"
  181. return
  182. end
  183. `hg log -r #{summary.source} 2>&1`
  184. if $? != 0
  185. puts "Unknown revision #{summary.source}, try: hg pull " +
  186. "(it could also be a fork)"
  187. return
  188. end
  189. ancestor=`hg log -r "ancestor(#{summary.source},#{summary.destination})" | head -1 | sed -e 's@.*:@@'`.chomp
  190. if ancestor != summary.destination
  191. puts "Need to merge branch #{summary.branch} with #{summary.destination}"
  192. end
  193. checkFiles(files, summary.source, fork)
  194. end
  195. def checkFiles(files, changeset, fork=true)
  196. files_list = ""
  197. files.each { |f| files_list += " " + f }
  198. hg_root = `hg root`.chomp
  199. if fork
  200. # this will allow real-time console output
  201. exec "echo #{files_list} | sh #{hg_root}/tools/code_check.sh --quick #{changeset}"
  202. else
  203. puts `echo #{files_list} | sh "#{hg_root}"/tools/code_check.sh --quick #{changeset}`
  204. end
  205. end
  206. end
  207. # default options
  208. options = {}
  209. options["list"] = false
  210. options["summary"] = nil
  211. options["check"] = false
  212. options["check_id"] = nil
  213. options["diff"] = nil
  214. options["files"] = nil
  215. options["range"] = nil
  216. options["show-url"] = false
  217. options["title"] = false
  218. options["verbose"] = false
  219. opt_parser = OptionParser.new do |o|
  220. o.on("-l", "--list",
  221. "List open pull requests with fields:\n" + " "*37 +
  222. "[id] [source] [dest] [branch]") { |o| options["list"] = o }
  223. o.on("-c", "--check [id]", Integer,
  224. "Run code_check on files changed by pull request [id]\n" + " "*37 +
  225. "if [id] is not supplied, check all open pull requests\n" + " "*37 +
  226. "if [id] is 0, check files changed by parent commit") { |o| options["check_id"] = o; options["check"] = true }
  227. o.on("-d", "--diff [id]", Integer,
  228. "Show diff from pull request") { |o| options["diff"] = o }
  229. o.on("-f", "--files [id]", Integer,
  230. "Show changed files in a pull request") { |o| options["files"] = o }
  231. o.on("--summary-range [changeset1],[changeset2]", Array,
  232. "Display all summaries from pull request\n\t\t\t\t\tmerged between changeset1 and changeset2") { |o| options["range"] = o }
  233. o.on("-s", "--summary [id]", Integer,
  234. "Summarize a pull request with fields:\n" + " "*37 +
  235. "[id] [source] [dest] [branch]") { |o| options["summary"] = o }
  236. o.on("-t", "--title",
  237. "Show pull request title with --list,\n\t\t\t\t\t--summary") { |o| options["title"] = o }
  238. o.on("-u", "--show-url",
  239. "Show urls accessed") { |o| options["show-url"] = o }
  240. o.on("-v", "--verbose",
  241. "Verbose output") { |o| options["verbose"] = o }
  242. o.on("-h", "--help", "Display this help message") do
  243. puts opt_parser
  244. exit
  245. end
  246. end
  247. opt_parser.parse!
  248. client = BitbucketPullRequests.new(options)
  249. if options["list"]
  250. puts client.listPullRequests()
  251. elsif !options["summary"].nil?
  252. puts client.getPullRequestSummary(options["summary"])
  253. elsif !options["range"].nil?
  254. client.getPullRequestSummaryFromRange(options["range"])
  255. elsif !options["diff"].nil?
  256. puts client.getPullRequestDiff(options["diff"])
  257. elsif !options["files"].nil?
  258. puts client.getPullRequestFiles(options["files"])
  259. elsif options["check"]
  260. if options["check_id"].nil?
  261. # check all open pull requests
  262. client.getOpenPullRequests().each { |id|
  263. client.checkPullRequest(id, false)
  264. }
  265. elsif options["check_id"] == 0
  266. client.checkCurrent()
  267. else
  268. client.checkPullRequest(options["check_id"])
  269. end
  270. else
  271. puts opt_parser
  272. end