danger 8.0.4

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 (121) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +94 -0
  4. data/bin/danger +5 -0
  5. data/lib/assets/DangerfileTemplate +13 -0
  6. data/lib/danger.rb +44 -0
  7. data/lib/danger/ci_source/appcenter.rb +55 -0
  8. data/lib/danger/ci_source/appveyor.rb +60 -0
  9. data/lib/danger/ci_source/azure_pipelines.rb +44 -0
  10. data/lib/danger/ci_source/bamboo.rb +41 -0
  11. data/lib/danger/ci_source/bitbucket_pipelines.rb +37 -0
  12. data/lib/danger/ci_source/bitrise.rb +65 -0
  13. data/lib/danger/ci_source/buddybuild.rb +62 -0
  14. data/lib/danger/ci_source/buildkite.rb +51 -0
  15. data/lib/danger/ci_source/ci_source.rb +37 -0
  16. data/lib/danger/ci_source/circle.rb +94 -0
  17. data/lib/danger/ci_source/circle_api.rb +51 -0
  18. data/lib/danger/ci_source/cirrus.rb +31 -0
  19. data/lib/danger/ci_source/code_build.rb +57 -0
  20. data/lib/danger/ci_source/codefresh.rb +53 -0
  21. data/lib/danger/ci_source/codeship.rb +44 -0
  22. data/lib/danger/ci_source/dotci.rb +52 -0
  23. data/lib/danger/ci_source/drone.rb +71 -0
  24. data/lib/danger/ci_source/github_actions.rb +43 -0
  25. data/lib/danger/ci_source/gitlab_ci.rb +86 -0
  26. data/lib/danger/ci_source/jenkins.rb +149 -0
  27. data/lib/danger/ci_source/local_git_repo.rb +119 -0
  28. data/lib/danger/ci_source/local_only_git_repo.rb +47 -0
  29. data/lib/danger/ci_source/screwdriver.rb +47 -0
  30. data/lib/danger/ci_source/semaphore.rb +37 -0
  31. data/lib/danger/ci_source/support/commits.rb +17 -0
  32. data/lib/danger/ci_source/support/find_repo_info_from_logs.rb +35 -0
  33. data/lib/danger/ci_source/support/find_repo_info_from_url.rb +42 -0
  34. data/lib/danger/ci_source/support/local_pull_request.rb +14 -0
  35. data/lib/danger/ci_source/support/no_pull_request.rb +7 -0
  36. data/lib/danger/ci_source/support/no_repo_info.rb +5 -0
  37. data/lib/danger/ci_source/support/pull_request_finder.rb +179 -0
  38. data/lib/danger/ci_source/support/remote_pull_request.rb +15 -0
  39. data/lib/danger/ci_source/support/repo_info.rb +10 -0
  40. data/lib/danger/ci_source/surf.rb +37 -0
  41. data/lib/danger/ci_source/teamcity.rb +161 -0
  42. data/lib/danger/ci_source/travis.rb +51 -0
  43. data/lib/danger/ci_source/vsts.rb +73 -0
  44. data/lib/danger/ci_source/xcode_server.rb +48 -0
  45. data/lib/danger/clients/rubygems_client.rb +14 -0
  46. data/lib/danger/commands/dangerfile/gem.rb +43 -0
  47. data/lib/danger/commands/dangerfile/init.rb +30 -0
  48. data/lib/danger/commands/dry_run.rb +54 -0
  49. data/lib/danger/commands/init.rb +297 -0
  50. data/lib/danger/commands/init_helpers/interviewer.rb +92 -0
  51. data/lib/danger/commands/local.rb +83 -0
  52. data/lib/danger/commands/local_helpers/http_cache.rb +36 -0
  53. data/lib/danger/commands/local_helpers/local_setup.rb +46 -0
  54. data/lib/danger/commands/local_helpers/pry_setup.rb +31 -0
  55. data/lib/danger/commands/plugins/plugin_json.rb +46 -0
  56. data/lib/danger/commands/plugins/plugin_lint.rb +54 -0
  57. data/lib/danger/commands/plugins/plugin_readme.rb +45 -0
  58. data/lib/danger/commands/pr.rb +92 -0
  59. data/lib/danger/commands/runner.rb +94 -0
  60. data/lib/danger/commands/staging.rb +53 -0
  61. data/lib/danger/commands/systems.rb +43 -0
  62. data/lib/danger/comment_generators/bitbucket_server.md.erb +20 -0
  63. data/lib/danger/comment_generators/bitbucket_server_inline.md.erb +15 -0
  64. data/lib/danger/comment_generators/bitbucket_server_message_group.md.erb +12 -0
  65. data/lib/danger/comment_generators/github.md.erb +55 -0
  66. data/lib/danger/comment_generators/github_inline.md.erb +26 -0
  67. data/lib/danger/comment_generators/gitlab.md.erb +40 -0
  68. data/lib/danger/comment_generators/gitlab_inline.md.erb +26 -0
  69. data/lib/danger/comment_generators/vsts.md.erb +20 -0
  70. data/lib/danger/core_ext/file_list.rb +18 -0
  71. data/lib/danger/core_ext/string.rb +20 -0
  72. data/lib/danger/danger_core/dangerfile.rb +341 -0
  73. data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
  74. data/lib/danger/danger_core/dangerfile_generator.rb +11 -0
  75. data/lib/danger/danger_core/environment_manager.rb +123 -0
  76. data/lib/danger/danger_core/executor.rb +92 -0
  77. data/lib/danger/danger_core/message_aggregator.rb +49 -0
  78. data/lib/danger/danger_core/message_group.rb +68 -0
  79. data/lib/danger/danger_core/messages/base.rb +56 -0
  80. data/lib/danger/danger_core/messages/markdown.rb +42 -0
  81. data/lib/danger/danger_core/messages/violation.rb +54 -0
  82. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +144 -0
  83. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb +211 -0
  84. data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +248 -0
  85. data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +158 -0
  86. data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +254 -0
  87. data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +240 -0
  88. data/lib/danger/danger_core/plugins/dangerfile_local_only_plugin.rb +42 -0
  89. data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +218 -0
  90. data/lib/danger/danger_core/plugins/dangerfile_vsts_plugin.rb +191 -0
  91. data/lib/danger/danger_core/standard_error.rb +143 -0
  92. data/lib/danger/helpers/array_subclass.rb +61 -0
  93. data/lib/danger/helpers/comment.rb +32 -0
  94. data/lib/danger/helpers/comments_helper.rb +178 -0
  95. data/lib/danger/helpers/comments_parsing_helper.rb +70 -0
  96. data/lib/danger/helpers/emoji_mapper.rb +41 -0
  97. data/lib/danger/helpers/find_max_num_violations.rb +31 -0
  98. data/lib/danger/helpers/message_groups_array_helper.rb +31 -0
  99. data/lib/danger/plugin_support/gems_resolver.rb +77 -0
  100. data/lib/danger/plugin_support/plugin.rb +49 -0
  101. data/lib/danger/plugin_support/plugin_file_resolver.rb +30 -0
  102. data/lib/danger/plugin_support/plugin_linter.rb +161 -0
  103. data/lib/danger/plugin_support/plugin_parser.rb +199 -0
  104. data/lib/danger/plugin_support/templates/readme_table.html.erb +26 -0
  105. data/lib/danger/request_sources/bitbucket_cloud.rb +171 -0
  106. data/lib/danger/request_sources/bitbucket_cloud_api.rb +181 -0
  107. data/lib/danger/request_sources/bitbucket_server.rb +105 -0
  108. data/lib/danger/request_sources/bitbucket_server_api.rb +117 -0
  109. data/lib/danger/request_sources/github/github.rb +530 -0
  110. data/lib/danger/request_sources/github/github_review.rb +126 -0
  111. data/lib/danger/request_sources/github/github_review_resolver.rb +19 -0
  112. data/lib/danger/request_sources/github/github_review_unsupported.rb +25 -0
  113. data/lib/danger/request_sources/gitlab.rb +525 -0
  114. data/lib/danger/request_sources/local_only.rb +53 -0
  115. data/lib/danger/request_sources/request_source.rb +85 -0
  116. data/lib/danger/request_sources/support/get_ignored_violation.rb +17 -0
  117. data/lib/danger/request_sources/vsts.rb +118 -0
  118. data/lib/danger/request_sources/vsts_api.rb +138 -0
  119. data/lib/danger/scm_source/git_repo.rb +181 -0
  120. data/lib/danger/version.rb +4 -0
  121. metadata +339 -0
