require "asana" require "tempfile" require_relative "./shared/git_config" require_relative "./shared/git_staged" ASANA_URL_PATTERN = %r{https?://app.asana.com/\d+/(?\d+)/(?\d+)}.freeze module Aid module Scripts class Begin < Aid::Script include Aid::GitConfig include Aid::GitStaged def self.description "Starts a new Asana story" end def self.help <<~HELP Fill me in. aid begin: Starts a story on Asana, moves it into the Work In Progress column, and opens a pull request on GitHub for you. Usage: aid begin "https://app.asana.com/0/1135298954694367/1135903276459289/f" HELP end def run check_for_hub! check_for_hub_credentials! prompt_for_config!( "asana.personal-access-token", "Enter your personal access token", <<~EOF 1. Visit https://app.asana.com/0/developer-console 2. Click "+ New access token" under "Personal access tokens" 3. Give the token a name 4. Copy the token and paste it back here. EOF ) prompt_for_config!( "github.user", "Enter your GitHub username", <<~EOF 1. Visit https://github.com and sign in 2. Find the GitHub username you use and paste it in here EOF ) set_asana_project_id_if_needed! check_for_existing_branch! ensure_base_branch_is_ok! step "Starting '#{asana_task.name}' on Asana" do start_task end step "Creating local branch off '#{base_branch}'" do check_for_staged_files! reset_hard_to_base_branch! create_git_branch! make_empty_commit! end step "Pushing to Github" do push_branch_to_github! end step "Opening pull request on Github" do open_pull_request_on_github! end end private def open_pull_request_on_github! tempfile = Tempfile.new("begin_pull_request") begin tempfile.write(pull_request_description) tempfile.close labels = "WIP" url = `hub pr list -h #{current_branch_name} -f '%U'`.strip if url.empty? url = `hub pull-request -F #{tempfile.path} -l "#{labels}" -d -a #{github_user}`.strip end puts colorize(:blue, url) # Copy it to your clipboard pbcopy_exist = system("command -v pbcopy >/dev/null 2>&1") system("echo #{url.inspect} | pbcopy") if pbcopy_exist # Update metadata in .git/config result = url.match(%r{pull/(\d+)}) pull_request_id = result && result[1] git_config("tasks.#{asana_task_id}.url", asana_url) git_config("tasks.#{asana_task_id}.name", asana_task.name) git_config("tasks.#{asana_task_id}.pull-request-id", pull_request_id) ensure tempfile.unlink end end def github_user git_config("github.user") end def push_branch_to_github! silent "git push --force-with-lease -u origin '#{current_branch_name}'" end def make_empty_commit! msg = "Initial commit for task ##{asana_task_id} [ci skip]" silent("git commit --allow-empty -m #{msg.inspect}") end def current_branch_name `git rev-parse --abbrev-ref HEAD`.strip end def ensure_base_branch_is_ok! base_branch end def base_branch @base_branch ||= prompt_for_base_branch end def prompt_for_base_branch current_branch = current_branch_name if current_branch != "master" print colorize( :yellow, "Current branch is #{colorize(:blue, current_branch)} - do you want "\ "to create a branch off that? (y/n) > " ) answer = STDIN.gets.strip exit if answer[0] != "y" end current_branch end def story_branch_name name = asana_task .name .gsub(%r{[/.]}, "-") .gsub(/\s+/, "-") .gsub(/[^\w-]/, "") .downcase .slice(0, 50) "#{name}-#{asana_task_id}" end def start_task # Assign task to runner of this script user = asana.users.me asana_task.update(assignee: user.gid) # Move it to the WIP column asana_task.add_project( project: asana_project.gid, section: asana_wip_section.gid ) end def asana_project @asana_project ||= asana.projects.find_by_id(asana_project_id) end def asana_task @asana_task ||= asana.tasks.find_by_id(asana_task_id) end def asana_sections asana.sections.find_by_project(project: asana_project.gid) end def asana_wip_section asana_sections.detect do |task| task.name =~ /(Work In Progress|WIP)/i end end def asana_project_id git_config("asana.project-id")&.to_i end def set_asana_project_id_if_needed! return if asana_project_id result = asana_url .match(ASANA_URL_PATTERN) abort help unless result id = result[:project_id].to_i puts "Setting asana.project-id to #{id}" git_config("asana.project-id", id) end def asana_task_id @asana_task_id ||= begin result = asana_url .match(ASANA_URL_PATTERN) abort help unless result result[:task_id].to_i end end def asana_url argv.first.to_s end def asana @asana ||= Asana::Client.new do |client| client.default_headers "asana-enable" => "string_ids,new_sections" client.authentication :access_token, git_config("asana.personal-access-token") end end def pull_request_description <<~EOF #{asana_task.name} #{asana_url} EOF end def reset_hard_to_base_branch! silent "git checkout #{base_branch}", "git fetch origin", "git reset --hard origin/#{base_branch}" end def check_for_existing_branch! return unless system "git ls-remote --exit-code --heads origin #{story_branch_name}" print colorize( :yellow, "The branch for this task (#{story_branch_name}) already exists on origin, \n"\ "if you continue, it will be reset to master. \n"\ "Do you want to continue? ? (y/n) > " ) answer = STDIN.gets.strip.downcase exit if answer[0] != "y" end def create_git_branch! system! "git checkout -b '#{story_branch_name}'" end def check_for_hub_credentials! config_file = "#{ENV['HOME']}/.config/hub" credentials_exist = File.exist?(config_file) && File.read(config_file).match(/oauth_token/i) unless credentials_exist abort <<~EOF Your GitHub credentials are not set. Run this command: $ hub pull-request and when prompted for login details, enter them. It will give you an error at the end, but you can safely ignore it. EOF end end def command?(name) system("which #{name} 2>&1 >/dev/null") end def check_for_hub! unless command?("hub") abort <<~EOF You need to install `hub` before you can use this program. To fix: $ brew install hub EOF end end def silent(*cmds) cmds.each { |cmd| system("#{cmd} >/dev/null") } end end end end