Class: Danger::RequestSources::GitHub

Inherits:
RequestSource show all
Defined in:
lib/danger/request_source/github.rb

Instance Attribute Summary collapse

Attributes inherited from RequestSource

#ci_source, #environment, #ignored_violations

Instance Method Summary collapse

Methods inherited from RequestSource

available_request_sources, inherited, #validates?

Constructor Details

#initialize(ci_source, environment) ⇒ GitHub

Returns a new instance of GitHub.



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

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"]
  if @environment["DANGER_GITHUB_API_HOST"]
    Octokit.api_endpoint = @environment["DANGER_GITHUB_API_HOST"]
  end
end

Instance Attribute Details

#issue_jsonObject

Returns the value of attribute issue_json.



8
9
10
# File 'lib/danger/request_source/github.rb', line 8

def issue_json
  @issue_json
end

#pr_jsonObject

Returns the value of attribute pr_json.



8
9
10
# File 'lib/danger/request_source/github.rb', line 8

def pr_json
  @pr_json
end

#support_tokenless_authObject

Returns the value of attribute support_tokenless_auth.



8
9
10
# File 'lib/danger/request_source/github.rb', line 8

def support_tokenless_auth
  @support_tokenless_auth
end

Instance Method Details

#clientObject



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

def client
  raise "No API token 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, danger_id: 'danger') ⇒ Object

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



130
131
132
133
134
135
136
137
# File 'lib/danger/request_source/github.rb', line 130

def delete_old_comments!(except: nil, danger_id: 'danger')
  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_id}")
    next if issue[:id] == except
    client.delete_comment(ci_source.repo_slug, issue[:id])
  end
end

#fetch_detailsObject



52
53
54
55
56
# File 'lib/danger/request_source/github.rb', line 52

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



64
65
66
67
# File 'lib/danger/request_source/github.rb', line 64

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: {}, danger_id: 'danger') ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/danger/request_source/github.rb', line 157

def generate_comment(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: {}, danger_id: 'danger')
  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
  @danger_id = danger_id

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

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



145
146
147
148
149
150
151
152
153
154
155
# File 'lib/danger/request_source/github.rb', line 145

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

#hostObject



26
27
28
# File 'lib/danger/request_source/github.rb', line 26

def host
  @host = @environment["DANGER_GITHUB_HOST"] || "github.com"
end

#ignored_violations_from_pr(pr_json) ⇒ Object



58
59
60
61
62
# File 'lib/danger/request_source/github.rb', line 58

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



35
36
37
# File 'lib/danger/request_source/github.rb', line 35

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

#parse_comment(comment) ⇒ Object



185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/danger/request_source/github.rb', line 185

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



215
216
217
# File 'lib/danger/request_source/github.rb', line 215

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

#process_markdown(violation) ⇒ Object



219
220
221
222
223
224
# File 'lib/danger/request_source/github.rb', line 219

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



139
140
141
142
143
# File 'lib/danger/request_source/github.rb', line 139

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

#scmObject



22
23
24
# File 'lib/danger/request_source/github.rb', line 22

def scm
  @scm ||= GitRepo.new
end

#setup_danger_branchesObject



39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/danger/request_source/github.rb', line 39

def setup_danger_branches
  # we can use a github specific feature here:
  pull_id = self.ci_source.pull_request_id
  test_branch = self.pr_json[:base][:sha]

  # Next, we want to ensure that we have a version of the current branch at a known location
  self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{test_branch}"

  # OK, so we want to ensure that we have a known head branch, this will always represent
  # the head of the PR ( e.g. the most recent commit that will be merged. )
  self.scm.exec "fetch origin +refs/pull/#{pull_id}/merge:#{EnvironmentManager.danger_head_branch}"
end

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



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/danger/request_source/github.rb', line 109

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 #{'error'.danger_pluralize(errors.count)} 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



175
176
177
178
179
180
181
182
183
# File 'lib/danger/request_source/github.rb', line 175

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

#table_kind_from_title(title) ⇒ Object



205
206
207
208
209
210
211
212
213
# File 'lib/danger/request_source/github.rb', line 205

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: [], danger_id: 'danger') ⇒ Object

Sending data to GitHub



70
71
72
73
74
75
76
77
78
79
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
# File 'lib/danger/request_source/github.rb', line 70

def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: 'danger')
  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_id}") == 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!(danger_id: danger_id)
  else
    body = generate_comment(warnings: warnings,
                              errors: errors,
                            messages: messages,
                           markdowns: markdowns,
                 previous_violations: previous_violations,
                           danger_id: danger_id)

    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



200
201
202
203
# File 'lib/danger/request_source/github.rb', line 200

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