danger 3.4.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/lib/danger/ci_source/jenkins.rb +32 -1
  4. data/lib/danger/ci_source/local_git_repo.rb +79 -21
  5. data/lib/danger/ci_source/support/commits.rb +17 -0
  6. data/lib/danger/ci_source/support/find_repo_info_from_logs.rb +35 -0
  7. data/lib/danger/ci_source/support/find_repo_info_from_url.rb +27 -0
  8. data/lib/danger/ci_source/support/local_pull_request.rb +20 -0
  9. data/lib/danger/ci_source/support/no_pull_request.rb +7 -0
  10. data/lib/danger/ci_source/support/no_repo_info.rb +5 -0
  11. data/lib/danger/ci_source/support/{merged_pull_request_finder.rb → pull_request_finder.rb} +71 -44
  12. data/lib/danger/ci_source/support/remote_pull_request.rb +15 -0
  13. data/lib/danger/ci_source/support/repo_info.rb +10 -0
  14. data/lib/danger/commands/dangerfile/gem.rb +43 -0
  15. data/lib/danger/commands/dangerfile/init.rb +30 -0
  16. data/lib/danger/commands/init.rb +2 -2
  17. data/lib/danger/commands/pr.rb +119 -0
  18. data/lib/danger/commands/runner.rb +8 -1
  19. data/lib/danger/commands/systems.rb +6 -4
  20. data/lib/danger/danger_core/dangerfile.rb +11 -7
  21. data/lib/danger/danger_core/dangerfile_generator.rb +11 -0
  22. data/lib/danger/danger_core/environment_manager.rb +1 -1
  23. data/lib/danger/danger_core/executor.rb +3 -1
  24. data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +4 -4
  25. data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +14 -13
  26. data/lib/danger/request_sources/github.rb +17 -14
  27. data/lib/danger/request_sources/gitlab.rb +26 -12
  28. data/lib/danger/request_sources/support/get_ignored_violation.rb +17 -0
  29. data/lib/danger/scm_source/git_repo.rb +45 -23
  30. data/lib/danger/version.rb +1 -1
  31. metadata +27 -28
  32. data/lib/assets/PluginTemplate.rb.template +0 -20
  33. data/lib/danger/ci_source/support/remote_finder.rb +0 -26
