Last active
August 2, 2017 11:51
-
-
Save evant/8937831 to your computer and use it in GitHub Desktop.
A ruby script to automatically create crucible reviews based on your current branch and youtrack ticket.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# A script to create a review on crucible based on the current git branch and | |
# youtrack ticket. | |
# | |
# To configure settings, create a file named .code-review in your home directory | |
# The format should be: | |
# ------------------------------------------------------------------------------ | |
# crucible: | |
# url: <crucible url> | |
# username: <username> | |
# password: <password> | |
# | |
# # This section is optional, without it your ticket will not be automatically | |
# # marked "In Review" | |
# youtrack: | |
# url: <youtrack url> | |
# username: <username> | |
# password: <password> | |
# | |
# projects: | |
# <project name>: # The name of the directory your project is in | |
# key: <key> # Project key found in crucible | |
# repo: <repository> # Project repository found in crucible | |
# reviewers: # The usernames of the reviewers | |
# - <reviewer1> | |
# - <reviewer2> | |
require 'net/http' | |
require 'json' | |
require 'rexml/document' | |
require 'yaml' | |
require 'pathname' | |
config = YAML.load_file(File.expand_path("~/.code-review")) | |
project_name = nil | |
Pathname.new(File.expand_path(".")).ascend do |path| | |
if path.children.any? { |file| file.basename.to_s == ".git" } | |
project_name = path.basename.to_s | |
break | |
end | |
end | |
unless project_name | |
warn "Not a git repository (or any of the parent directories)" | |
exit 1 | |
end | |
crucible = config['crucible'] | |
youtrack = config['youtrack'] | |
project = config['projects'][project_name] | |
unless project | |
warn "no configuation found for project: #{project_name}" | |
exit 1 | |
end | |
crucible_url = crucible['url'] | |
crucible_user_name = crucible['username'] | |
crucible_password = crucible['password'] | |
youtrack_url = youtrack['url'] if youtrack | |
youtrack_user_name = youtrack['username'] if youtrack | |
youtrack_password = youtrack['password'] if youtrack | |
project_key = project['key'] | |
repository = project['repo'] | |
reviewers = project['reviewers'] | |
# get info about the current branch from git | |
display_name = `git config user.name` | |
id = `git rev-parse --short HEAD`.strip | |
branch = `git rev-parse --abbrev-ref HEAD`.strip | |
commit_abbrev = `git log -1 --pretty=%B --abbrev-commit`.strip | |
commit = `git log -1 --pretty=%B` | |
def description(branch, commit, youtrack_url) | |
res = commit | |
res += "\n#{youtrack_url}/#{branch}" if youtrack_url | |
res | |
end | |
def request(req) | |
res = Net::HTTP.start(req.uri.host, req.uri.port) do |http| | |
http.request req | |
end | |
unless res.is_a?(Net::HTTPSuccess) | |
warn(if block_given? then yield(res) else res.body end) | |
exit 1 | |
end | |
res | |
end | |
def request_json(req, crucible_user_name, crucible_password, &block) | |
req.basic_auth crucible_user_name, crucible_password | |
req["Content-Type"] = "application/json" | |
req["Accept"] = "application/json" | |
request(req) do |res| | |
if block then yield(res) else JSON.parse(res.body)["message"] end | |
end | |
end | |
def delete_review(crucible_url, review_id, crucible_user_name, crucible_password) | |
# first we must move the review to abandoned | |
uri = URI("#{crucible_url}/rest-service/reviews-v1/#{review_id}/transition?action=action:abandonReview") | |
req = Net::HTTP::Post.new(uri) | |
request_json(req, crucible_user_name, crucible_password) | |
# now we delete the review | |
uri = URI("#{crucible_url}/rest-service/reviews-v1/#{review_id}") | |
req = Net::HTTP::Delete.new(uri) | |
request_json(req, crucible_user_name, crucible_password) | |
end | |
# trap Ctrl-C so we delete the review if it has been created | |
review_id = nil | |
deleting = false | |
trap("SIGINT") { | |
break if deleting | |
if review_id | |
deleting = true | |
puts "Deleting review..." | |
delete_review(crucible_url, review_id, crucible_user_name, crucible_password) | |
exit 0 | |
else | |
exit 1 | |
end | |
} | |
# create a new review | |
uri = URI("#{crucible_url}/rest-service/reviews-v1") | |
req = Net::HTTP::Post.new(uri) | |
req.body = { | |
reviewData: { | |
author: { | |
displayName: display_name, | |
userName: crucible_user_name | |
}, | |
name: "#{branch}: #{commit_abbrev}", | |
description: description(branch, commit, youtrack_url), | |
projectKey: project_key, | |
state: "Review", | |
allowReviewersToJoin: true | |
} | |
}.to_json | |
res = request_json(req, crucible_user_name, crucible_password) | |
review_id = JSON.parse(res.body)["permaId"]["id"] | |
# now add the changeset (this request doesn't work with json for some reason) | |
def add_changeset(crucible_url, review_id, repository, id, crucible_user_name, crucible_password) | |
uri = URI("#{crucible_url}/rest-service/reviews-v1/#{review_id}/addChangeset") | |
req = Net::HTTP::Post.new(uri) | |
req.basic_auth crucible_user_name, crucible_password | |
req["Content-Type"] = "application/xml" | |
req["Accept"] = "application/xml" | |
req.body = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?> | |
<addChangeset> | |
<repository>#{repository}</repository> | |
<changesets> | |
<changesetData> | |
<id>#{id}</id> | |
</changesetData> | |
</changesets> | |
</addChangeset>" | |
res = Net::HTTP.start(req.uri.host, req.uri.port) do |http| | |
http.request req | |
end | |
unless res.is_a?(Net::HTTPSuccess) | |
error = REXML::Document.new(res.body).elements['error'] | |
if error.elements['code'].first == 'IllegalArgument' | |
warn "Failed to add changeset: #{id}, retrying..." | |
sleep 3 # sleep a bit so we aren't pinging server too much | |
add_changeset(crucible_url, review_id, repository, id, crucible_user_name, crucible_password) | |
else | |
error.elements['message'].first | |
end | |
end | |
end | |
add_changeset(crucible_url, review_id, repository, id, crucible_user_name, crucible_password) | |
puts "Created review: #{crucible_url}/cru/#{review_id}" | |
# add reviews to the review if they exist | |
unless reviewers.empty? | |
uri = URI("#{crucible_url}/rest-service/reviews-v1/#{review_id}/reviewers") | |
req = Net::HTTP::Post.new(uri) | |
req.body = reviewers.join(',') | |
request_json(req, crucible_user_name, crucible_password) | |
end | |
if youtrack | |
#login to youtrack | |
uri = URI("#{youtrack_url}/rest/user/login") | |
req = Net::HTTP::Post.new(uri) | |
req.body = "login=#{youtrack_user_name}&password=#{youtrack_password}" | |
res = request(req) | |
cookie = res['Set-Cookie'] | |
# update youtrack ticket status | |
uri = URI("#{youtrack_url}/rest/issue/#{branch}/execute") | |
req = Net::HTTP::Post.new(uri) | |
req['Cookie'] = cookie | |
req.body = "command=State%20In%20Review" | |
request(req) do |res| | |
REXML::Document.new(res.body).elements['error'].first | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment