module GitPr

Constants

VERSION

Public Class Methods

are_branches_identical?(git, branch_one, branch_two) click to toggle source
# File lib/git_pr/merge.rb, line 19
def self.are_branches_identical? git, branch_one, branch_two
  sha1 = `git rev-parse --short #{branch_one}`.strip!
  sha2 = `git rev-parse --short #{branch_two}`.strip!
  return (sha1 == sha2) &&
         git.diff(branch_one, branch_two).stats[:total][:files] == 0
end
diff() click to toggle source
# File lib/git_pr/diff.rb, line 3
def self.diff

end
ensure_remote_for_project(git, username, ssh_url, git_url) click to toggle source
# File lib/git_pr.rb, line 42
def self.ensure_remote_for_project git, username, ssh_url, git_url
  # find or add a remote for the PR
  remote = git.remotes.find { |r| [git_url, ssh_url].include? r.url }
  unless remote
    puts "Adding remote '#{username}' from #{ssh_url}"
    remote = git.add_remote username, ssh_url
  end
  remote
end
ensure_remotes_for_pull_request(git, pull) click to toggle source
# File lib/git_pr/merge.rb, line 5
def self.ensure_remotes_for_pull_request git, pull
  source_remote = GitPr.ensure_remote_for_project(git,
                                                  pull.head.user.login,
                                                  pull.head.repo.ssh_url,
                                                  pull.head.repo.git_url)

  target_remote = GitPr.ensure_remote_for_project(git,
                                                  pull.base.user.login,
                                                  pull.base.repo.ssh_url,
                                                  pull.base.repo.git_url)

  [source_remote, target_remote]
end
get_char() click to toggle source
# File lib/git_pr.rb, line 26
def self.get_char
  state = `stty -g`
  `stty raw -echo -icanon isig`

  c = STDIN.getc.chr
  c
ensure
  `stty #{state}`
  puts "#{c}"
end
merge_pull_cleanly(git, pull, command_options) click to toggle source
# File lib/git_pr/merge.rb, line 26
  def self.merge_pull_cleanly git, pull, command_options
    unless command_options.yolo
      case pull.state
      when 'failure'
        failed_statuses = pull.statuses.select { |s| s.state == 'failure' }
        max_context = failed_statuses.map { |s| s.context.length }.max
        puts <<EOS
#{"ERROR".red}: One or more status checks have failed on this pull request!
You should fix these before merging.

EOS
        failed_statuses.each do |status|
          puts "#{GitPr::PullRequest.summary_icon(status.state)}  #{status.context.ljust(max_context)}  #{status.target_url}"
        end

        puts <<EOS

If you're still sure you want to merge, run 'git pr merge --yolo #{pull.number}'
EOS
        exit 1
      when 'success'
        nil
      else
        unless pull.statuses.empty?
          pending_statuses = pull.statuses.select { |s| s.state == 'pending' }
          max_context = pending_statuses.map { |s| s.context.length }.max
          puts <<EOS
#{"WARNING".yellow}: One or more status checks is in progress on this pull request.
You should let them finish.

EOS
          pending_statuses.each do |status|
            puts "#{GitPr::PullRequest.summary_icon(status.state)}  #{status.context.ljust(max_context)}  #{status.target_url}"
          end

          puts <<EOS