@@ -0,0 +1,119 @@
1
+ require "danger/commands/local_helpers/http_cache"
2
+ require "faraday/http_cache"
3
+ require "fileutils"
4
+ require "octokit"
5
+ require "tmpdir"
6
+
7
+ module Danger
8
+ class PR < Runner
9
+ self.summary = "Run the Dangerfile against Pull Requests (works with forks, too).".freeze
10
+ self.command = "pr".freeze
11
+
12
+ def self.options
13
+ [
14
+ ["--clear-http-cache", "Clear the local http cache before running Danger locally."],
15
+ ["--pry", "Drop into a Pry shell after evaluating the Dangerfile."],
16
+ ["--dangerfile=<path/to/dangerfile>", "The location of your Dangerfile"]
17
+ ]
18
+ end
19
+
20
+ def initialize(argv)
21
+ @pr_url = argv.shift_argument
22
+ @clear_http_cache = argv.flag?("clear-http-cache", false)
23
+ dangerfile = argv.option("dangerfile", "Dangerfile")
24
+
25
+ super
26
+
27
+ @dangerfile_path = dangerfile if File.exist?(dangerfile)
28
+
29
+ setup_pry if should_pry?(argv)
30
+ end
31
+
32
+ def should_pry?(argv)
33
+ argv.flag?("pry", false) && !@dangerfile_path.empty? && validate_pry_available
34
+ end
35
+
36
+ def setup_pry
37
+ File.delete "_Dangerfile.tmp" if File.exist? "_Dangerfile.tmp"
38
+ FileUtils.cp @dangerfile_path, "_Dangerfile.tmp"
39
+ File.open("_Dangerfile.tmp", "a") do |f|
40
+ f.write("binding.pry; File.delete(\"_Dangerfile.tmp\")")
41
+ end
42
+ @dangerfile_path = "_Dangerfile.tmp"
43
+ end
44
+
45
+ def validate_pry_available
46
+ require "pry"
47
+ rescue LoadError
48
+ cork.warn "Pry was not found, and is required for 'danger pr --pry'."
49
+ cork.print_warnings
50
+ abort
51
+ end
52
+
53
+ def validate!
54
+ super
55
+ unless @dangerfile_path
56
+ help! "Could not find a Dangerfile."
57
+ end
58
+ end
59
+
60
+ def run
61
+ ENV["DANGER_USE_LOCAL_GIT"] = "YES"
62
+ ENV["LOCAL_GIT_PR_URL"] = @pr_url if @pr_url
63
+
64
+ # setup caching for Github calls to hitting the API rate limit too quickly
65
+ cache_file = File.join(ENV["DANGER_TMPDIR"] || Dir.tmpdir, "danger_local_cache")
66
+ cache = HTTPCache.new(cache_file, clear_cache: @clear_http_cache)
67
+ Octokit.middleware = Faraday::RackBuilder.new do |builder|
68
+ builder.use Faraday::HttpCache, store: cache, serializer: Marshal, shared_cache: false
69
+ builder.use Octokit::Response::RaiseError
70
+ builder.adapter Faraday.default_adapter
71
+ end
72
+
73
+ env = EnvironmentManager.new(ENV, cork)
74
+ dm = Dangerfile.new(env, cork)
75
+ dm.init_plugins
76
+
77
+ source = dm.env.ci_source
78
+ if source.nil? or source.repo_slug.empty?
79
+ cork.puts "danger pr failed because it only works with GitHub projects at the moment. Sorry.".red
80
+ exit 0
81
+ end
82
+
83
+ gh = dm.env.request_source
84
+
85
+ cork.puts "Running your Dangerfile against this PR - https://#{gh.host}/#{source.repo_slug}/pull/#{source.pull_request_id}"
86
+
87
+ if verbose != true
88
+ cork.puts "Turning on --verbose"
89
+ dm.verbose = true
90
+ end
91
+
92
+ cork.puts
93
+
94
+ # We can use tokenless here, as it's running on someone's computer
95
+ # and is IP locked, as opposed to on the CI.
96
+ gh.support_tokenless_auth = true
97
+
98
+ begin
99
+ gh.fetch_details
100
+ rescue Octokit::NotFound
101
+ cork.puts "Local repository was not found on GitHub. If you're trying to test a private repository please provide a valid API token through " + "DANGER_GITHUB_API_TOKEN".yellow + " environment variable."
102
+ return
103
+ end
104
+
105
+ dm.env.request_source = gh
106
+
107
+ begin
108
+ dm.env.fill_environment_vars
109
+ dm.env.ensure_danger_branches_are_setup
110
+ dm.env.scm.diff_for_folder(".", from: Danger::EnvironmentManager.danger_base_branch, to: Danger::EnvironmentManager.danger_head_branch)
111
+
112
+ dm.parse(Pathname.new(@dangerfile_path))
113
+ dm.print_results
114
+ ensure
115
+ dm.env.clean_up
116
+ end
117
+ end
118
+ end
119
+ end
@@ -3,6 +3,7 @@ module Danger
3
3
  require "danger/commands/init"
4
4
  require "danger/commands/local"
5
5
  require "danger/commands/systems"
6
+ require "danger/commands/pr"
6
7
 
7
8
  # manually set claide plugins as a subcommand
8
9
  require "claide_plugin"
@@ -19,6 +20,9 @@ module Danger
19
20
  require "danger/commands/plugins/plugin_json"
20
21
  require "danger/commands/plugins/plugin_readme"
21
22
 
23
+ require "danger/commands/dangerfile/init"
24
+ require "danger/commands/dangerfile/gem"
25
+
22
26
  attr_accessor :cork
23
27
 
24
28
  self.summary = "Run the Dangerfile."
@@ -33,6 +37,7 @@ module Danger
33
37
  @base = argv.option("base")
34
38
  @head = argv.option("head")
35
39
  @fail_on_errors = argv.option("fail-on-errors", false)
40
+ @new_comment = argv.flag?("new-comment")
36
41
  @danger_id = argv.option("danger_id", "danger")
37
42
  @cork = Cork::Board.new(silent: argv.option("silent", false),
38
43
  verbose: argv.option("verbose", false))
@@ -52,7 +57,8 @@ module Danger
52
57
  ["--head=[master|dev|stable]", "A branch/tag/commit to use as the head"],
53
58
  ["--fail-on-errors=<true|false>", "Should always fail the build process, defaults to false"],
54
59
  ["--dangerfile=<path/to/dangerfile>", "The location of your Dangerfile"],
