flash_flow 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/.gitignore +10 -0
 - data/.ruby-version +1 -0
 - data/Gemfile +4 -0
 - data/Gemfile.lock +81 -0
 - data/LICENSE.txt +22 -0
 - data/README.md +152 -0
 - data/Rakefile +10 -0
 - data/bin/flash_flow +23 -0
 - data/flash_flow.gemspec +28 -0
 - data/flash_flow.yml.erb.example +83 -0
 - data/lib/flash_flow.rb +7 -0
 - data/lib/flash_flow/branch_merger.rb +52 -0
 - data/lib/flash_flow/cmd_runner.rb +37 -0
 - data/lib/flash_flow/config.rb +71 -0
 - data/lib/flash_flow/data.rb +6 -0
 - data/lib/flash_flow/data/base.rb +58 -0
 - data/lib/flash_flow/data/branch.rb +131 -0
 - data/lib/flash_flow/data/collection.rb +181 -0
 - data/lib/flash_flow/data/github.rb +129 -0
 - data/lib/flash_flow/data/store.rb +33 -0
 - data/lib/flash_flow/deploy.rb +184 -0
 - data/lib/flash_flow/git.rb +248 -0
 - data/lib/flash_flow/install.rb +19 -0
 - data/lib/flash_flow/issue_tracker.rb +52 -0
 - data/lib/flash_flow/issue_tracker/pivotal.rb +160 -0
 - data/lib/flash_flow/lock.rb +25 -0
 - data/lib/flash_flow/lock/github.rb +91 -0
 - data/lib/flash_flow/notifier.rb +24 -0
 - data/lib/flash_flow/notifier/hipchat.rb +36 -0
 - data/lib/flash_flow/options.rb +36 -0
 - data/lib/flash_flow/time_helper.rb +11 -0
 - data/lib/flash_flow/version.rb +3 -0
 - data/test/lib/data/test_base.rb +10 -0
 - data/test/lib/data/test_branch.rb +203 -0
 - data/test/lib/data/test_collection.rb +238 -0
 - data/test/lib/data/test_github.rb +23 -0
 - data/test/lib/data/test_store.rb +53 -0
 - data/test/lib/issue_tracker/test_pivotal.rb +221 -0
 - data/test/lib/lock/test_github.rb +70 -0
 - data/test/lib/test_branch_merger.rb +76 -0
 - data/test/lib/test_config.rb +84 -0
 - data/test/lib/test_deploy.rb +175 -0
 - data/test/lib/test_git.rb +73 -0
 - data/test/lib/test_issue_tracker.rb +43 -0
 - data/test/lib/test_notifier.rb +33 -0
 - data/test/minitest_helper.rb +38 -0
 - metadata +217 -0
 
| 
         @@ -0,0 +1,129 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'octokit'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'flash_flow/data/branch'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module FlashFlow
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Data
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Github
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  attr_accessor :repo, :unmergeable_label
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def initialize(config={})
         
     | 
| 
      
 11 
     | 
    
         
            +
                    initialize_connection!(config['token'])
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @repo = config['repo']
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @master_branch = config['master_branch'] || master
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @unmergeable_label = config['unmergeable_label'] || 'unmergeable'
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @do_not_merge_label = config['do_not_merge_label'] || 'do not merge'
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def initialize_connection!(token)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    if token.nil?
         
     | 
| 
      
 20 
     | 
    
         
            +
                      raise RuntimeError.new("Github token must be set in your flash_flow config file.")
         
     | 
| 
      
 21 
     | 
    
         
            +
                    end
         
     | 
| 
      
 22 
     | 
    
         
            +
                    octokit.configure do |c|
         
     | 
| 
      
 23 
     | 
    
         
            +
                      c.access_token = token
         
     | 
| 
      
 24 
     | 
    
         
            +
                    end
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def remove_from_merge(branch)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    pr = pr_for(branch)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    if pr && @do_not_merge_label
         
     | 
| 
      
 30 
     | 
    
         
            +
                      add_label(pr.number, @do_not_merge_label)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  def fetch
         
     | 
| 
      
 35 
     | 
    
         
            +
                    pull_requests.map do |pr|
         
     | 
| 
      
 36 
     | 
    
         
            +
                      Branch.from_hash(
         
     | 
| 
      
 37 
     | 
    
         
            +
                          'remote_url' => pr.head.repo.ssh_url,
         
     | 
| 
      
 38 
     | 
    
         
            +
                          'ref' => pr.head.ref,
         
     | 
| 
      
 39 
     | 
    
         
            +
                          'status' => status_from_labels(pr),
         
     | 
| 
      
 40 
     | 
    
         
            +
                          'metadata' => metadata(pr)
         
     | 
| 
      
 41 
     | 
    
         
            +
                      )
         
     | 
