Class: Danger::GitHub

Inherits:
Object
  • Object
show all
Defined in:
lib/danger/request_sources/github.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ci_source, environment) ⇒ GitHub

Returns a new instance of GitHub.



9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/danger/request_sources/github.rb', line 9

def initialize(ci_source, environment)
  self.ci_source = ci_source
  self.environment = environment
  self.support_tokenless_auth = false

  Octokit.auto_paginate = true
  @token = @environment["DANGER_GITHUB_API_TOKEN"]
  self.github_host = @environment["DANGER_GITHUB_HOST"] || "github.com"
  if @environment["DANGER_GITHUB_API_HOST"]
    Octokit.api_endpoint = @environment["DANGER_GITHUB_API_HOST"]
  end
end

Instance Attribute Details

#base_commitObject

Returns the value of attribute base_commit.



7
8
9
# File 'lib/danger/request_sources/github.rb', line 7

def base_commit
  @base_commit
end

#ci_sourceObject

Returns the value of attribute ci_source.



7
8
9
# File 'lib/danger/request_sources/github.rb', line 7

def ci_source
  @ci_source
end

#environmentObject

Returns the value of attribute environment.



7
8
9
# File 'lib/danger/request_sources/github.rb', line 7

def environment
  @environment
end

#github_hostObject

Returns the value of attribute github_host.



7
8
9
# File 'lib/danger/request_sources/github.rb', line 7

def github_host
  @github_host
end

#head_commitObject

Returns the value of attribute head_commit.



7
8
9
# File 'lib/danger/request_sources/github.rb', line 7

def head_commit
  @head_commit
end

#ignored_violationsObject

Returns the value of attribute ignored_violations.



7
8
9
# File 'lib/danger/request_sources/github.rb', line 7

def ignored_violations
  @ignored_violations
end

#issue_jsonObject

Returns the value of attribute issue_json.



7
8
9
# File 'lib/danger/request_sources/github.rb', line 7

def issue_json
  @issue_json
end

#pr_jsonObject

Returns the value of attribute pr_json.



7
8
9
# File 'lib/danger/request_sources/github.rb', line 7

def pr_json
  @pr_json
end

#support_tokenless_authObject

Returns the value of attribute support_tokenless_auth.



7
8
9
# File 'lib/danger/request_sources/github.rb', line 7

def support_tokenless_auth
  @support_tokenless_auth
end

Instance Method Details

#branch_for_mergeObject



59
60
61
# File 'lib/danger/request_sources/github.rb', line 59

def branch_for_merge
  self.pr_json[:base][:ref]
end

#clientObject



22
23
24
25
26
27
28
# File 'lib/danger/request_sources/github.rb', line 22

def client
  raise "No API given, please provide one using `DANGER_GITHUB_API_TOKEN`" if !@token && !support_tokenless_auth

  @client ||= Octokit::Client.new(
    access_token: @token
  )
end

#delete_old_comments!(except: nil) ⇒ Object

Get rid of the previously posted comment, to only have the latest one



139
140
141
142
143
144
145
146
# File 'lib/danger/request_sources/github.rb', line 139

def delete_old_comments!(except: nil)
  issues = client.issue_comments(ci_source.repo_slug, ci_source.pull_request_id)
  issues.each do |issue|
    next unless issue[:body].include?("generated_by_danger")
    next if issue[:id] == except
    client.delete_comment(ci_source.repo_slug, issue[:id])
  end
end

#fetch_detailsObject



34
35
36
37
38
# File 'lib/danger/request_sources/github.rb', line 34

def fetch_details
  self.pr_json = client.pull_request(ci_source.repo_slug, ci_source.pull_request_id)
  fetch_issue_details(self.pr_json)
  self.ignored_violations = ignored_violations_from_pr(self.pr_json)
end

#fetch_issue_details(pr_json) ⇒ Object



46
47
48
49
# File 'lib/danger/request_sources/github.rb', line 46

def fetch_issue_details(pr_json)
  href = pr_json[:_links][:issue][:href]
  self.issue_json = client.get(href)
end

#generate_comment(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: {}) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/danger/request_sources/github.rb', line 166

def generate_comment(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: {})
  require 'erb'

  md_template = File.join(Danger.gem_path, "lib/danger/comment_generators/github.md.erb")

  # erb: http://www.rrn.dk/rubys-erb-templating-system
  # for the extra args: http://stackoverflow.com/questions/4632879/erb-template-removing-the-trailing-line
  @tables = [
    table("Error", "no_entry_sign", errors, previous_violations),
    table("Warning", "warning", warnings, previous_violations),
    table("Message", "book", messages, previous_violations)
  ]
  @markdowns = markdowns

  return ERB.new(File.read(md_template), 0, "-").result(binding)
end

#generate_github_description(warnings: nil, errors: nil) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/danger/request_sources/github.rb', line 154

def generate_github_description(warnings: nil, errors: nil)
  if errors.empty? && warnings.empty?
    return "All green. #{random_compliment}"
  else
    message = ""
    message += "#{errors.count} Error#{errors.count == 1 ? '' : 's'}. " unless errors.empty?
    message += "#{warnings.count} Warning#{warnings.count == 1 ? '' : 's'}. " unless warnings.empty?
    message += "Don't worry, everything is fixable."
    return message
  end
end

#ignored_violations_from_pr(pr_json) ⇒ Object



40
41
42
43
44
# File 'lib/danger/request_sources/github.rb', line 40

def ignored_violations_from_pr(pr_json)
  pr_body = pr_json[:body]
  return [] if pr_body.nil?
  pr_body.chomp.scan(/>\s*danger\s*:\s*ignore\s*"(.*)"/i).flatten