55
- ["--danger_id=<id>", "The identifier of this Danger instance"]
60
+ ["--danger_id=<id>", "The identifier of this Danger instance"],
61
+ ["--new-comment", "Makes Danger post a new comment instead of editing it's previous one"]
56
62
  ].concat(super)
57
63
  end
58
64
 
@@ -62,6 +68,7 @@ module Danger
62
68
  head: @head,
63
69
  dangerfile_path: @dangerfile_path,
64
70
  danger_id: @danger_id,
71
+ new_comment: @new_comment,
65
72
  fail_on_errors: @fail_on_errors
66
73
  )
67
74
  end
@@ -16,10 +16,12 @@ module Danger
16
16
  require "yard"
17
17
  # Pull out all the Danger::CI subclasses docs
18
18
  registry = YARD::Registry.load(ci_source_paths, true)
19
- ci_sources = registry.all(:class)
20
- .select { |klass| klass.inheritance_tree.map(&:name).include? :CI }
21
- .reject { |source| source.name == :CI }
22
- .reject { |source| source.name == :LocalGitRepo }
19
+ ci_sources = begin
20
+ registry.all(:class)
21
+ .select { |klass| klass.inheritance_tree.map(&:name).include? :CI }
22
+ .reject { |source| source.name == :CI }
23
+ .reject { |source| source.name == :LocalGitRepo }
24
+ end
23
25
 
24
26
  # Fail if anything is added and not documented
25
27
  cis_without_docs = ci_sources.select { |source| source.base_docstring.empty? }
@@ -122,12 +122,15 @@ module Danger
122
122
  when :api
123
123
  value = "Octokit::Client"
124
124
 
125
- when :pr_json
126
- value = "[Skipped]"
125
+ when :pr_json, :mr_json
126
+ value = "[Skipped JSON]"
127
+
128
+ when :pr_diff, :mr_diff
129
+ value = "[Skipped Diff]"
127
130
 
128
131
  else
129
132
  value = plugin.send(method)
130
- value = wrap_text(value) if value.kind_of?(String)
133
+ value = wrap_text(value.encode("utf-8")) if value.kind_of?(String)
131
134
  # So that we either have one value per row
132
135
  # or we have [] for an empty array
133
136
  value = value.join("\n") if value.kind_of?(Array) && value.count > 0
@@ -238,7 +241,7 @@ module Danger
238
241
  violation_report[:errors].count > 0
239
242
  end
240
243
 
241
- def post_results(danger_id)
244
+ def post_results(danger_id, new_comment)
242
245
  violations = violation_report
243
246
 
244
247
  env.request_source.update_pull_request!(
@@ -246,7 +249,8 @@ module Danger
246
249
  errors: violations[:errors],
247
250
  messages: violations[:messages],
248
251
  markdowns: status_report[:markdowns],
249
- danger_id: danger_id
252
+ danger_id: danger_id,
253
+ new_comment: new_comment
250
254
  )
251
255
  end
252
256
 
@@ -255,7 +259,7 @@ module Danger
255
259
  env.scm.diff_for_folder(".".freeze, from: base_branch, to: head_branch)
256
260
  end
257
261
 
258
- def run(base_branch, head_branch, dangerfile_path, danger_id)
262
+ def run(base_branch, head_branch, dangerfile_path, danger_id, new_comment)
259
263
  # Setup internal state
260
264
  init_plugins
261
265
  env.fill_environment_vars
@@ -270,7 +274,7 @@ module Danger
270
274
  # Push results to the API
271
275
  # Pass along the details of the run to the request source
272
276
  # to send back to the code review site.
273
- post_results(danger_id)
277
+ post_results(danger_id, new_comment)
274
278
 
275
279
  # Print results in the terminal
276
280
  print_results
@@ -0,0 +1,11 @@
1
+ module Danger
2
+ class DangerfileGenerator
3
+ # returns the string for a Dangerfile based on a folder's contents'
4
+ def self.create_dangerfile(_path, _ui)
5
+ # Use this template for now, but this is a really ripe place to
6
+ # improve now!
7
+ dir = Danger.gem_path
8
+ File.read(File.join(dir, "lib", "assets", "DangerfileTemplate"))
9
+ end
10
+ end
11
+ end
@@ -39,7 +39,7 @@ module Danger
39
39
  self.request_source = request_source
40
40
  end