If you're still sure you want to merge, run 'git pr merge --yolo #{pull.number}'
EOS
          exit 1
        end
      end
    end

    pull_number = pull.number
    source_branch = pull.head.ref
    source_repo_ssh_url = pull.head.repo.git_url
    source_repo_clone_url = pull.head.repo.clone_url

    target_branch = pull.base.ref
    target_repo_ssh_url = pull.base.repo.git_url
    target_repo_clone_url = pull.base.repo.clone_url

    puts "Merging #{pull.summary}".cyan
    puts "#{target_repo_ssh_url}/#{target_branch} <= #{source_repo_ssh_url}/#{source_branch}\n".cyan

    # find or add a remote for the PR
    source_remote, target_remote = self.ensure_remotes_for_pull_request git, pull

    # Fetch latest changes from source & target remotes. Useful in case one of source or target
    # branches doesn't exist locally yet, or if we've never pulled from one of the remotes.
    puts "Fetching latest changes from '#{source_remote}'"
    source_remote.fetch
    unless target_remote.name == source_remote.name
      puts "Fetching latest changes from '#{target_remote}'"
      target_remote.fetch
    end

    current_branch = `git rev-parse --abbrev-ref HEAD`.strip!
    at_exit do
      # Restore the branch we started on, if it hasn't been deleted as part of
      # completing a merge.
      if git.branches[current_branch]
        GitPr.run_command "git checkout -q #{current_branch}"
      end
    end

    # Get the target branch up to date
    puts "Update branch '#{target_branch}' from remote"
    GitPr.run_command "git checkout -q #{target_branch}"
    GitPr.run_command "git pull --no-rebase --ff-only", :failure => lambda {
      "Unable to update local target branch '#{target_branch}'. Please repair manually before continuing.".red
    }

    # If the local target branch differs from the remote target branch, they
    # must be reconciled manually.
    remote_target_branch = "#{target_remote}/#{target_branch}"
    unless GitPr.are_branches_identical? git, "remotes/#{remote_target_branch}", target_branch
      puts "Local branch '#{target_branch}' differs from remote branch '#{remote_target_branch}'. Please reconcile before continuing.".red
      exit -1
    end

    # If a local branch exists with the name source_branch, check that it has the
    # same contents as the remote source branch. If not, it must be reconciled
    # manually.
    remote_source_branch = "#{source_remote}/#{source_branch}"
    if git.is_local_branch? source_branch and
        not GitPr.are_branches_identical? git, "remotes/#{remote_source_branch}", source_branch
      puts "Local branch '#{source_branch}' differs from remote branch '#{remote_source_branch}'. Please reconcile before continuing.".red
      exit -1
    end

    # Check out the remote source branch using a temporary branch name,
    # failing if the temporary name already exists.
    rebase_branch = "#{source_branch}-rebase"
    puts "Create temporary branch '#{rebase_branch}'"
    if git.is_local_branch? rebase_branch
      puts "Local rebase branch '#{rebase_branch}' already exists. Please remove before continuing.".red
      GitPr.run_command "git checkout -q #{current_branch}"
      exit -1
    end
    GitPr.run_command "git checkout -q -b #{rebase_branch} #{remote_source_branch}"

    # Add an at_exit handler to blow away the temp branch when we exit
    at_exit do
      if git.is_local_branch? rebase_branch
        puts "Removing temporary branch #{rebase_branch}" if $verbose
        GitPr.run_command "git checkout -q #{target_branch}"
        GitPr.run_command "git branch -D #{rebase_branch}"
      end
    end

    # Rebase the rebase branch on top of the target branch
    puts "Rebasing '#{rebase_branch}' on top of '#{target_branch}'"
    GitPr.run_command "git rebase #{target_branch} 2>&1", :failure => lambda {
      GitPr.run_command "git rebase --abort"

      puts "Unable to automatically rebase #{remote_source_branch} on top of #{target_branch}. Rebase manually and push before trying again.".red
      puts "Run: " + "git checkout #{source_branch}".yellow
      puts "     " + "git rebase #{target_branch}".yellow + " and fix up any conflicts."
      puts "     " + "git push --force-with-lease".yellow
    }

    # Force push the rebased branch to the source remote.
    puts "Pushing changes from '#{rebase_branch}' to '#{source_remote.name}/#{source_branch}'"
    GitPr.run_command "git push --force-with-lease #{source_remote.name} HEAD:#{source_branch} 2>&1"

    # Merge the source branch into the target. Use --no-ff so that an explicit
    # merge commit is created.
    puts "Merging changes from '#{rebase_branch}' to '#{target_branch}'"
    GitPr.run_command "git checkout -q #{target_branch}"
    commit_message = <<EOS
