#!/usr/bin/env ruby # encoding: utf-8 require File.dirname(__FILE__) + '/../lib/stagecoach.rb' CONFIG_FILE = `pwd`.chomp + '/.stagecoach' module Stagecoach staging = 'staging' master = 'master' # Command line options courtesy of the Trollop gem. opts = CommandLine.trollop # Initial setup with -s flag. if opts[:setup] Config.setup end # Initial setup without -s flag. unless File.exist?(CONFIG_FILE) Config.setup end # Load config file to a hash. config = Config.yaml_to_hash # Checks validity of argument variables. # Ignore these checks with -t flag. unless opts[:testing_given] Config.setup if config["redmine_site"] == "none" Config.setup if config["redmine_api"] == "none" # Checks that command-line args are present and correct. Trollop::die :redmine, "issue number can only contain digits" if opts[:redmine] && opts[:redmine][/\D/] Trollop::die :github, "issue number can only contain digits" if opts[:github] && opts[:github][/\D/] Trollop::die :branch, "name must be longer than 1 character" if opts[:branch] && opts[:branch].length <= 1 # You can't give a redmine and a github issue number (for the moment) if opts[:github] && opts[:redmine] puts "You can't enter a github issue at the same time as a redmine issue at the moment. Make up your mind!\nExiting..." exit end end # Set up redmine client config. RedmineApi::Client.instance_eval do self.site = config["redmine_site"] self.user = config["redmine_api_key"] end # Checks for uncommitted/unstashed changes and aborts if present. if Git.changes != '' puts "You have uncommitted changes:".red puts Git.changes puts "Please commit or stash these changes before running Stagecoach. -h for help." puts "Exiting..." exit end # ------------------------------------------------------------------ # Initial stage - set up branch and git issue. # ------------------------------------------------------------------ unless opts[:deploy] or opts[:push] # If no issue argument has been given. if opts[:github].nil? && opts[:redmine].nil? print "Are you working on a [R]edmine or a [G]ithub issue: " case STDIN.gets.chomp when 'R' print "Please enter your Redmine issue number: " opts[:redmine] = gets.chomp when 'G' print "Please enter your Github issue number: " opts[:github] = gets.chomp end end # Check that the redmine issue is not already assigned. if opts[:redmine] redmine_issue_number = opts[:redmine] redmine_issue = Redmine.issue(redmine_issue_number) if redmine_issue.status.id != '1' puts "Warning!".red puts "This issue is in status '#{redmine_issue.status.name}'" begin puts "It is assigned to #{redmine_issue.assigned_to.name}" rescue puts "But it is not assigned to anybody yet." end print "Continue? [Y]es or [Q]uit: " case STDIN.gets.chomp when 'Y' when 'Q' exit end end # Set the redmine issue status to 'In Bearbeitung' redmine_issue.status_id = 2 redmine_issue.save end # TODO: Check that the github issue is not already assigned. CommandLine.line_break puts "Stagecoach: initial stage" # Change to master, pull changes, and create a new branch. CommandLine.line_break puts "Switching to master branch" # # TODO if there is a file that has been git added but not git committed, it # will pop up at this point looking confusing (eg. "A test_file"). # Handle this better? # Git.checkout(master) puts "Pulling changes:" Git.pull if opts[:branch_given] new_branch = opts[:branch] else print "Please enter a new git branch name for your changes (branch will be created): " new_branch = STDIN.gets.chomp end # Check that the new branch isn't master, because that would be silly case new_branch when 'master', 'Master' puts "You can't use stagecoach to deploy your master branch.\nExiting..." exit end # Make sure new local branch does not already exist. if Git.branch_exist?(new_branch) puts "There is already a local branch called #{new_branch}." if Git.diff(master, new_branch) == "" print "#{new_branch} is up to date with master. [U]se or [Q]uit: " else puts "#{new_branch} is not up to date with master. Please use a different branch or update this one.".red puts "Exiting..." CommandLine.line_break puts "The following files in branch '#{new_branch}' differ from their master branch versions:" puts Git.diff(master, new_branch) CommandLine.line_break exit end case STDIN.gets.chomp when 'U' Git.change_to_branch(new_branch) when 'Q' exit end else Git.new_branch(new_branch) end # Ugly code, pretty output... CommandLine.line_break # Issue handling. if opts[:github] config[Git.current_branch] = {:github_issue => opts[:github]} #TODO check that github issue is not assigned to somebody already elsif opts[:redmine] config[Git.current_branch] = {:redmine_issue => redmine_issue_number} end issue = config[Git.current_branch] # Set up the related issue for this branch. if redmine_issue = issue[:redmine_issue] begin puts "Searching for issue number #{redmine_issue}..." redmine_issue = Redmine.issue(redmine_issue) rescue ActiveResource::ResourceNotFound => e puts e.message exit end puts "Issue found: #{redmine_issue.subject}\n" # Create a Github issue referencing the redmine issue. puts "Creating Git issue with subject: " + redmine_issue.subject body = "Redmine issue: #{Redmine.issue_url(redmine_issue)} \n\n #{redmine_issue.description}" # Create a Git issue. github_issue = Git.new_issue(redmine_issue.subject, body) github_issue_id = github_issue[/\d+/] # Save it so we can reference it in commits using the magic of git hooks! config[Git.current_branch] = {:github_issue => github_issue_id, :redmine_issue => redmine_issue} print "Would you like to edit the issue on Github? [Y]es or anything else to continue: " if STDIN.gets.chomp == 'Y' Git.view_issue(github_issue_id) else end end # Github issues are easier. if issue[:github] #TODO what happens if no github issue is found? puts "Searching for github issue number #{issue[:number]}..." github_issue = Git.issue(issue[:number]) puts "Issue found: #{github_issue} \n" end # Saves the branch-specific details for later. Config.save(config) puts "Happy coding! Run stagecoach -d when you're ready to deploy." end # ------------------------------------------------------------------ # Push stage # ------------------------------------------------------------------ if opts[:push] # Get the current git branch branch = Git.current_branch # There's no point in pushing without any commits unless Git.branch_has_commits?(branch) puts "You don't have any uncommitted changes on branch #{branch}." puts "If you have already pushed your changes, run:" puts "stagecoach -d [deploy_branch] - default is 'staging'" puts "Otherwise, go make some commits!" puts "#%s for help" % "stagecoach -h".green puts "Exiting..." exit end # You never know! Display git status in case there are any nasty surprises. unless Git.status =~ /nothing to commit/ CommandLine.line_break puts "You have a dirty git branch:\n".red puts Git.status CommandLine.line_break print "[P]ush anyway".red print " or " print "[anything else] to cancel: ".green case STDIN.gets.chomp when "D" CommandLine.line_break puts "DEPLOYING:" CommandLine.line_break else puts "Exiting..." exit end end # TODO this needs removing if we implement the 'deploy to staging' stuff # Stop anybody deploying master to staging... case branch when 'master', 'Master', 'staging', 'Staging' puts "You can't use stagecoach to deploy your #{branch} branch.\nExiting..." exit end # Push the branch and give option to continue or quit Git.push(branch) puts "Push successful." unless opts[:deploy_given] "stagecoach -d [branch_name] to deploy (default is staging)." "exiting..." exit end end # ------------------------------------------------------------------ # Deploy stage. # ------------------------------------------------------------------ if opts[:deploy_given] # Get the current git branch from_branch = Git.current_branch to_branch = opts[:deploy] Git.merge(to_branch, from_branch) Git.push(to_branch) Capistrano.deploy(to_branch) Git.change_to_branch(master) # Redmine issue to feedback status if redmine_issue_number = config[branch][:redmine_issue] CommandLine.line_break puts "Attempting to change Redmine ticket status to 'Feedback' for you:" issue = Redmine.issue(redmine_issue_number) issue.status_id = 4 issue.save Redmine.view_issue(issue) end end end