41
41
 
42
- raise_error_for_no_request_source(env, ui) unless self.request_source
42
+ raise_error_for_no_request_source(env, self.ui) unless self.request_source
43
43
  self.scm = self.request_source.scm
44
44
  end
45
45
 
@@ -11,6 +11,7 @@ module Danger
11
11
  head: nil,
12
12
  dangerfile_path: nil,
13
13
  danger_id: nil,
14
+ new_comment: nil,
14
15
  fail_on_errors: nil)
15
16
  # Create a silent Cork instance if cork is nil, as it's likely a test
16
17
  cork ||= Cork::Board.new(silent: false, verbose: false)
@@ -27,7 +28,8 @@ module Danger
27
28
  base_branch(base),
28
29
  head_branch(head),
29
30
  dangerfile_path,
30
- danger_id
31
+ danger_id,
32
+ new_comment
31
33
  )
32
34
  end
33
35
 
@@ -28,15 +28,15 @@ module Danger
28
28
  #
29
29
  # fail "Please re-submit this MR to develop, we may have already fixed your issue." if gitlab.branch_for_merge != "develop"
30
30
  #
31
- # @example Note when MRs don't reference a milestone, which goes away when it does.
31
+ # @example Note when MRs don't reference a milestone, make the warning stick around on subsequent runs
32
32
  #
33
33
  # has_milestone = gitlab.mr_json["milestone"] != nil
34
- # warn("This MR does not refer to an existing milestone", sticky: false) unless has_milestone
34
+ # warn("This MR does not refer to an existing milestone", sticky: true) unless has_milestone
35
35
  #
36
- # @example Note when a MR cannot be manually merged, which goes away when you can.
36
+ # @example Note when a MR cannot be manually merged
37
37
  #
38
38
  # can_merge = gitlab.mr_json["mergeable"]
39
- # warn("This MR cannot be merged yet.", sticky: false) unless can_merge
39
+ # warn("This MR cannot be merged yet.") unless can_merge
40
40
  #
41
41
  # @example Highlight when a celebrity makes a merge request.
42
42
  #
@@ -8,14 +8,15 @@ module Danger
8
8
  #
9
9
  # The message within which Danger communicates back is amended on each run in a session.
10
10
  #
11
- # Each of `message`, `warn` and `fail` have a `sticky` flag, `true` by default, which
12
- # means that the message will be crossed out instead of being removed. If it's not use on
13
- # subsequent runs.
11
+ # Each of `message`, `warn` and `fail` have a `sticky` flag, `false` by default, which
12
+ # when `true` means that the message will be crossed out instead of being removed.
13
+ # If it's not called again on subsequent runs.
14
14
  #
15
15
  # By default, using `fail` would fail the corresponding build. Either via an API call, or
16
- # via the return value for the danger command.
16
+ # via the return value for the danger command. If you have linters with errors for this call
17
+ # you can use `messaging.fail` instead.
17
18
  #
18
- # It is possible to have Danger ignore specific warnings or errors by writing `Danger: Ignore "[warning/error text]`.
19
+ # It is possible to have Danger ignore specific warnings or errors by writing `Danger: Ignore "[warning/error text]"`.
19
20
  #
20
21
  # Sidenote: Messaging is the only plugin which adds functions to the root of the Dangerfile.
21
22
  #
@@ -23,9 +24,9 @@ module Danger
23
24
  #
24
25
  # fail "This build didn't pass tests"
25
26
  #
26
- # @example Failing a build, but not keeping its value around on subsequent runs
27
+ # @example Failing a build, and note that on subsequent runs
27
28
  #
28
- # fail("This build didn't pass tests", sticky: false)
29
+ # fail("This build didn't pass tests", sticky: true)
29
30
  #
30
31
  # @example Passing a warning
31
32
  #
@@ -87,14 +88,14 @@ module Danger
87
88
  # The message to present to the user
88
89
  # @param [Boolean] sticky
89
90
  # Whether the message should be kept after it was fixed,
90
- # defaults to `true`.
91
+ # defaults to `false`.
91
92
  # @param [String] file
92
93
  # Optional. Path to the file that the message is for.
93
94
  # @param [String] line
94
95
  # Optional. The line in the file to present the message in.
95
96
  # @return [void]
96
97
  #
97
- def message(message, sticky: true, file: nil, line: nil)
98
+ def message(message, sticky: false, file: nil, line: nil)
98
99
  @messages << Violation.new(message, sticky, file, line)
