class Git::Pr::Release::CLI
Attributes
labels[R]
production_branch[R]
repository[R]
staging_branch[R]
template_path[R]
Public Class Methods
new()
click to toggle source
# File lib/git/pr/release/cli.rb, line 16 def initialize @dry_run = false @json = false @no_fetch = false @squashed = false end
start()
click to toggle source
# File lib/git/pr/release/cli.rb, line 11 def self.start result = self.new.start exit result end
Public Instance Methods
build_and_merge_pr_title_and_body(release_pr, merged_prs, changed_files)
click to toggle source
# File lib/git/pr/release/cli.rb, line 233 def build_and_merge_pr_title_and_body(release_pr, merged_prs, changed_files) # release_pr is nil when dry_run && create_mode old_body = (release_pr && release_pr.body != nil) ? release_pr.body : "" pr_title, new_body = build_pr_title_and_body(release_pr, merged_prs, changed_files, template_path) [pr_title, merge_pr_body(old_body, new_body)] end
client()
click to toggle source
# File lib/git/pr/release/cli.rb, line 57 def client @client ||= Octokit::Client.new :access_token => obtain_token! end
configure()
click to toggle source
# File lib/git/pr/release/cli.rb, line 61 def configure host, @repository, scheme = host_and_repository_and_scheme if host if scheme == 'https' # GitHub Enterprise ssl_no_verify = %w[true 1].include? ENV.fetch('GIT_PR_RELEASE_SSL_NO_VERIFY') { git_config('ssl-no-verify') } if ssl_no_verify OpenSSL::SSL.const_set :VERIFY_PEER, OpenSSL::SSL::VERIFY_NONE end end Octokit.configure do |c| c.api_endpoint = "#{scheme}://#{host}/api/v3" c.web_endpoint = "#{scheme}://#{host}/" end end @production_branch = ENV.fetch('GIT_PR_RELEASE_BRANCH_PRODUCTION') { git_config('branch.production') } || 'master' @staging_branch = ENV.fetch('GIT_PR_RELEASE_BRANCH_STAGING') { git_config('branch.staging') } || 'staging' @template_path = ENV.fetch('GIT_PR_RELEASE_TEMPLATE') { git_config('template') } _labels = ENV.fetch('GIT_PR_RELEASE_LABELS') { git_config('labels') } @labels = _labels && _labels.split(/\s*,\s*/) || [] say "Repository: #{repository}", :debug say "Production branch: #{production_branch}", :debug say "Staging branch: #{staging_branch}", :debug say "Template path: #{template_path}", :debug say "Labels #{labels}", :debug end
create_release_pr(merged_prs)
click to toggle source
# File lib/git/pr/release/cli.rb, line 184 def create_release_pr(merged_prs) found_release_pr = detect_existing_release_pr create_mode = found_release_pr.nil? if create_mode if @dry_run release_pr = nil changed_files = [] else release_pr = prepare_release_pr changed_files = pull_request_files(release_pr) end else release_pr = found_release_pr changed_files = pull_request_files(release_pr) end pr_title, pr_body = if @overwrite_description build_pr_title_and_body(release_pr, merged_prs, changed_files, template_path) else build_and_merge_pr_title_and_body(release_pr, merged_prs, changed_files) end if @dry_run say 'Dry-run. Not updating PR', :info say pr_title, :notice say pr_body, :notice dump_result_as_json( release_pr, merged_prs, changed_files ) if @json return end update_release_pr(release_pr, pr_title, pr_body) say "#{create_mode ? 'Created' : 'Updated'} pull request: #{release_pr.rels[:html].href}", :notice dump_result_as_json( release_pr, merged_prs, changed_files ) if @json end
detect_existing_release_pr()
click to toggle source
# File lib/git/pr/release/cli.rb, line 221 def detect_existing_release_pr say 'Searching for existing release pull requests...', :info user=repository.split("/")[0] client.pull_requests(repository, head: "#{user}:#{staging_branch}", base: production_branch).first end
fetch_merged_pr_numbers_from_git_remote()
click to toggle source
# File lib/git/pr/release/cli.rb, line 113 def fetch_merged_pr_numbers_from_git_remote merged_feature_head_sha1s = git(:log, '--merges', '--pretty=format:%P', "origin/#{production_branch}..origin/#{staging_branch}").map do |line| main_sha1, feature_sha1 = line.chomp.split /\s+/ feature_sha1 end git('ls-remote', 'origin', 'refs/pull/*/head').map do |line| sha1, ref = line.chomp.split /\s+/ if merged_feature_head_sha1s.include? sha1 if %r<^refs/pull/(\d+)/head$>.match ref pr_number = $1.to_i if git('merge-base', sha1, "origin/#{production_branch}").first.chomp == sha1 say "##{pr_number} (#{sha1}) is already merged into #{production_branch}", :debug else pr_number end else say "Bad pull request head ref format: #{ref}", :warn nil end end end.compact end
fetch_merged_prs()
click to toggle source
# File lib/git/pr/release/cli.rb, line 92 def fetch_merged_prs bool = git(:'rev-parse', '--is-shallow-repository').first.chomp if bool == 'true' git(:fetch, '--unshallow') end git :remote, 'update', 'origin' unless @no_fetch merged_pull_request_numbers = fetch_merged_pr_numbers_from_git_remote if @squashed merged_pull_request_numbers.concat(fetch_squash_merged_pr_numbers_from_github) end merged_prs = merged_pull_request_numbers.uniq.sort.map do |nr| pr = client.pull_request repository, nr say "To be released: ##{pr.number} #{pr.title}", :notice pr end merged_prs end
fetch_squash_merged_pr_numbers_from_github()
click to toggle source
# File lib/git/pr/release/cli.rb, line 148 def fetch_squash_merged_pr_numbers_from_github # When "--abbrev" is specified, the length of the each line of the stdout isn't fixed. # It is just a minimum length, and if the commit cannot be uniquely identified with # that length, a longer commit hash will be displayed. # We specify this option to minimize the length of the query string, but we use # "--abbrev=7" because the SHA syntax of the search API requires a string of at # least 7 characters. # ref. https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests#search-by-commit-sha # This is done because there is a length limit on the API query string, and we want # to create a string with the minimum possible length. shas = git(:log, '--pretty=format:%h', "--abbrev=7", "--no-merges", "--first-parent", "origin/#{production_branch}..origin/#{staging_branch}").map(&:chomp) pr_nums = [] query_base = "repo:#{repository} is:pr is:closed" query = query_base # Make bulk requests with multiple SHAs of the maximum possible length. # If multiple SHAs are specified, the issue search API will treat it like an OR search, # and all the pull requests will be searched. # This is difficult to read from the current documentation, but that is the current # behavior and GitHub support has responded that this is the spec. shas.each do |sha| # Longer than 256 characters are not supported in the query. # ref. https://docs.github.com/en/rest/reference/search#limitations-on-query-length if query.length + 1 + sha.length >= 256 pr_nums.concat(search_issue_numbers(query)) query = query_base end query += " " + sha end if query != query_base pr_nums.concat(search_issue_numbers(query)) end pr_nums end
prepare_release_pr()
click to toggle source
# File lib/git/pr/release/cli.rb, line 227 def prepare_release_pr client.create_pull_request( repository, production_branch, staging_branch, 'Preparing release pull request...', '' ) end
pull_request_files(pull_request)
click to toggle source
Fetch PR files of specified pull_request
# File lib/git/pr/release/cli.rb, line 257 def pull_request_files(pull_request) return [] if pull_request.nil? # Fetch files as many as possible client.auto_paginate = true files = client.pull_request_files repository, pull_request.number client.auto_paginate = false return files end
search_issue_numbers(query)
click to toggle source
# File lib/git/pr/release/cli.rb, line 139 def search_issue_numbers(query) sleep 1 say "search issues with query:#{query}", :debug # Fortunately, we don't need to take care of the page count in response, because # the default value of per_page is 30 and we can't specify more than 30 commits due to # the length limit specification of the query string. client.search_issues("#{query}")[:items].map(&:number) end
start()
click to toggle source
# File lib/git/pr/release/cli.rb, line 23 def start OptionParser.new do |opts| opts.on('-n', '--dry-run', 'Do not create/update a PR. Just prints out') do |v| @dry_run = v end opts.on('--json', 'Show data of target PRs in JSON format') do |v| @json = v end opts.on('--no-fetch', 'Do not fetch from remote repo before determining target PRs (CI friendly)') do |v| @no_fetch = v end opts.on('--squashed', 'Handle squash merged PRs') do |v| @squashed = v end opts.on('--overwrite-description', 'Force overwrite PR description') do |v| @overwrite_description = v end end.parse! ### Set up configuration configure ### Fetch merged PRs merged_prs = fetch_merged_prs if merged_prs.empty? say 'No pull requests to be released', :error return 1 end ### Create a release PR create_release_pr(merged_prs) return 0 end
update_release_pr(release_pr, pr_title, pr_body)
click to toggle source
# File lib/git/pr/release/cli.rb, line 241 def update_release_pr(release_pr, pr_title, pr_body) say 'Pull request body:', :debug say pr_body, :debug client.update_pull_request( repository, release_pr.number, :title => pr_title, :body => pr_body ) unless labels.empty? client.add_labels_to_an_issue( repository, release_pr.number, labels ) end end