| 
      
 42 
     | 
    
         
            +
                    end
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  def add_to_merge(branch)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    pr = pr_for(branch)
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    pr ||= create_pr(branch.ref, branch.ref, branch.ref)
         
     | 
| 
      
 49 
     | 
    
         
            +
                    branch.add_metadata(metadata(pr))
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                    if pr && @do_not_merge_label
         
     | 
| 
      
 52 
     | 
    
         
            +
                      remove_label(pr.number, @do_not_merge_label)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  def mark_success(branch)
         
     | 
| 
      
 57 
     | 
    
         
            +
                    remove_label(branch.metadata['pr_number'], @unmergeable_label)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  def mark_failure(branch)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    add_label(branch.metadata['pr_number'], @unmergeable_label)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  private
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                  def status_from_labels(pull_request)
         
     | 
| 
      
 67 
     | 
    
         
            +
                    case
         
     | 
| 
      
 68 
     | 
    
         
            +
                      when has_label?(pull_request.number, @do_not_merge_label)
         
     | 
| 
      
 69 
     | 
    
         
            +
                        'removed'
         
     | 
| 
      
 70 
     | 
    
         
            +
                      when has_label?(pull_request.number, @unmergeable_label)
         
     | 
| 
      
 71 
     | 
    
         
            +
                        'fail'
         
     | 
| 
      
 72 
     | 
    
         
            +
                      else
         
     | 
| 
      
 73 
     | 
    
         
            +
                        nil
         
     | 
| 
      
 74 
     | 
    
         
            +
                    end
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                  def pr_for(branch)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    pull_requests.detect { |p| branch.remote_url == p.head.repo.ssh_url && branch.ref == p.head.ref }
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                  def update_pr(pr_number)
         
     | 
| 
      
 82 
     | 
    
         
            +
                    octokit.update_pull_request(repo, pr_number, {})
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  def create_pr(branch, title, body)
         
     | 
| 
      
 86 
     | 
    
         
            +
                    pr = octokit.create_pull_request(repo, @master_branch, branch, title, body)
         
     | 
| 
      
 87 
     | 
    
         
            +
                    pull_requests << pr
         
     | 
| 
      
 88 
     | 
    
         
            +
                    pr
         
     | 
| 
      
 89 
     | 
    
         
            +
                  end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                  def pull_requests
         
     | 
| 
      
 92 
     | 
    
         
            +
                    @pull_requests ||= octokit.pull_requests(repo).sort_by(&:updated_at)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  def remove_label(pull_request_number, label)
         
     | 
| 
      
 96 
     | 
    
         
            +
                    if has_label?(pull_request_number, label)
         
     | 
| 
      
 97 
     | 
    
         
            +
                      octokit.remove_label(repo, pull_request_number, label)
         
     | 
| 
      
 98 
     | 
    
         
            +
                    end
         
     | 
| 
      
 99 
     | 
    
         
            +
                  end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                  def add_label(pull_request_number, label)
         
     | 
| 
      
 102 
     | 
    
         
            +
                    unless has_label?(pull_request_number, label)
         
     | 
| 
      
 103 
     | 
    
         
            +
                      octokit.add_labels_to_an_issue(repo, pull_request_number, [label])
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
      
 105 
     | 
    
         
            +
                  end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                  def has_label?(pull_request_number, label_name)
         
     | 
| 
      
 108 
     | 
    
         
            +
                    labels(pull_request_number).detect { |label| label.name == label_name }
         
     | 
| 
      
 109 
     | 
    
         
            +
                  end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                  def labels(pull_request_number)
         
     | 
| 
      
 112 
     | 
    
         
            +
                    @labels ||= {}
         
     | 
| 
      
 113 
     | 
    
         
            +
                    @labels[pull_request_number] ||= octokit.labels_for_issue(repo, pull_request_number)
         
     | 
| 
      
 114 
     | 
    
         
            +
                  end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                  def metadata(pr)
         
     | 
| 
      
 117 
     | 
    
         
            +
                    {
         
     | 
| 
      
 118 
     | 
    
         
            +
                        'pr_number' => pr.number,
         
     | 
| 
      
 119 
     | 
    
         
            +
                        'user_url' => pr.user.html_url,
         
     | 
| 
      
 120 
     | 
    
         
            +
                        'repo_url' => pr.head.repo.html_url
         
     | 
| 
      
 121 
     | 
    
         
            +
                    }
         
     | 
| 
      
 122 
     | 
    
         
            +
                  end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                  def octokit
         
     | 
| 
      
 125 
     | 
    
         
            +
                    Octokit
         
     | 