99
100
  end
100
101
 
@@ -105,14 +106,14 @@ module Danger
105
106
  # The message to present to the user
106
107
  # @param [Boolean] sticky
107
108
  # Whether the message should be kept after it was fixed,
108
- # defaults to `true`.
109
+ # defaults to `false`.
109
110
  # @param [String] file
110
111
  # Optional. Path to the file that the message is for.
111
112
  # @param [String] line
112
113
  # Optional. The line in the file to present the message in.
113
114
  # @return [void]
114
115
  #
115
- def warn(message, sticky: true, file: nil, line: nil)
116
+ def warn(message, sticky: false, file: nil, line: nil)
116
117
  return if should_ignore_violation(message)
117
118
  @warnings << Violation.new(message, sticky, file, line)
118
119
  end
@@ -124,14 +125,14 @@ module Danger
124
125
  # The message to present to the user
125
126
  # @param [Boolean] sticky
126
127
  # Whether the message should be kept after it was fixed,
127
- # defaults to `true`.
128
+ # defaults to `false`.
128
129
  # @param [String] file
129
130
  # Optional. Path to the file that the message is for.
130
131
  # @param [String] line
131
132
  # Optional. The line in the file to present the message in.
132
133
  # @return [void]
133
134
  #
134
- def fail(message, sticky: true, file: nil, line: nil)
135
+ def fail(message, sticky: false, file: nil, line: nil)
135
136
  return if should_ignore_violation(message)
136
137
  @errors << Violation.new(message, sticky, file, line)
137
138
  end
@@ -3,6 +3,8 @@ require "octokit"
3
3
  require "danger/helpers/comments_helper"
4
4
  require "danger/helpers/comment"
5
5
 
6
+ require "danger/request_sources/support/get_ignored_violation"
7
+
6
8
  module Danger
7
9
  module RequestSources
8
10
  class GitHub < RequestSource
@@ -83,13 +85,11 @@ module Danger
83
85
  end
84
86
 
85
87
  fetch_issue_details(self.pr_json)
86
- self.ignored_violations = ignored_violations_from_pr(self.pr_json)
88
+ self.ignored_violations = ignored_violations_from_pr
87
89
  end
88
90
 
89
- def ignored_violations_from_pr(pr_json)
90
- pr_body = pr_json["body"]
91
- return [] if pr_body.nil?
92
- pr_body.chomp.scan(/>\s*danger\s*:\s*ignore\s*"(.*)"/i).flatten
91
+ def ignored_violations_from_pr
92
+ GetIgnoredViolation.new(self.pr_json["body"]).call
93
93
  end
94
94
 
95
95
  def fetch_issue_details(pr_json)
@@ -98,20 +98,23 @@ module Danger
98
98
  end
99
99
 
100
100
  def issue_comments
101
- @comments ||= client.issue_comments(ci_source.repo_slug, ci_source.pull_request_id)
102
- .map { |comment| Comment.from_github(comment) }
101
+ @comments ||= begin
102
+ client.issue_comments(ci_source.repo_slug, ci_source.pull_request_id)
103
+ .map { |comment| Comment.from_github(comment) }
104
+ end
103
105
  end
104
106
 
105
107
  # Sending data to GitHub
106
- def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger")
108
+ def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false)
107
109
  comment_result = {}
108
110
  editable_comments = issue_comments.select { |comment| comment.generated_by_danger?(danger_id) }
111
+ last_comment = editable_comments.last
112
+ should_create_new_comment = new_comment || last_comment.nil?
109
113
 
110
- if editable_comments.empty?
114
+ if should_create_new_comment
111
115
  previous_violations = {}
112
116
  else
113
- comment = editable_comments.first.body
114
- previous_violations = parse_comment(comment)
117
+ previous_violations = parse_comment(last_comment.body)
115
118
  end
116
119
 
117
120
  main_violations = (warnings + errors + messages + markdowns).reject(&:inline?)
@@ -152,11 +155,10 @@ module Danger
152
155
  danger_id: danger_id,
153
156
  template: "github")
154
157
 
155
- if editable_comments.empty?
158
+ if should_create_new_comment
156
159
  comment_result = client.add_comment(ci_source.repo_slug, ci_source.pull_request_id, body)
157
160
  else