Merge #{pull.summary}

#{pull.body}
EOS
    GitPr.run_command "git merge --no-ff #{rebase_branch} -m #{Shellwords.escape commit_message}"

    # Print a log of the merge with branch structure visible. Jump through hoops to
    # get the right branch to start the log revision range with. If origin/develop
    # is a merge commit, we need the right parent of the merge.
    #
    # The goal is to get output like this:
    #
    # *   5be2a77 (HEAD, develop) PR #1269. Merge branch floatplane/feature/categories into develop.
    # |\
    # | * 2242141 (floatplane/feature/categories, feature/categories) Process CR feedback. Remove StaticCreatorListDataSource, will just rework Streamed* version to meet needs instead.
    # | * d7cf231 Implement StaticCreatorListDataSource for categories, rename CreatorListDataSource => StreamedCreatorListDataSource
    # | * ef034d0 Don't animate profile pic transitions when we're re-using a cell and needing to replace someone else's picture. Only animate from the blank thumbnail to an actual picture.
    # | * 25cda8b Refactor CreatorListViewController.
    # | * 682b7ba Adjust search dialog size and default position. Remove temp close button. Stub categories into search dialog.
    # | * e8ba0b1 Rename CollaboratorsListViewController => CreatorListViewController. Add CollaboratorListViewController as a subclass of CreatorListViewController, will refactor behavior into it in future commits.
    # | * e901256 Make dismissWithBackgroundTouch work for all CustomModalDialogs, even those that don't set useCustomPopover. Fix latent bug in ApplicationInfoNavigationController's implementation of the same.
    # |/
    # * 8d5ecbc (origin/develop, origin/HEAD) Merge branch 'feature/schemaUpgradeUtils' into develop
    #
    # where the log stops at origin/develop, no matter whether it's a merge commit or not.
    #
    puts "\nVerify that the merge looks clean:\n".cyan
    origin_parent = `git rev-list --abbrev-commit --parents -n 1 #{target_remote}/#{target_branch}`.split().last
    GitPr.run_command "git log --graph --decorate --pretty=oneline --abbrev-commit --color #{target_branch} #{origin_parent}..#{target_branch}", :force_print_output => true

    if GitPr.prompt "\nDo you want to proceed with the merge (y/n)? ".cyan
      puts "Pushing changes to '#{target_remote}'"
      GitPr.run_command "git push #{target_remote} #{target_branch} 2>&1"

      # Delete the remote and local feature branches
      source_branch_sha = git.branches["#{source_remote}/#{source_branch}"].gcommit.sha[0..6]
      GitPr.run_command "git push #{source_remote} :#{source_branch} 2>&1"
      if git.is_local_branch? source_branch
        source_branch_sha = git.branches[source_branch].gcommit.sha[0..6]
        GitPr.run_command "git branch -D #{source_branch}"
      end
      puts "Feature branch '#{source_branch}' deleted. To restore it, run: " + "git branch #{source_branch} #{source_branch_sha}".green

      puts "\nMerge complete!".cyan
    else
      puts "\nUndoing local merge"
      GitPr.run_command "git reset --hard #{target_remote}/#{target_branch}"
    end
  end
prompt(prompt) click to toggle source
# File lib/git_pr.rb, line 37
def self.prompt prompt
  print prompt
  return GitPr.get_char.downcase == 'y'
end
run_command(cmd, args = { :failure => lambda {}, :force_print_output => false }) click to toggle source

Your code goes here…

# File lib/git_pr.rb, line 11
def self.run_command(cmd,
                     args = {
                       :failure => lambda {},
                       :force_print_output => false
                     })
  puts cmd.green if $verbose
  result = `#{cmd}`
  puts result if $verbose || args[:force_print_output]
  puts '' if $verbose
  if $?.exitstatus != 0
    args[:failure].call()
    exit -1
  end
end