| 
      
 126 
     | 
    
         
            +
                  end
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
              end
         
     | 
| 
      
 129 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,33 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'flash_flow/data'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module FlashFlow
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Data
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Store
         
     | 
| 
      
 7 
     | 
    
         
            +
                  def initialize(filename, git, opts={})
         
     | 
| 
      
 8 
     | 
    
         
            +
                    @filename = filename
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @git = git
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @logger = opts[:logger] || Logger.new('/dev/null')
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  def get
         
     | 
| 
      
 14 
     | 
    
         
            +
                    file_contents = @git.read_file_from_merge_branch(@filename)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    JSON.parse(file_contents)
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  rescue JSON::ParserError, Errno::ENOENT
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @logger.error "Unable to read branch info from file: #{@filename}"
         
     | 
| 
      
 19 
     | 
    
         
            +
                    {}
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def write(branches, file=nil)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    @git.in_temp_merge_branch do
         
     | 
| 
      
 24 
     | 
    
         
            +
                      file ||= File.open(@filename, 'w')
         
     | 
| 
      
 25 
     | 
    
         
            +
                      file.puts JSON.pretty_generate(branches)
         
     | 
| 
      
 26 
     | 
    
         
            +
                      file.close
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                      @git.add_and_commit(@filename, 'Branch Info', add: { force: true })
         
     | 
| 
      
 29 
     | 
    
         
            +
                    end
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,184 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'logger'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'flash_flow/git'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'flash_flow/data'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'flash_flow/lock'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'flash_flow/notifier'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'flash_flow/branch_merger'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            module FlashFlow
         
     | 
| 
      
 10 
     | 
    
         
            +
              class Deploy
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                class OutOfSyncWithRemote < RuntimeError ; end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                def initialize(opts={})
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @do_not_merge = opts[:do_not_merge]
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @force = opts[:force]
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @rerere_forget = opts[:rerere_forget]
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @stories = [opts[:stories]].flatten.compact
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  @git = Git.new(Config.configuration.git, logger)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @lock = Lock::Base.new(Config.configuration.lock)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @notifier = Notifier::Base.new(Config.configuration.notifier)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @data = Data::Base.new(Config.configuration.branches, Config.configuration.branch_info_file, @git, logger: logger)
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def logger
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @logger ||= FlashFlow::Config.configuration.logger
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def run
         
     | 
| 
      
 31 
     | 
    
         
            +
                  check_version
         
     | 
| 
      
 32 
     | 
    
         
            +
                  check_repo
         
     | 
| 
      
 33 
     | 
    
         
            +
                  puts "Building #{@git.merge_branch}... Log can be found in #{FlashFlow::Config.configuration.log_file}"
         
     | 
| 
      
 34 
     | 
    
         
            +
                  logger.info "\n\n### Beginning #{@git.merge_branch} merge ###\n\n"
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  fetch(@git.merge_remote)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @git.in_original_merge_branch do
         
     | 
| 
      
 38 
     | 
    
         
            +
                    @git.initialize_rerere
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 42 
     | 
    
         
            +
                    @lock.with_lock do
         
     | 
| 
      
 43 
     | 
    
         
            +
                      open_pull_request
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                      @git.reset_temp_merge_branch
         
     | 
| 
      
 46 
     | 
    
         
            +
                      @git.in_temp_merge_branch do
         
     | 
| 
      
 47 
     | 
    
         
            +
                        merge_branches
         
     | 
| 
      
 48 
     | 
    
         
            +
                        commit_branch_info
         
     | 
| 
      
 49 
     | 
    
         
            +
                        commit_rerere
         
     | 
| 
      
 50 
     | 
    
         
            +
                      end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                      @git.copy_temp_to_merge_branch
         
     | 
| 
      
 53 
     | 
    
         
            +
                      @git.delete_temp_merge_branch
         
     | 
| 
      
 54 
     | 
    
         
            +
                      @git.push_merge_branch
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                    print_errors
         
     | 
| 
      
 58 
     | 
    
         
            +
                    logger.info "### Finished #{@git.merge_branch} merge ###"
         
     | 
| 
      
 59 
     | 
    
         
            +
                  rescue Lock::Error, OutOfSyncWithRemote => e
         
     | 
| 
      
 60 
     | 
    
         
            +
                    puts 'Failure!'
         
     | 
| 
      
 61 
     | 
    
         
            +
                    puts e.message
         
     | 
| 
      
 62 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 63 
     | 
    
         
            +
                    @git.run("checkout #{@git.working_branch}")
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                def check_repo
         
     | 
| 
      
 68 
     | 
    
         
            +
                  if @git.staged_and_working_dir_files.any?
         
     | 