158
- original_id = editable_comments.first.id
159
- comment_result = client.update_comment(ci_source.repo_slug, original_id, body)
161
+ comment_result = client.update_comment(ci_source.repo_slug, last_comment.id, body)
160
162
  end
161
163
  end
162
164
 
@@ -197,6 +199,7 @@ module Danger
197
199
  end
198
200
  else
199
201
  puts message
202
+ puts "\nDanger does not have write access to the PR to set a PR status.".yellow
200
203
  end
201
204
  end
202
205
  end
@@ -1,8 +1,9 @@
1
1
  # coding: utf-8
2
- require "gitlab"
3
2
  require "danger/helpers/comments_helper"
4
3
  require "danger/helpers/comment"
5
4
 
5
+ require "danger/request_sources/support/get_ignored_violation"
6
+
6
7
  module Danger
7
8
  module RequestSources
8
9
  class GitLab < RequestSource
@@ -27,10 +28,19 @@ module Danger
27
28
  def client
28
29
  token = @environment["DANGER_GITLAB_API_TOKEN"]
29
30
  raise "No API token given, please provide one using `DANGER_GITLAB_API_TOKEN`" unless token
31
+
32
+ # The require happens inline so that it won't cause exceptions when just using the `danger` gem.
33
+ require "gitlab"
34
+
30
35
  params = { private_token: token }
31
36
  params[:endpoint] = endpoint
32
37
 
33
38
  @client ||= Gitlab.client(params)
39
+
40
+ rescue LoadError
41
+ puts "The GitLab gem was not installed, you will need to change your Gem from `danger` to `danger-gitlab`.".red
42
+ puts "\n - See https://github.com/danger/danger/blob/master/CHANGELOG.md#400"
43
+ abort
34
44
  end
35
45
 
36
46
  def validates_as_api_source?
@@ -55,13 +65,17 @@ module Danger
55
65
  end
56
66
 
57
67
  def mr_comments
58
- @comments ||= client.merge_request_comments(escaped_ci_slug, ci_source.pull_request_id)
59
- .map { |comment| Comment.from_gitlab(comment) }
68
+ @comments ||= begin
69
+ client.merge_request_comments(escaped_ci_slug, ci_source.pull_request_id)
70
+ .map { |comment| Comment.from_gitlab(comment) }
71
+ end
60
72
  end
61
73
 
62
74
  def mr_diff
63
- @mr_diff ||= client.merge_request_changes(escaped_ci_slug, ci_source.pull_request_id)
64
- .changes.map { |change| change["diff"] }.join("\n")
75
+ @mr_diff ||= begin
76
+ client.merge_request_changes(escaped_ci_slug, ci_source.pull_request_id)
77
+ .changes.map { |change| change["diff"] }.join("\n")
78
+ end
65
79
  end
66
80
 
67
81
  def escaped_ci_slug
@@ -84,19 +98,19 @@ module Danger
84
98
  def fetch_details
85
99
  self.mr_json = client.merge_request(escaped_ci_slug, self.ci_source.pull_request_id)
86
100
  self.commits_json = client.merge_request_commits(escaped_ci_slug, self.ci_source.pull_request_id)
87
- self.ignored_violations = ignored_violations_from_pr(self.mr_json)
101
+ self.ignored_violations = ignored_violations_from_pr
88
102
  end
89
103
 
90
- def ignored_violations_from_pr(mr_json)
91
- pr_body = mr_json.description
92
- return [] if pr_body.nil?
93
- pr_body.chomp.scan(/>\s*danger\s*:\s*ignore\s*"(.*)"/i).flatten
104
+ def ignored_violations_from_pr
105
+ GetIgnoredViolation.new(self.mr_json.description).call
94
106
  end
95
107
 
96
- def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger")
108
+ def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false)
97
109
  editable_comments = mr_comments.select { |comment| comment.generated_by_danger?(danger_id) }
98
110
 
99
- if editable_comments.empty?
111
+ should_create_new_comment = new_comment || editable_comments.empty?
112
+
113
+ if should_create_new_comment
100
114
  previous_violations = {}
101
115
  else
102
116
  comment = editable_comments.first.body
@@ -0,0 +1,17 @@
1
+ class GetIgnoredViolation
2
+ IGNORE_REGEXP = />*\s*danger\s*:\s*ignore\s*"(?<error>[^"]*)"/i
3
+
4
+ def initialize(body)
5
+ @body = body
6
+ end
7
+
8
+ def call
9
+ return [] unless body
10
+
11
+ body.chomp.scan(IGNORE_REGEXP).flatten
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :body
17
+ end
@@ -12,10 +12,10 @@ module Danger
12
12
 
