Module: Git::Pr::Release::Util

Included in:
CLI, PullRequest, PullRequest
Defined in:
lib/git/pr/release/util.rb

Constant Summary collapse

DEFAULT_PR_TEMPLATE =

First line will be the title of the PR

<<ERB
Release <%= Time.now %>
<% pull_requests.each do |pr| -%>
<%=  pr.to_checklist_item %>
<% end -%>
ERB

Instance Method Summary collapse

Instance Method Details

#build_pr_title_and_body(release_pr, merged_prs, changed_files, template_path) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/git/pr/release/util.rb', line 86

def build_pr_title_and_body(release_pr, merged_prs, changed_files, template_path)
  release_pull_request = target_pull_request = release_pr ? PullRequest.new(release_pr) : DummyPullRequest.new
  merged_pull_requests = pull_requests = merged_prs.map { |pr| PullRequest.new(pr) }

  template = if template_path
               template_fullpath = File.join(git('rev-parse', '--show-toplevel').first.chomp, template_path)
               File.read(template_fullpath)
             else
               DEFAULT_PR_TEMPLATE
             end

  erb = ERB.new template, nil, '-'
  content = erb.result binding
  content.split(/\n/, 2)
end

#dump_result_as_json(release_pr, merged_prs, changed_files) ⇒ Object



102
103
104
105
106
107
108
# File 'lib/git/pr/release/util.rb', line 102

def dump_result_as_json(release_pr, merged_prs, changed_files)
  puts( {
    :release_pull_request => (release_pr ? PullRequest.new(release_pr) : DummyPullRequest.new).to_hash,
    :merged_pull_requests => merged_prs.map { |pr| PullRequest.new(pr).to_hash },
    :changed_files        => changed_files.map { |file| file.to_hash }
  }.to_json )
end

#git(*command) ⇒ Object



48
49
50
51
52
53
54
55
56
# File 'lib/git/pr/release/util.rb', line 48

def git(*command)
  command = [ 'git', *command.map(&:to_s) ]
  say "Executing `#{command.join(' ')}`", :trace
  out, status = Open3.capture2(*command)
  unless status.success?
    raise "Executing `#{command.join(' ')}` failed: #{status}"
  end
  out.each_line
end

#git_config(key) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/git/pr/release/util.rb', line 58

def git_config(key)
  host, _ = host_and_repository_and_scheme()

  plain_key = [ 'pr-release', key ].join('.')
  host_aware_key = [ 'pr-release', host, key ].compact.join('.')

  begin
    git(:config, '-f', '.git-pr-release', plain_key).first.chomp
  rescue
    git(:config, host_aware_key).first.chomp rescue nil
  end
end

#git_config_set(key, value) ⇒ Object



71
72
73
74
75
76
# File 'lib/git/pr/release/util.rb', line 71

def git_config_set(key, value)
  host, _ = host_and_repository_and_scheme()
  host_aware_key = [ 'pr-release', host, key ].compact.join('.')

  git :config, '--global', host_aware_key, value
end

#host_and_repository_and_schemeObject



12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/git/pr/release/util.rb', line 12

def host_and_repository_and_scheme
  @host_and_repository_and_scheme ||= begin
    remote = git(:config, 'remote.origin.url').first.chomp
    unless %r(^\w+://) === remote
      remote = "ssh://#{remote.sub(':', '/')}"
    end

    remote_url = URI.parse(remote)
    repository = remote_url.path.sub(%r(^/), '').sub(/\.git$/, '')

    host = remote_url.host == 'github.com' ? nil : remote_url.host
    [ host, repository, remote_url.scheme === 'http' ? 'http' : 'https' ]
  end
end

#merge_pr_body(old_body, new_body) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/git/pr/release/util.rb', line 110

def merge_pr_body(old_body, new_body)
  # Try to take over checklist statuses
  pr_body_lines = []

  check_status = {}
  old_body.split(/\r?\n/).each { |line|
    line.match(/^- \[(?<check_value>[ x])\] #(?<issue_number>\d+)/) { |m|
      say "Found pull-request checkbox \##{m[:issue_number]} is #{m[:check_value]}.", :trace
      check_status[m[:issue_number]] = m[:check_value]
    }
  }
  old_body_unchecked = old_body.gsub /^- \[[ x]\] \#(\d+)/, '- [ ] #\1'

  Diff::LCS.traverse_balanced(old_body_unchecked.split(/\r?\n/), new_body.split(/\r?\n/)) do |event|
    say "diff: #{event.inspect}", :trace
    action, old, new = *event
    old_nr, old_line = *old
    new_nr, new_line = *new

    case action
    when '=', '+'
      say "Use line as is: #{new_line}", :trace
      pr_body_lines << new_line
    when '-'
      say "Use old line: #{old_line}", :trace
      pr_body_lines << old_line
    when '!'
      if [ old_line, new_line ].all? { |line| /^- \[ \]/ === line }
        say "Found checklist diff; use old one: #{old_line}", :trace
        pr_body_lines << old_line
      else
        # not a checklist diff, use both line
        say "Use line as is: #{old_line}", :trace
        pr_body_lines << old_line

        say "Use line as is: #{new_line}", :trace
        pr_body_lines << new_line
      end
    else
      say "Unknown diff event: #{event}", :warn
    end
  end

  merged_body = pr_body_lines.join("\n")
  check_status.each { |issue_number, check_value|
    say "Update pull-request checkbox \##{issue_number} to #{check_value}.", :trace
    merged_body.gsub! /^- \[ \] \##{issue_number}/, "- [#{check_value}] \##{issue_number}"
  }

  merged_body
end

#obtain_token!Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/git/pr/release/util.rb', line 162

def obtain_token!
  token = ENV.fetch('GIT_PR_RELEASE_TOKEN') { git_config('token') }

  unless token
    require 'highline/import'
    STDERR.puts 'Could not obtain GitHub API token.'
    STDERR.puts 'Trying to generate token...'

    username = ask('username? ') { |q| q.default = ENV['USER'] }
    password = ask('password? (not saved) ') { |q| q.echo = '*' }

    temporary_client = Octokit::Client.new :login => username, :password => password

    auth = request_authorization(temporary_client, nil)

    token = auth.token
    git_config_set 'token', token
  end

  token
end

#request_authorization(client, two_factor_code) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/git/pr/release/util.rb', line 184

def request_authorization(client, two_factor_code)
  params = { :scopes => [ 'public_repo', 'repo' ], :note => 'git-pr-release' }
  params[:headers] = { "X-GitHub-OTP" => two_factor_code} if two_factor_code

  auth = nil
  begin
    auth = client.create_authorization(params)
  rescue Octokit::OneTimePasswordRequired
    two_factor_code = ask('two-factor authentication code? ')
    auth = request_authorization(client, two_factor_code)
  end

  auth
end

#say(message, level) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/git/pr/release/util.rb', line 27

def say(message, level)
  color = case level
    when :trace
      return unless ENV['DEBUG']
      nil
    when :debug
      return unless ENV['DEBUG']
      :blue
    when :info
      :green
    when :notice
      :yellow
    when :warn
      :magenta
    when :error
      :red
    end

  STDERR.puts message.colorize(color)
end