@@ -0,0 +1,158 @@
1
+ require "danger/plugin_support/plugin"
2
+ require "danger/core_ext/file_list"
3
+
4
+ # Danger
5
+ module Danger
6
+ # Handles interacting with git inside a Dangerfile. Providing access to files that have changed, and useful statistics. Also provides
7
+ # access to the commits in the form of [Git::Log](https://github.com/schacon/ruby-git/blob/master/lib/git/log.rb) objects.
8
+ #
9
+ # @example Do something to all new and edited markdown files
10
+ #
11
+ # markdowns = (git.added_files + git.modified_files)
12
+ # do_something markdowns.select{ |file| file.end_with? "md" }
13
+ #
14
+ # @example Don't allow a file to be deleted
15
+ #
16
+ # deleted = git.deleted_files.include? "my/favourite.file"
17
+ # failure "Don't delete my precious" if deleted
18
+ #
19
+ # @example Fail really big diffs
20
+ #
21
+ # failure "We cannot handle the scale of this PR" if git.lines_of_code > 50_000
22
+ #
23
+ # @example Warn when there are merge commits in the diff
24
+ #
25
+ # if git.commits.any? { |c| c.message =~ /^Merge branch 'master'/ }
26
+ # warn 'Please rebase to get rid of the merge commits in this PR'
27
+ # end
28
+ #
29
+ # @example Warn when somebody tries to add nokogiri to the project
30
+ #
31
+ # diff = git.diff_for_file("Gemfile.lock")
32
+ # if diff && diff.patch =~ "nokogiri"
33
+ # warn 'Please do not add nokogiri to the project. Thank you.'
34
+ # end
35
+ #
36
+ # @see danger/danger
37
+ # @tags core, git
38
+
39
+ class DangerfileGitPlugin < Plugin
40
+ # The instance name used in the Dangerfile
41
+ # @return [String]
42
+ #
43
+ def self.instance_name
44
+ "git"
45
+ end
46
+
47
+ def initialize(dangerfile)
48
+ super(dangerfile)
49
+ raise unless dangerfile.env.scm.class == Danger::GitRepo
50
+
51
+ @git = dangerfile.env.scm
52
+ end
53
+
54
+ # @!group Git Files
55
+ # Paths for files that were added during the diff
56
+ # @return [FileList<String>] an [Array] subclass
57
+ #
58
+ def added_files
59
+ Danger::FileList.new(@git.diff.select { |diff| diff.type == "new" }.map(&:path))
60
+ end
61
+
62
+ # @!group Git Files
63
+ # Paths for files that were removed during the diff
64
+ # @return [FileList<String>] an [Array] subclass
65
+ #
66
+ def deleted_files
67
+ Danger::FileList.new(@git.diff.select { |diff| diff.type == "deleted" }.map(&:path))
68
+ end
69
+
70
+ # @!group Git Files
71
+ # Paths for files that changed during the diff
72
+ # @return [FileList<String>] an [Array] subclass
73
+ #
74
+ def modified_files
75
+ Danger::FileList.new(@git.diff.select { |diff| diff.type == "modified" }.map(&:path))
76
+ end
77
+
78
+ # @!group Git Metadata
79
+ # List of renamed files
80
+ # @return [Array<Hash>] with keys `:before` and `:after`
81
+ #
82
+ def renamed_files
83
+ @git.renamed_files
84
+ end
85
+
86
+ # @!group Git Metadata
87
+ # Whole diff
88
+ # @return [Git::Diff] from the gem `git`
89
+ #
90
+ def diff
91
+ @git.diff
92
+ end
93
+
94
+ # @!group Git Metadata
95
+ # The overall lines of code added/removed in the diff
96
+ # @return [Fixnum]
97
+ #
98
+ def lines_of_code
99
+ @git.diff.lines
100
+ end
101
+
102
+ # @!group Git Metadata
103
+ # The overall lines of code removed in the diff
104
+ # @return [Fixnum]
105
+ #
106
+ def deletions
107
+ @git.diff.deletions
108
+ end
109
+
110
+ # @!group Git Metadata
111
+ # The overall lines of code added in the diff
112
+ # @return [Fixnum]
113
+ #
114
+ def insertions
115
+ @git.diff.insertions
116
+ end
117
+
118
+ # @!group Git Metadata
119
+ # The log of commits inside the diff
120
+ # @return [Git::Log] from the gem `git`
121
+ #
122
+ def commits
123
+ @git.log.to_a
124
+ end
125
+
126
+ # @!group Git Metadata
127
+ # Details for a specific file in this diff
128
+ # @return [Git::Diff::DiffFile] from the gem `git`
129
+ #
130
+ def diff_for_file(file)
131
+ (added_files + modified_files).include?(file) ? @git.diff[file] : nil
132
+ end
133
+
134
+ # @!group Git Metadata
135
+ # Statistics for a specific file in this diff
136
+ # @return [Hash] with keys `:insertions`, `:deletions` giving line counts, and `:before`, `:after` giving file contents, or nil if the file has no changes or does not exist
137
+ #
138
+ def info_for_file(file)
139
+ return nil unless modified_files.include?(file) || added_files.include?(file) || deleted_files.include?(file)
140
+ stats = @git.diff.stats[:files][file]
141
+ diff = @git.diff[file]
142
+ {
143
+ insertions: stats[:insertions],
144
+ deletions: stats[:deletions],
145
+ before: added_files.include?(file) || deleted_files.include?(file) ? nil : diff.blob(:src).contents,
146
+ after: added_files.include?(file) || deleted_files.include?(file) ? nil : diff.blob(:dst).contents
147
+ }
148
+ end
149
+
150
+ # @!group Git Metadata
151
+ # List of remote tags
152
+ # @return [String]
153
+ #
154
+ def tags
155
+ @git.tags.each_line
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,254 @@
1
+ require "danger/plugin_support/plugin"
2
+
3
+ module Danger
4
+ # Handles interacting with GitHub inside a Dangerfile. Provides a few functions which wrap `pr_json` and also
5
+ # through a few standard functions to simplify your code.
6
+ #
7
+ # @example Warn when a PR is classed as work in progress
8
+ #
9
+ # warn "PR is classed as Work in Progress" if github.pr_title.include? "[WIP]"
10
+ #
11
+ # @example Declare a PR to be simple to avoid specific Danger rules
12
+ #
13
+ # declared_trivial = (github.pr_title + github.pr_body).include?("#trivial")
14
+ #
15
+ # @example Ensure that labels have been used on the PR
16
+ #
17
+ # failure "Please add labels to this PR" if github.pr_labels.empty?
18
+ #
19
+ # @example Check if a user is in a specific GitHub org, and message them if so
20
+ #
21
+ # unless github.api.organization_member?('danger', github.pr_author)
22
+ # message "@#{github.pr_author} is not a contributor yet, would you like to join the Danger org?"
23
+ # end
24
+ #
25
+ # @example Ensure there is a summary for a PR
26
+ #
27
+ # failure "Please provide a summary in the Pull Request description" if github.pr_body.length < 5
28
+ #
29
+ # @example Only accept PRs to the develop branch
30
+ #
31
+ # failure "Please re-submit this PR to develop, we may have already fixed your issue." if github.branch_for_base != "develop"
32
+ #
33
+ # @example Note when PRs don't reference a milestone, which goes away when it does
34
+ #
35
+ # has_milestone = github.pr_json["milestone"] != nil
36
+ # warn("This PR does not refer to an existing milestone", sticky: false) unless has_milestone
37
+ #
38
+ # @example Note when a PR cannot be manually merged, which goes away when you can
39
+ #
40
+ # can_merge = github.pr_json["mergeable"]
41
+ # warn("This PR cannot be merged yet.", sticky: false) unless can_merge
42
+ #
43
+ # @example Highlight when a celebrity makes a pull request
44
+ #
45
+ # message "Welcome, Danger." if github.pr_author == "dangermcshane"
46
+ #
47
+ # @example Ensure that all PRs have an assignee
48
+ #
49
+ # warn "This PR does not have any assignees yet." unless github.pr_json["assignee"]
50
+ #
51
+ # @example Send a message with links to a collection of specific files
52
+ #
53
+ # if git.modified_files.include? "config/*.js"
54
+ # config_files = git.modified_files.select { |path| path.include? "config/" }
55
+ # message "This PR changes #{ github.html_link(config_files) }"
56
+ # end
57
+ #
58
+ # @example Highlight with a clickable link if a Package.json is changed
59
+ #
60
+ # warn "#{github.html_link("Package.json")} was edited." if git.modified_files.include? "Package.json"
61
+ #
62
+ # @example Note an issue with a particular line on a file using the #L[num] syntax, e.g. `#L23`
63
+ #
64
+ # linter_json = `my_linter lint "file"`
65
+ # results = JSON.parse linter_json
66
+ # unless results.empty?
67
+ # file, line, warning = result.first
68
+ # warn "#{github.html_link("#{file}#L#{line}")} has linter issue: #{warning}."
69
+ # end
70
+ #
71
+ #
72
+ # @see danger/danger
73
+ # @tags core, github
74
+ #
75
+ class DangerfileGitHubPlugin < Plugin
76
+ # So that this init can fail.
77
+ def self.new(dangerfile)
78
+ return nil if dangerfile.env.request_source.class != Danger::RequestSources::GitHub
79
+ super
80
+ end
81
+
82
+ def initialize(dangerfile)
83
+ super(dangerfile)
84
+
85
+ @github = dangerfile.env.request_source
86
+ end
87
+
88
+ # The instance name used in the Dangerfile
89
+ # @return [String]
90
+ #
91
+ def self.instance_name
92
+ "github"
93
+ end
94
+
95
+ # @!group PR Review
96
+ #
97
+ # In Beta. Provides access to creating a GitHub Review instead of a typical GitHub comment.
98
+ #
99
+ # To use you announce the start of your review, and the end via the `start` and `submit` functions,
100
+ # for example:
101
+ #
102
+ # github.review.start
103
+ # github.review.fail(message)
104
+ # github.review.warn(message)
105
+ # github.review.message(message)
106
+ # github.review.markdown(message)
107
+ # github.review.submit
108
+ #
109
+ # @return [ReviewDSL]
110
+ def review
111
+ @github.review
112
+ end
113
+
114
+ # @!group PR Metadata
115
+ # The title of the Pull Request.
116
+ # @return [String]
117
+ #
118
+ def pr_title
119
+ @github.pr_json["title"].to_s
120
+ end
121
+
122
+ # @!group PR Metadata
123
+ # The body text of the Pull Request.
124
+ # @return [String]
125
+ #
126
+ def pr_body
127
+ pr_json["body"].to_s
128
+ end
129
+
130
+ # @!group PR Metadata
131
+ # The username of the author of the Pull Request.
132
+ # @return [String]
133
+ #
134
+ def pr_author
135
+ pr_json["user"]["login"].to_s
136
+ end
137
+
138
+ # @!group PR Metadata
139
+ # The labels assigned to the Pull Request.
140
+ # @return [String]
141
+ #
142
+ def pr_labels
143
+ @github.issue_json["labels"].map { |l| l[:name] }
144
+ end
145
+
146
+ # @!group PR Commit Metadata
147
+ # The branch to which the PR is going to be merged into.
148
+ # @return [String]
149
+ #
150
+ def branch_for_base
151
+ pr_json["base"]["ref"]
152
+ end
153
+
154
+ # @!group PR Commit Metadata
155
+ # The branch to which the PR is going to be merged from.
156
+ # @return [String]
157
+ #
158
+ def branch_for_head
159
+ pr_json["head"]["ref"]
160
+ end
161
+
162
+ # @!group PR Commit Metadata
163
+ # The base commit to which the PR is going to be merged as a parent.
164
+ # @return [String]
165
+ #
166
+ def base_commit
167
+ pr_json["base"]["sha"]
168
+ end
169
+
170
+ # @!group PR Commit Metadata
171
+ # The head commit to which the PR is requesting to be merged from.
172
+ # @return [String]
173
+ #
174
+ def head_commit
175
+ pr_json["head"]["sha"]
176
+ end
177
+
178
+ # @!group GitHub Misc
179
+ # The hash that represents the PR's JSON. For an example of what this looks like
180
+ # see the [Danger Fixture'd one](https://raw.githubusercontent.com/danger/danger/master/spec/fixtures/github_api/pr_response.json).
181
+ # @return [Hash]
182
+ #
183
+ def pr_json
184
+ @github.pr_json
185
+ end
186
+
187
+ # @!group GitHub Misc
188
+ # Provides access to the GitHub API client used inside Danger. Making
189
+ # it easy to use the GitHub API inside a Dangerfile.
190
+ # @return [Octokit::Client]
191
+ def api
192
+ @github.client
193
+ end
194
+
195
+ # @!group PR Content
196
+ # The unified diff produced by Github for this PR
197
+ # see [Unified diff](https://en.wikipedia.org/wiki/Diff_utility#Unified_format)
198
+ # @return [String]
199
+ def pr_diff
200
+ @github.pr_diff
201
+ end
202
+
203
+ # @!group GitHub Misc
204
+ # Returns a list of HTML anchors for a file, or files in the head repository. An example would be:
205
+ # `<a href='https://github.com/artsy/eigen/blob/561827e46167077b5e53515b4b7349b8ae04610b/file.txt'>file.txt</a>`. It returns a string of multiple anchors if passed an array.
206
+ # @param [String or Array<String>] paths
207
+ # A list of strings to convert to github anchors
208
+ # @param [Bool] full_path
209
+ # Shows the full path as the link's text, defaults to `true`.
210
+ #
211
+ # @return [String]
212
+ def html_link(paths, full_path: true)
213
+ paths = [paths] unless paths.kind_of?(Array)
214
+ commit = head_commit
215
+ repo = pr_json["head"]["repo"]["html_url"]
216
+
217
+ paths = paths.map do |path|
218
+ url_path = path.start_with?("/") ? path : "/#{path}"
219
+ text = full_path ? path : File.basename(path)
220
+ create_link("#{repo}/blob/#{commit}#{url_path}", text)
221
+ end
222
+
223
+ return paths.first if paths.count < 2
224
+ paths.first(paths.count - 1).join(", ") + " & " + paths.last
225
+ end
226
+
227
+ # @!group GitHub Misc
228
+ # Use to ignore inline messages which lay outside a diff's range, thereby not posting them in the main comment.
229
+ # You can set hash to change behavior per each kinds. (ex. `{warning: true, error: false}`)
230
+ # @param [Bool] or [Hash<Symbol, Bool>] dismiss
231
+ # Ignore out of range inline messages, defaults to `true`
232
+ #
233
+ # @return [void]
234
+ def dismiss_out_of_range_messages(dismiss = true)
235
+ if dismiss.kind_of?(Hash)
236
+ @github.dismiss_out_of_range_messages = dismiss
237
+ elsif dismiss.kind_of?(TrueClass)
238
+ @github.dismiss_out_of_range_messages = true
239
+ elsif dismiss.kind_of?(FalseClass)
240
+ @github.dismiss_out_of_range_messages = false
241
+ end
242
+ end
243
+
244
+ %i(title body author labels json).each do |suffix|
245
+ alias_method "mr_#{suffix}".to_sym, "pr_#{suffix}".to_sym
246
+ end
247
+
248
+ private
249
+
250
+ def create_link(href, text)
251
+ "<a href='#{href}'>#{text}</a>"
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,240 @@
1
+ require "danger/plugin_support/plugin"
2
+
3
+ module Danger
4
+ # Handles interacting with GitLab inside a Dangerfile. Provides a few functions which wrap `mr_json` and also
5
+ # through a few standard functions to simplify your code.
6
+ #
7
+ # @example Warn when an MR is classed as work in progress.
8
+ #
9
+ # warn "MR is classed as Work in Progress" if gitlab.mr_title.include? "[WIP]"
10
+ #
11
+ # @example Declare a MR to be simple to avoid specific Danger rules.
12
+ #
13
+ # declared_trivial = (gitlab.mr_title + gitlab.mr_body).include?("#trivial")
14
+ #
15
+ # @example Ensure that labels have been applied to the MR.
16
+ #
17
+ # failure "Please add labels to this MR" if gitlab.mr_labels.empty?
18
+ #
19
+ # @example Ensure that all MRs have an assignee.
20
+ #
21
+ # warn "This MR does not have any assignees yet." unless gitlab.mr_json["assignee"]
22
+ #
23
+ # @example Ensure there is a summary for a MR.
24
+ #
25
+ # failure "Please provide a summary in the Merge Request description" if gitlab.mr_body.length < 5
26
+ #
27
+ # @example Only accept MRs to the develop branch.
28
+ #
29
+ # failure "Please re-submit this MR to develop, we may have already fixed your issue." if gitlab.branch_for_merge != "develop"
30
+ #
31
+ # @example Note when MRs don't reference a milestone, make the warning stick around on subsequent runs
32
+ #
33
+ # has_milestone = gitlab.mr_json["milestone"] != nil
34
+ # warn("This MR does not refer to an existing milestone", sticky: true) unless has_milestone
35
+ #
36
+ # @example Note when a MR cannot be manually merged
37
+ #
38
+ # can_merge = gitlab.mr_json["mergeable"]
39
+ # warn("This MR cannot be merged yet.") unless can_merge
40
+ #
41
+ # @example Highlight when a celebrity makes a merge request.
42
+ #
43
+ # message "Welcome, Danger." if gitlab.mr_author == "dangermcshane"
44
+ #
45
+ # @example Send a message with links to a collection of specific files.
46
+ #
47
+ # if git.modified_files.include? "config/*.js"
48
+ # config_files = git.modified_files.select { |path| path.include? "config/" }
49
+ # message "This MR changes #{ gitlab.html_link(config_files) }"
50
+ # end
51
+ #
52
+ # @example Highlight with a clickable link if a Package.json is changed.
53
+ #
54
+ # warn "#{gitlab.html_link("Package.json")} was edited." if git.modified_files.include? "Package.json"
55
+ #
56
+ # @example Select a random group member as assignee if no assignee is selected
57
+ #
58
+ # if gitlab.mr_json["assignee"].nil?
59
+ # reviewer = gitlab.api.group_members(gitlab.api.merge_request_approvals(project_id, mr_id).to_hash["approver_groups"].first["group"]["id"]).sample
60
+ # if gitlab.api.group_members(gitlab.api.merge_request_approvals(project_id, mr_id).to_hash["approver_groups"].first["group"]["id"]).length > 1
61
+ # while reviewer.to_hash["id"] == gitlab.mr_json["author"]["id"] do
62
+ # reviewer = gitlab.api.group_members(gitlab.api.merge_request_approvals(project_id, mr_id).to_hash["approver_groups"].first["group"]["id"]).sample
63
+ # end
64
+ # end
65
+ # message "Reviewer roulete rolled for: #{reviewer.to_hash['name']} (@#{reviewer.to_hash['username']})"
66
+ # gitlab.api.update_merge_request(project_id, mr_id, { assignee_id: reviewer.to_hash["id"] })
67
+ # end
68
+ #
69
+ #
70
+ # @see danger/danger
71
+ # @tags core, gitlab
72
+ #
73
+ class DangerfileGitLabPlugin < Plugin
74
+ # So that this init can fail.
75
+ def self.new(dangerfile)
76
+ return nil if dangerfile.env.request_source.class != Danger::RequestSources::GitLab
77
+ super
78
+ end
79
+
80
+ # The instance name used in the Dangerfile
81
+ # @return [String]
82
+ #
83
+ def self.instance_name
84
+ "gitlab"
85
+ end
86
+
87
+ def initialize(dangerfile)
88
+ super(dangerfile)
89
+
90
+ @gitlab = dangerfile.env.request_source
91
+ end
92
+
93
+ # @!group MR Metadata
94
+ # The title of the Merge Request
95
+ # @return [String]
96
+ #
97
+ def mr_title
98
+ @gitlab.mr_json.title.to_s
99
+ end
100
+
101
+ # @!group MR Metadata
102
+ # The body text of the Merge Request
103
+ # @return [String]
104
+ #
105
+ def mr_body
106
+ @gitlab.mr_json.description.to_s
107
+ end
108
+
109
+ # @!group MR Metadata
110
+ # The username of the author of the Merge Request
111
+ # @return [String]
112
+ #
113
+ def mr_author
114
+ @gitlab.mr_json.author.username.to_s
115
+ end
116
+
117
+ # @!group MR Metadata
118
+ # The labels assigned to the Merge Request
119
+ # @return [String]
120
+ #
121
+ def mr_labels
122
+ @gitlab.mr_json.labels
123
+ end
124
+
125
+ # @!group MR Content
126
+ # The unified diff produced by GitLab for this MR
127
+ # see [Unified diff](https://en.wikipedia.org/wiki/Diff_utility#Unified_format)
128
+ # @return [String]
129
+ #
130
+ def mr_diff
131
+ @gitlab.mr_diff
132
+ end
133
+
134
+ # @!group MR Commit Metadata
135
+ # The branch to which the MR is going to be merged into
136
+ # @deprecated Please use {#branch_for_base} instead
137
+ # @return [String]
138
+ #
139
+ def branch_for_merge
140
+ branch_for_base
141
+ end
142
+
143
+ # @!group MR Commit Metadata
144
+ # The branch to which the MR is going to be merged into.
145
+ # @return [String]
146
+ #
147
+ def branch_for_base
148
+ @gitlab.mr_json.target_branch
149
+ end
150
+
151
+ # @!group MR Commit Metadata
152
+ # The branch to which the MR is going to be merged from.
153
+ # @return [String]
154
+ #
155
+ def branch_for_head
156
+ @gitlab.mr_json.source_branch
157
+ end
158
+
159
+ # @!group MR Commit Metadata
160
+ # The base commit to which the MR is going to be merged as a parent
161
+ # @return [String]
162
+ #
163
+ def base_commit
164
+ @gitlab.mr_json.diff_refs.base_sha
165
+ end
166
+
167
+ # @!group MR Commit Metadata
168
+ # The head commit to which the MR is requesting to be merged from
169
+ # @return [String]
170
+ #
171
+ def head_commit
172
+ @gitlab.mr_json.diff_refs.head_sha
173
+ end
174
+
175
+ # @!group GitLab Misc
176
+ # The hash that represents the MR's JSON. See documentation for the
177
+ # structure [here](http://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr)
178
+ # @return [Hash]
179
+ #
180
+ def mr_json
181
+ @gitlab.mr_json.to_hash
182
+ end
183
+
184
+ # @!group GitLab Misc
185
+ # Provides access to the GitLab API client used inside Danger. Making
186
+ # it easy to use the GitLab API inside a Dangerfile. See the gitlab
187
+ # gem's [documentation](http://www.rubydoc.info/gems/gitlab/Gitlab/Client)
188
+ # for accessible methods.
189
+ # @return [GitLab::Client]
190
+ #
191
+ def api
192
+ @gitlab.client
193
+ end
194
+
195
+ # @!group GitLab Misc
196
+ # Returns the web_url of the source project.
197
+ # @return [String]
198
+ #
199
+ def repository_web_url
200
+ @repository_web_url ||= begin
201
+ project = api.project(mr_json["source_project_id"])
202
+ project.web_url
203
+ end
204
+ end
205
+
206
+ # @!group GitLab Misc
207
+ # Returns a list of HTML anchors for a file, or files in the head repository. An example would be:
208
+ # `<a href='https://gitlab.com/artsy/eigen/blob/561827e46167077b5e53515b4b7349b8ae04610b/file.txt'>file.txt</a>`. It returns a string of multiple anchors if passed an array.
209
+ # @param [String or Array<String>] paths
210
+ # A list of strings to convert to gitlab anchors
211
+ # @param [Bool] full_path
212
+ # Shows the full path as the link's text, defaults to `true`.
213
+ #
214
+ # @return [String]
215
+ #
216
+ def html_link(paths, full_path: true)
217
+ paths = [paths] unless paths.kind_of?(Array)
218
+ commit = head_commit
219
+
220
+ paths = paths.map do |path|
221
+ url_path = path.start_with?("/") ? path : "/#{path}"
222
+ text = full_path ? path : File.basename(path)
223
+ create_link("#{repository_web_url}/blob/#{commit}#{url_path}", text)
224
+ end
225
+
226
+ return paths.first if paths.count < 2
227
+ paths.first(paths.count - 1).join(", ") + " & " + paths.last
228
+ end
229
+
230
+ %i(title body author labels json diff).each do |suffix|
231
+ alias_method "pr_#{suffix}".to_sym, "mr_#{suffix}".to_sym
232
+ end
233
+
234
+ private
235
+
236
+ def create_link(href, text)
237
+ "<a href='#{href}'>#{text}</a>"
238
+ end
239
+ end
240
+ end