| 
      
 69 
     | 
    
         
            +
                    raise RuntimeError.new('You have changes in your working directory. Please stash and try again')
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                def check_version
         
     | 
| 
      
 74 
     | 
    
         
            +
                  data_version = @data.version
         
     | 
| 
      
 75 
     | 
    
         
            +
                  return if data_version.nil?
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                  written_version = data_version.split(".").map(&:to_i)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  running_version = FlashFlow::VERSION.split(".").map(&:to_i)
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  unless written_version[0] < running_version[0] ||
         
     | 
| 
      
 81 
     | 
    
         
            +
                      (written_version[0] == running_version[0] && written_version[1] <= running_version[1]) # Ignore the point release number
         
     | 
| 
      
 82 
     | 
    
         
            +
                    raise RuntimeError.new("Your version of flash flow (#{FlashFlow::VERSION}) is behind the version that was last used (#{data_version}) by a member of your team. Please upgrade to at least #{written_version[0]}.#{written_version[1]}.0 and try again.")
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
                end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                def commit_branch_info
         
     | 
| 
      
 87 
     | 
    
         
            +
                  @stories.each do |story_id|
         
     | 
| 
      
 88 
     | 
    
         
            +
                    @data.add_story(@git.merge_remote, @git.working_branch, story_id)
         
     | 
| 
      
 89 
     | 
    
         
            +
                  end
         
     | 
| 
      
 90 
     | 
    
         
            +
                  @data.save!
         
     | 
| 
      
 91 
     | 
    
         
            +
                end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                def commit_rerere
         
     | 
| 
      
 94 
     | 
    
         
            +
                  current_branches = @data.merged_branches.to_a.select { |branch| !@git.master_branch_contains?(branch.sha) && (Time.now - branch.updated_at < two_weeks) }
         
     | 
| 
      
 95 
     | 
    
         
            +
                  current_rereres = current_branches.map { |branch| branch.resolutions.to_h.values }.flatten
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                  @git.commit_rerere(current_rereres)
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                def two_weeks
         
     | 
| 
      
 101 
     | 
    
         
            +
                  60 * 60 * 24 * 14
         
     | 
| 
      
 102 
     | 
    
         
            +
                end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                def merge_branches
         
     | 
| 
      
 105 
     | 
    
         
            +
                  @data.mergeable.each do |branch|
         
     | 
| 
      
 106 
     | 
    
         
            +
                    remote = @git.fetch_remote_for_url(branch.remote_url)
         
     | 
| 
      
 107 
     | 
    
         
            +
                    if remote.nil?
         
     | 
| 
      
 108 
     | 
    
         
            +
                      raise RuntimeError.new("No remote found for #{branch.remote_url}. Please run 'git remote add *your_remote_name* #{branch.remote_url}' and try again.")
         
     | 
| 
      
 109 
     | 
    
         
            +
                    end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                    fetch(branch.remote)
         
     | 
| 
      
 112 
     | 
    
         
            +
                    git_merge(branch, branch.ref == @git.working_branch)
         
     | 
| 
      
 113 
     | 
    
         
            +
                  end
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                def git_merge(branch, is_working_branch)
         
     | 
| 
      
 117 
     | 
    
         
            +
                  merger = BranchMerger.new(@git, branch)
         
     | 
| 
      
 118 
     | 
    
         
            +
                  forget_rerere = is_working_branch && @rerere_forget
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                  case merger.do_merge(forget_rerere)
         
     | 
| 
      
 121 
     | 
    
         
            +
                    when :deleted
         
     | 
| 
      
 122 
     | 
    
         
            +
                      @data.mark_deleted(branch)
         
     | 
| 
      
 123 
     | 
    
         
            +
                      @notifier.deleted_branch(branch) unless is_working_branch
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                    when :success
         
     | 
| 
      
 126 
     | 
    
         
            +
                      branch.sha = merger.sha
         
     | 
| 
      
 127 
     | 
    
         
            +
                      @data.mark_success(branch)
         
     | 
| 
      
 128 
     | 
    
         
            +
                      @data.set_resolutions(branch, merger.resolutions)
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                    when :conflict
         
     | 
| 
      
 131 
     | 
    
         
            +
                      @data.mark_failure(branch, merger.conflict_sha)
         
     | 
| 
      
 132 
     | 
    
         
            +
                      @notifier.merge_conflict(branch) unless is_working_branch
         
     | 
| 
      
 133 
     | 
    
         
            +
                  end
         
     | 
| 
      
 134 
     | 
    
         
            +
                end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                def open_pull_request
         
     | 
