#!/usr/bin/env ruby # encoding: UTF-8 # # git-pivotal-pr -- # # Open a pull request for the current branch in your default browser # # Assumes the branches are named # /- # require 'rubygems' require 'pivotal-tracker' require 'optparse' require 'cgi' require 'term/ansicolor' require 'git-whistles/app' class App < Git::Whistles::App BROWSERS = %w(xdg-open open firefox iceweasel) SAFE_QUERY_STRING_SIZE = 8000 def initialize super end def main(args) super parse_args!(args) if args.count > 0 die "Too many arguments", :usage => true end if options.from == options.to die "You cannot issue a pull request to the same branch (#{options.from})." end query = { } # guess team name if options.from =~ %r{^(\w+)/.*} team = $1.capitalize else team = nil end # guess title. title = options.from.split('/').last.split(/[_-]/).delete_if { |word| word =~ /^\d+$/ }.join(' ').capitalize query[:"pull_request[title]"] = team ? "#{team}: #{title}" : title # add Pivotal infos add_pivotal_info(query, $1.to_i) if options.from =~ /(\d+)$/ query_string = query.map { |key,value| "#{CGI.escape key.to_s}=#{CGI.escape value}" }.join('&') url = "https://github.com/#{repo}/compare/#{options.to}...#{options.from}?#{query_string}" puts "Preparing a pull request for branch #{options.from}" unless launch_browser(url) log.warn "Sorry, I don't know how to launch a web browser on your system. You can open it yourself and paste this URL:\n#{url}" end end private def defaults { :from => run!('git symbolic-ref HEAD').strip.gsub(%r(^refs/heads/), ""), :to => 'master', :remote => 'origin' } end def option_parser @option_parser ||= OptionParser.new do |op| op.banner = "Usage: git pivotal-pr [options]" op.on("-f", "--from YOUR_BRANCH", "Branch to issue pull request for [head]") do |v| options.from = v end op.on("-to", "--to UPSTREAM_BRANCH", "Branch into which you want your code merged [master]") do |v| options.to = v end op.on("-r", "--remote NAME", "The remote you're sending this to [origin]") do |v| options.to = v end end end def origin_url @origin_url ||= begin run!("git config --get remote.#{options.remote}.url").strip.tap do |url| url =~ /github\.com/ or die "origin does not have a Github URL !" end end end def repo @repo ||= origin_url.sub(/.*github\.com[\/:]/,'').sub(/\.git$/,'') end def add_pivotal_info(query, story_id) token = `git config pivotal-tracker.token`.strip if token.empty? puts Term::ANSIColor.yellow %Q{ Your branch appears to have a story ID, but I don't know your Pivotal Tracker token! Please set it with: $ git config [--global] pivotal-tracker.token } die "Aborting." end log.info "Finding your project and story¬" PivotalTracker::Client.token = token begin story, project = PivotalTracker::Project.all.find do |project| log.info '.¬' story = project.stories.find(story_id) and break story, project end log.info '.' rescue RestClient::Unauthorized log.info '.' die "Your token is not authorized by Pivotal Tracker! Please make sure you have the correct one" end if story.nil? log.warn "Apologies... I could not find story #{story_id}." return end log.info "Found story #{story_id} in '#{project.name}'" title = "#{project.name}: #{story.name} [##{story.id}]" headline = "Pivotal tracker story [##{story_id}](#{story.url}) in project *#{project.name}*:" description = story.description.split("\n").map { |line| "> #{line}" }.join("\n") query.merge! subject: story.name, :"pull_request[title]" => title if (headline.length + description.length) > SAFE_QUERY_STRING_SIZE log.warn "Oops looks like your story body exceeds maximum allowed caracters to send a github request" log.warn "Please copy the info below to your pull request body:" puts puts headline puts puts puts description puts puts puts "Press any key to continue..." gets query.merge! :"pull_request[body]" => "Please check your command line for the story body" else body = "TODO: describe your changes\n\n===\n\n#{headline}\n\n#{description}" query.merge! :"pull_request[body]" => body end end def launch_browser(url) BROWSERS.each do |command| next if run("which #{command}").strip.empty? system(command, url) and return true end false end end ############################################################################ App.run!