end

#markdown_parserObject



30
31
32
# File 'lib/danger/request_sources/github.rb', line 30

def markdown_parser
  @markdown_parser ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML, no_intra_emphasis: true)
end

#parse_comment(comment) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/danger/request_sources/github.rb', line 193

def parse_comment(comment)
  tables = parse_tables_from_comment(comment)
  violations = {}
  tables.each do |table|
    next unless table =~ %r{<th width="100%"(.*?)</th>}im
    title = Regexp.last_match(1)
    kind = table_kind_from_title(title)
    next unless kind

    violations[kind] = violations_from_table(table)
  end

  violations.reject { |_, v| v.empty? }
end

#parse_tables_from_comment(comment) ⇒ Object



223
224
225
# File 'lib/danger/request_sources/github.rb', line 223

def parse_tables_from_comment(comment)
  comment.split('</table>')
end

#pr_authorObject



71
72
73
# File 'lib/danger/request_sources/github.rb', line 71

def pr_author
  self.pr_json[:user][:login].to_s
end

#pr_bodyObject



67
68
69
# File 'lib/danger/request_sources/github.rb', line 67

def pr_body
  self.pr_json[:body].to_s
end

#pr_labelsObject



75
76
77
# File 'lib/danger/request_sources/github.rb', line 75

def pr_labels
  self.issue_json[:labels].map { |l| l[:name] }
end

#pr_titleObject



63
64
65
# File 'lib/danger/request_sources/github.rb', line 63

def pr_title
  self.pr_json[:title].to_s
end

#process_markdown(violation) ⇒ Object



227
228
229
230
231
232
# File 'lib/danger/request_sources/github.rb', line 227

def process_markdown(violation)
  html = markdown_parser.render(violation.message)
  match = html.match(%r{^<p>(.*)</p>$})
  message = match.nil? ? html : match.captures.first
  Violation.new(message, violation.sticky)
end

#random_complimentObject



148
149
150
151
152
# File 'lib/danger/request_sources/github.rb', line 148

def random_compliment
  compliment = ["Well done.", "Congrats.", "Woo!",
                "Yay.", "Jolly good show.", "Good on 'ya.", "Nice work."]
  compliment.sample
end

#submit_pull_request_status!(warnings: nil, errors: nil, details_url: nil) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/danger/request_sources/github.rb', line 118

def submit_pull_request_status!(warnings: nil, errors: nil, details_url: nil)
  status = (errors.count == 0 ? 'success' : 'failure')
  message = generate_github_description(warnings: warnings, errors: errors)
  client.create_status(ci_source.repo_slug, latest_pr_commit_ref, status, {
    description: message,
    context: "danger/danger",
    target_url: details_url
  })
rescue
  # This usually means the user has no commit access to this repo
  # That's always the case for open source projects where you can only
  # use a read-only GitHub account
  if errors.count > 0
    # We need to fail the actual build here
    abort("\nDanger has failed this build. \nFound #{errors.count} error(s) and I don't have write access to the PR set a PR status.")
  else
    puts message
  end
end

#table(name, emoji, violations, all_previous_violations) ⇒ Object



183
184
185
186
187
188
189
190
191
# File 'lib/danger/request_sources/github.rb', line 183

def table(name, emoji, violations, all_previous_violations)
  content = violations.map { |v| process_markdown(v) }
  kind = table_kind_from_title(name)
  previous_violations = all_previous_violations[kind] || []
  messages = content.map(&:message)
  resolved_violations = previous_violations.reject { |s| messages.include? s }
  count = content.count
  { name: name, emoji: emoji, content: content, resolved: resolved_violations, count: count }
end

#table_kind_from_title(title) ⇒ Object



213
214
215
216
217
218
219
220
221
# File 'lib/danger/request_sources/github.rb', line 213

def table_kind_from_title(title)
  if title =~ /error/i
    :error
  elsif title =~ /warning/i
    :warning
  elsif title =~ /message/i
    :message
  end
end

#update_pull_request!(warnings: [], errors: [], messages: [], markdowns: []) ⇒ Object

Sending data to GitHub



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/danger/request_sources/github.rb', line 80

def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [])
  comment_result = {}

  issues = client.issue_comments(ci_source.repo_slug, ci_source.pull_request_id)
  editable_issues = issues.reject { |issue| issue[:body].include?("generated_by_danger") == false }

  if editable_issues.empty?
    previous_violations = {}
  else
    comment = editable_issues.first[:body]
    previous_violations = parse_comment(comment)
  end

  if previous_violations.empty? && (warnings + errors + messages + markdowns).empty?
    # Just remove the comment, if there's nothing to say.
    delete_old_comments!
  else
    body = generate_comment(warnings: warnings,
                              errors: errors,
                            messages: messages,
                           markdowns: markdowns,
                 previous_violations: previous_violations)

    if editable_issues.empty?
      comment_result = client.add_comment(ci_source.repo_slug, ci_source.pull_request_id, body)
    else
      original_id = editable_issues.first[:id]
      comment_result = client.update_comment(ci_source.repo_slug, original_id, body)
    end
  end

  # Now, set the pull request status.
  # Note: this can terminate the entire process.
  submit_pull_request_status!(warnings: warnings,
                                errors: errors,
                           details_url: comment_result['html_url'])
end

#violations_from_table(table) ⇒ Object



208
209
210
211
# File 'lib/danger/request_sources/github.rb', line 208

def violations_from_table(table)
  regex = %r{<td data-sticky="true">(?:<del>)?(.*?)(?:</del>)?\s*</td>}im
  table.scan(regex).flatten.map(&:strip)
end