| 
      
 137 
     | 
    
         
            +
                  return false if [@git.master_branch, @git.merge_branch].include?(@git.working_branch)
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                  # TODO - This should use the actual remote for the branch we're on
         
     | 
| 
      
 140 
     | 
    
         
            +
                  @git.push(@git.working_branch, force: @force)
         
     | 
| 
      
 141 
     | 
    
         
            +
                  raise OutOfSyncWithRemote.new("Your branch is out of sync with the remote. If you want to force push, run 'flash_flow -f'") unless @git.last_success?
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                  # TODO - This should use the actual remote for the branch we're on
         
     | 
| 
      
 144 
     | 
    
         
            +
                  if @do_not_merge
         
     | 
| 
      
 145 
     | 
    
         
            +
                    @data.remove_from_merge(@git.merge_remote, @git.working_branch)
         
     | 
| 
      
 146 
     | 
    
         
            +
                  else
         
     | 
| 
      
 147 
     | 
    
         
            +
                    @data.add_to_merge(@git.merge_remote, @git.working_branch)
         
     | 
| 
      
 148 
     | 
    
         
            +
                  end
         
     | 
| 
      
 149 
     | 
    
         
            +
                end
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                def print_errors
         
     | 
| 
      
 152 
     | 
    
         
            +
                  puts format_errors
         
     | 
| 
      
 153 
     | 
    
         
            +
                end
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                def format_errors
         
     | 
| 
      
 156 
     | 
    
         
            +
                  errors = []
         
     | 
| 
      
 157 
     | 
    
         
            +
                  branch_not_merged = nil
         
     | 
| 
      
 158 
     | 
    
         
            +
                  @data.failures.each do |full_ref, failure|
         
     | 
| 
      
 159 
     | 
    
         
            +
                    if failure.ref == @git.working_branch
         
     | 
| 
      
 160 
     | 
    
         
            +
                      branch_not_merged = "\nERROR: Your branch did not merge to #{@git.merge_branch}. Run the following commands to fix the merge conflict and then re-run this script:\n\n  git checkout #{failure.metadata['conflict_sha']}\n  git merge #{@git.working_branch}\n  # Resolve the conflicts\n  git add <conflicted files>\n  git commit --no-edit"
         
     | 
| 
      
 161 
     | 
    
         
            +
                    else
         
     | 
| 
      
 162 
     | 
    
         
            +
                      errors << "WARNING: Unable to merge branch #{failure.remote}/#{failure.ref} to #{@git.merge_branch} due to conflicts."
         
     | 
| 
      
 163 
     | 
    
         
            +
                    end
         
     | 
| 
      
 164 
     | 
    
         
            +
                  end
         
     | 
| 
      
 165 
     | 
    
         
            +
                  errors << branch_not_merged if branch_not_merged
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                  if errors.empty?
         
     | 
| 
      
 168 
     | 
    
         
            +
                    "Success!"
         
     | 
| 
      
 169 
     | 
    
         
            +
                  else
         
     | 
| 
      
 170 
     | 
    
         
            +
                    errors.join("\n")
         
     | 
| 
      
 171 
     | 
    
         
            +
                  end
         
     | 
| 
      
 172 
     | 
    
         
            +
                end
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                private
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                def fetch(remote)
         
     | 
| 
      
 177 
     | 
    
         
            +
                  @fetched_remotes ||= {}
         
     | 
| 
      
 178 
     | 
    
         
            +
                  unless @fetched_remotes[remote]
         
     | 
| 
      
 179 
     | 
    
         
            +
                    @git.fetch(remote)
         
     | 
| 
      
 180 
     | 
    
         
            +
                    @fetched_remotes[remote] = true
         
     | 
| 
      
 181 
     | 
    
         
            +
                  end
         
     | 
| 
      
 182 
     | 
    
         
            +
                end
         
     | 
| 
      
 183 
     | 
    
         
            +
              end
         
     | 
| 
      
 184 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,248 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'flash_flow/cmd_runner'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module FlashFlow
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Git
         
     | 
| 
      
 5 
     | 
    
         
            +
                ATTRIBUTES = [:merge_remote, :merge_branch, :master_branch, :use_rerere]
         
     | 
| 
      
 6 
     | 
    
         
            +
                attr_reader *ATTRIBUTES
         
     | 
| 
      
 7 
     | 
    
         
            +
                attr_reader :working_branch
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                UNMERGED_STATUSES = %w{DD AU UD UA DU AA UU}
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def initialize(config, logger=nil)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @cmd_runner = CmdRunner.new(logger: logger)
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  ATTRIBUTES.each do |attr|
         
     | 
| 
      
 15 
     | 
    
         
            +
                    unless config.has_key?(attr.to_s)
         
     | 