13
13
  ensure_commitish_exists!(from)
14
14
  ensure_commitish_exists!(to)
15
- merge_base = repo.merge_base(from, to)
16
15
 
17
- ensure_commitish_exists!(merge_base)
18
- self.diff = repo.diff(merge_base.to_s, to)
16
+ merge_base = find_merge_base(repo, from, to)
17
+
18
+ self.diff = repo.diff(merge_base, to)
19
19
  self.log = repo.log.between(from, to)
20
20
  end
21
21
 
@@ -29,7 +29,7 @@ module Danger
29
29
  end
30
30
 
31
31
  def head_commit
32
- exec "rev-parse HEAD"
32
+ exec("rev-parse HEAD")
33
33
  end
34
34
 
35
35
  def origins
@@ -37,38 +37,60 @@ module Danger
37
37
  end
38
38
 
39
39
  def ensure_commitish_exists!(commitish)
40
- exec("fetch") if exec("rev-parse --quiet --verify \"#{commitish}^{commit}\"").empty?
40
+ git_shallow_fetch if commit_not_exists?(commitish)
41
+
42
+ if commit_not_exists?(commitish)
43
+ raise_if_we_cannot_find_the_commit(commitish)
44
+ end
41
45
  end
42
46
 
43
47
  private
44
48
 
49
+ def git_shallow_fetch
50
+ exec("fetch --unshallow")
51
+ end
52
+
45
53
  def default_env
46
54
  { "LANG" => "en_US.UTF-8" }
47
55
  end
48
- end
49
- end
50
56
 
51
- # For full context see:
52
- # https://github.com/danger/danger/issues/160
53
- # and https://github.com/danger/danger/issues/316
54
- #
55
- # for which the fix comes from an unmerged PR from 2012
56
- # https://github.com/schacon/ruby-git/pull/43
57
+ def raise_if_we_cannot_find_the_commit(commitish)
58
+ raise "Commit #{commitish[0..7]} doesn't exist. Are you running `danger local/pr` against the correct repository? Also this usually happens when you rebase/reset and force-pushed."
59
+ end
57
60
 
58
- module Git
59
- class Base
60
- def merge_base(commit1, commit2, *other_commits)
61
- Git::Object.new self, self.lib.merge_base(commit1, commit2, *other_commits)
61
+ def commit_exists?(sha1)
62
+ !commit_not_exists?(sha1)
63
+ end
64
+
65
+ def commit_not_exists?(sha1)
66
+ exec("rev-parse --quiet --verify #{sha1}^{commit}").empty?
67
+ end
68
+
69
+ def find_merge_base(repo, from, to)
70
+ possible_merge_base = possible_merge_base(repo, from, to)
71
+
72
+ unless possible_merge_base
73
+ git_shallow_fetch
74
+ possible_merge_base = possible_merge_base(repo, from, to)
75
+ end
76
+
77
+ raise "Cannot find a merge base between #{from} and #{to}." unless possible_merge_base
78
+
79
+ possible_merge_base
80
+ end
81
+
82
+ def possible_merge_base(repo, from, to)
83
+ [repo.merge_base(from, to)].find { |base| commit_exists?(base) }
62
84
  end
63
85
  end
86
+ end
64
87
 
65
- class Lib
88
+ module Git
89
+ class Base
90
+ # Use git-merge-base https://git-scm.com/docs/git-merge-base to
91
+ # find as good common ancestors as possible for a merge
66
92
  def merge_base(commit1, commit2, *other_commits)
67
- arr_opts = []
68
- arr_opts << commit1
69
- arr_opts << commit2
70
- arr_opts += other_commits
71
- command("merge-base", arr_opts)
93
+ Open3.popen2("git", "merge-base", "--all", commit1, commit2, *other_commits) { |_stdin, stdout, _wait_thr| stdout.read.rstrip }
72
94
  end
73
95
  end
74
96
  end
@@ -1,4 +1,4 @@
1
1
  module Danger
2
- VERSION = "3.4.2".freeze
2
+ VERSION = "4.0.0".freeze
3
3
  DESCRIPTION = "Like Unit Tests, but for your Team Culture.".freeze
4
4
  end