| 
      
 16 
     | 
    
         
            +
                      raise RuntimeError.new("git configuration missing. Required config parameters: #{ATTRIBUTES}")
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    instance_variable_set("@#{attr}", config[attr.to_s])
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  @working_branch = current_branch
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def last_stdout
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @cmd_runner.last_stdout
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def last_command
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @cmd_runner.last_command
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def last_success?
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @cmd_runner.last_success?
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def run(cmd)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  @cmd_runner.run("git #{cmd}")
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def add_and_commit(files, message, opts={})
         
     | 
| 
      
 42 
     | 
    
         
            +
                  files = [files].flatten
         
     | 
| 
      
 43 
     | 
    
         
            +
                  run("add #{'-f ' if opts[:add] && opts[:add][:force]}#{files.join(' ')}")
         
     | 
| 
      
 44 
     | 
    
         
            +
                  run("commit -m '#{message}'")
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def push(branch, options)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  run("push #{'-f' if options[:force]} #{merge_remote} #{branch}")
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def merge(branch)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  run("merge #{branch}")
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                def fetch(remote)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  run("fetch #{remote}")
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def master_branch_contains?(ref)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  run("branch --contains #{ref}")
         
     | 
| 
      
 61 
     | 
    
         
            +
                  last_stdout.split("\n").detect { |str| str[2..-1] == master_branch }
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                def in_original_merge_branch
         
     | 
| 
      
 65 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 66 
     | 
    
         
            +
                    starting_branch = current_branch
         
     | 
| 
      
 67 
     | 
    
         
            +
                    run("checkout #{merge_remote}/#{merge_branch}")
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                    yield
         
     | 
| 
      
 70 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 71 
     | 
    
         
            +
                    run("checkout #{starting_branch}")
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                def read_file_from_merge_branch(filename)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  run("show #{merge_remote}/#{merge_branch}:#{filename}")
         
     | 
| 
      
 77 
     | 
    
         
            +
                  last_stdout
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                def initialize_rerere
         
     | 
| 
      
 81 
     | 
    
         
            +
                  return unless use_rerere
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                  @cmd_runner.run('mkdir .git/rr-cache')
         
     | 
| 
      
 84 
     | 
    
         
            +
                  @cmd_runner.run('cp -R rr-cache/* .git/rr-cache/')
         
     | 
| 
      
 85 
     | 
    
         
            +
                end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                def commit_rerere(current_rereres)
         
     | 
| 
      
 88 
     | 
    
         
            +
                  return unless use_rerere
         
     | 
| 
      
 89 
     | 
    
         
            +
                  @cmd_runner.run('mkdir rr-cache')
         
     | 
| 
      
 90 
     | 
    
         
            +
                  @cmd_runner.run('rm -rf rr-cache/*')
         
     | 
| 
      
 91 
     | 
    
         
            +
                  current_rereres.each do |rerere|
         
     | 
| 
      
 92 
     | 
    
         
            +
                    @cmd_runner.run("cp -R .git/rr-cache/#{rerere} rr-cache/")
         
     | 
| 
      
 93 
     | 
    
         
            +
                  end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  run('add rr-cache/')
         
     | 
| 
      
 96 
     | 
    
         
            +
                  run("commit -m 'Update rr-cache'")
         
     | 
| 
      
 97 
     | 
    
         
            +
                end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                def rerere_resolve!
         
     | 
| 
      
 100 
     | 
    
         
            +
                  return false unless use_rerere
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  merging_files = staged_and_working_dir_files.select { |s| UNMERGED_STATUSES.include?(s[0..1]) }.map { |s| s[3..-1] }
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                  conflicts = merging_files.map do |file|
         
     | 
| 
      
 105 
     | 
    
         
            +
                    File.open(file) { |f| f.grep(/>>>>/) }
         
     | 
| 
      
 106 
     | 
    
         
            +
                  end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                  if conflicts.all? { |c| c.empty? }
         
     | 
| 
      
 109 
     | 
    
         
            +
                    run("add #{merging_files.join(" ")}")
         
     | 
| 
      
 110 
     | 
    
         
            +
                    run('commit --no-edit')
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                    resolutions(merging_files)
         
     | 
| 
      
 113 
     | 
    
         
            +
                  else
         
     | 
| 
      
 114 
     | 
    
         
            +
                    false
         
     | 
| 
      
 115 
     | 
    
         
            +
                  end
         
     | 
| 
      
 116 
     | 
    
         
            +
                end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                def resolutions(files)
         
     | 
| 
      
 119 
     | 
    
         
            +
                  {}.tap do |hash|
         
     | 
| 
      
 120 
     | 
    
         
            +
                    files.map do |file|
         
     | 
| 
      
 121 
     | 
    
         
            +
                      hash[file] = resolution_candidates(file)
         
     | 
| 
      
 122 
     | 
    
         
            +
                    end.flatten
         
     | 
| 
      
 123 
     | 
    
         
            +
                  end
         
     | 
| 
      
 124 
     | 
    
         
            +
                end
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                # git rerere doesn't give you a deterministic way to determine which resolution was used
         
     | 
| 
      
 127 
     | 
    
         
            +
                def resolution_candidates(file)
         
     | 
| 
      
 128 
     | 
    
         
            +
                  @cmd_runner.run("diff -q --from-file #{file} .git/rr-cache/*/postimage")
         
     | 
| 
      
 129 
     | 
    
         
            +
                  different_files = split_diff_lines(@cmd_runner.last_stdout)
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                  @cmd_runner.run('ls -la .git/rr-cache/*/postimage')
         
     | 
| 
      
 132 
     | 
    
         
            +
                  all_files = split_diff_lines(@cmd_runner.last_stdout)
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                  all_files - different_files
         
     | 
| 
      
 135 
     | 
    
         
            +
                end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                def split_diff_lines(arr)
         
     | 
| 
      
 138 
     | 
    
         
            +
                  arr.split("\n").map { |s| s.split(".git/rr-cache/").last.split("/postimage").first }
         
     | 
| 
      
 139 
     | 
    
         
            +
                end
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
                def remotes
         
     | 
| 
      
 142 
     | 
    
         
            +
                  run('remote -v')
         
     | 
| 
      
 143 
     | 
    
         
            +
                  last_stdout.split("\n")
         
     | 
| 
      
 144 
     | 
    
         
            +
                end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                def remotes_hash
         
     | 
| 
      
 147 
     | 
    
         
            +
                  return @remotes_hash if @remotes_hash
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                  @remotes_hash = {}
         
     | 
| 
      
 150 
     | 
    
         
            +
                  remotes.each do |r|
         
     | 
| 
      
 151 
     | 
    
         
            +
                    name = r.split[0]
         
     | 
| 
      
 152 
     | 
    
         
            +
                    url = r.split[1]
         
     | 
| 
      
 153 
     | 
    
         
            +
                    @remotes_hash[name] ||= url
         
     | 
| 
      
 154 
     | 
    
         
            +
                  end
         
     | 
| 
      
 155 
     | 
    
         
            +
                  @remotes_hash
         
     | 
| 
      
 156 
     | 
    
         
            +
                end
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                def fetch_remote_for_url(url)
         
     | 
| 
      
 159 
     | 
    
         
            +
                  fetch_remotes = remotes.grep(Regexp.new(url)).grep(/ \(fetch\)/)
         
     | 
| 
      
 160 
     | 
    
         
            +
                  fetch_remotes.map { |remote| remote.to_s.split("\t").first }.first
         
     | 
| 
      
 161 
     | 
    
         
            +
                end
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                def staged_and_working_dir_files
         
     | 
| 
      
 164 
     | 
    
         
            +
                  run("status --porcelain")
         
     | 
| 
      
 165 
     | 
    
         
            +
                  last_stdout.split("\n").reject { |line| line[0..1] == '??' }
         
     | 
| 
      
 166 
     | 
    
         
            +
                end
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
                def current_branch
         
     | 
| 
      
 169 
     | 
    
         
            +
                  run("rev-parse --abbrev-ref HEAD")
         
     | 
| 
      
 170 
     | 
    
         
            +
                  last_stdout.strip
         
     | 
| 
      
 171 
     | 
    
         
            +
                end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                def most_recent_commit
         
     | 
| 
      
 174 
     | 
    
         
            +
                  run("show -s --format=%cd head")
         
     | 
| 
      
 175 
     | 
    
         
            +
                end
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                def reset_temp_merge_branch
         
     | 
| 
      
 178 
     | 
    
         
            +
                  in_branch(master_branch) do
         
     | 
| 
      
 179 
     | 
    
         
            +
                    run("fetch #{merge_remote}")
         
     | 
| 
      
 180 
     | 
    
         
            +
                    run("branch -D #{temp_merge_branch}")
         
     | 
| 
      
 181 
     | 
    
         
            +
                    run("checkout -b #{temp_merge_branch}")
         
     | 
| 
      
 182 
     | 
    
         
            +
                    run("reset --hard #{merge_remote}/#{master_branch}")
         
     | 
| 
      
 183 
     | 
    
         
            +
                  end
         
     | 
| 
      
 184 
     | 
    
         
            +
                end
         
     | 
| 
      
 185 
     | 
    
         
            +
             
     | 
| 
      
 186 
     | 
    
         
            +
                def push_merge_branch
         
     | 
| 
      
 187 
     | 
    
         
            +
                  run("push -f #{merge_remote} #{merge_branch}")
         
     | 
| 
      
 188 
     | 
    
         
            +
                end
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
                def copy_temp_to_merge_branch
         
     | 
| 
      
 191 
     | 
    
         
            +
                  run("checkout #{temp_merge_branch}")
         
     | 
| 
      
 192 
     | 
    
         
            +
                  run("merge --strategy=ours --no-edit #{merge_branch}")
         
     | 
| 
      
 193 
     | 
    
         
            +
                  run("checkout #{merge_branch}")
         
     | 
| 
      
 194 
     | 
    
         
            +
                  run("merge #{temp_merge_branch}")
         
     | 
| 
      
 195 
     | 
    
         
            +
             
     | 
| 
      
 196 
     | 
    
         
            +
                  squash_commits
         
     | 
| 
      
 197 
     | 
    
         
            +
                end
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
                def commit_message(log)
         
     | 
| 
      
 200 
     | 
    
         
            +
                  "Flash Flow run from branch: #{working_branch}\n\n#{log}".gsub(/'/, '')
         
     | 
| 
      
 201 
     | 
    
         
            +
                end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                def delete_temp_merge_branch
         
     | 
| 
      
 204 
     | 
    
         
            +
                  in_merge_branch do
         
     | 
| 
      
 205 
     | 
    
         
            +
                    run("branch -d #{temp_merge_branch}")
         
     | 
| 
      
 206 
     | 
    
         
            +
                  end
         
     | 
| 
      
 207 
     | 
    
         
            +
                end
         
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
      
 209 
     | 
    
         
            +
                def in_temp_merge_branch(&block)
         
     | 
| 
      
 210 
     | 
    
         
            +
                  in_branch(temp_merge_branch, &block)
         
     | 
| 
      
 211 
     | 
    
         
            +
                end
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
      
 213 
     | 
    
         
            +
                def in_merge_branch(&block)
         
     | 
| 
      
 214 
     | 
    
         
            +
                  in_branch(merge_branch, &block)
         
     | 
| 
      
 215 
     | 
    
         
            +
                end
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
                private
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
                def squash_commits
         
     | 
| 
      
 220 
     | 
    
         
            +
                  # There are three commits created by flash flow that we don't need in the message
         
     | 
| 
      
 221 
     | 
    
         
            +
                  run("log #{merge_remote}/#{merge_branch}..#{merge_branch}~3")
         
     | 
| 
      
 222 
     | 
    
         
            +
                  log = last_stdout
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
                  # Get all the files that differ between existing acceptance and new acceptance
         
     | 
| 
      
 225 
     | 
    
         
            +
                  run("diff --name-only #{merge_remote}/#{merge_branch} #{merge_branch}")
         
     | 
| 
      
 226 
     | 
    
         
            +
                  files = last_stdout.split("\n")
         
     | 
| 
      
 227 
     | 
    
         
            +
                  run("reset #{merge_remote}/#{merge_branch}")
         
     | 
| 
      
 228 
     | 
    
         
            +
                  run("add #{files.map { |f| "'#{f}'" }.join(" ")}")
         
     | 
| 
      
 229 
     | 
    
         
            +
             
     | 
| 
      
 230 
     | 
    
         
            +
                  run("commit -m '#{commit_message(log)}'")
         
     | 
| 
      
 231 
     | 
    
         
            +
                end
         
     | 
| 
      
 232 
     | 
    
         
            +
             
     | 
| 
      
 233 
     | 
    
         
            +
                def temp_merge_branch
         
     | 
| 
      
 234 
     | 
    
         
            +
                  "flash_flow/#{merge_branch}"
         
     | 
| 
      
 235 
     | 
    
         
            +
                end
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
                def in_branch(branch)
         
     | 
| 
      
 238 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 239 
     | 
    
         
            +
                    starting_branch = current_branch
         
     | 
| 
      
 240 
     | 
    
         
            +
                    run("checkout #{branch}")
         
     | 
| 
      
 241 
     | 
    
         
            +
             
     | 
| 
      
 242 
     | 
    
         
            +
                    yield
         
     | 
| 
      
 243 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 244 
     | 
    
         
            +
                    run("checkout #{starting_branch}")
         
     | 
| 
      
 245 
     | 
    
         
            +
                  end
         
     | 
| 
      
 246 
     | 
    
         
            +
                end
         
     | 
| 
      
 247 
     | 
    
         
            +
              end
         
     | 
| 
      
 248 
     | 
    
         
            +
            end
         
     |