danger-additional-logging 0.0.1

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 (127) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +93 -0
  4. data/bin/danger +5 -0
  5. data/lib/assets/DangerfileTemplate +13 -0
  6. data/lib/danger/ci_source/appcenter.rb +55 -0
  7. data/lib/danger/ci_source/appcircle.rb +83 -0
  8. data/lib/danger/ci_source/appveyor.rb +64 -0
  9. data/lib/danger/ci_source/azure_pipelines.rb +61 -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 +78 -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 +71 -0
  20. data/lib/danger/ci_source/codefresh.rb +47 -0
  21. data/lib/danger/ci_source/codemagic.rb +58 -0
  22. data/lib/danger/ci_source/codeship.rb +44 -0
  23. data/lib/danger/ci_source/concourse.rb +60 -0
  24. data/lib/danger/ci_source/custom_ci_with_github.rb +49 -0
  25. data/lib/danger/ci_source/dotci.rb +50 -0
  26. data/lib/danger/ci_source/drone.rb +71 -0
  27. data/lib/danger/ci_source/github_actions.rb +44 -0
  28. data/lib/danger/ci_source/gitlab_ci.rb +89 -0
  29. data/lib/danger/ci_source/jenkins.rb +148 -0
  30. data/lib/danger/ci_source/local_git_repo.rb +117 -0
  31. data/lib/danger/ci_source/local_only_git_repo.rb +44 -0
  32. data/lib/danger/ci_source/screwdriver.rb +48 -0
  33. data/lib/danger/ci_source/semaphore.rb +37 -0
  34. data/lib/danger/ci_source/support/commits.rb +19 -0
  35. data/lib/danger/ci_source/support/find_repo_info_from_logs.rb +35 -0
  36. data/lib/danger/ci_source/support/find_repo_info_from_url.rb +43 -0
  37. data/lib/danger/ci_source/support/local_pull_request.rb +14 -0
  38. data/lib/danger/ci_source/support/no_pull_request.rb +7 -0
  39. data/lib/danger/ci_source/support/no_repo_info.rb +5 -0
  40. data/lib/danger/ci_source/support/pull_request_finder.rb +190 -0
  41. data/lib/danger/ci_source/support/remote_pull_request.rb +15 -0
  42. data/lib/danger/ci_source/support/repo_info.rb +10 -0
  43. data/lib/danger/ci_source/surf.rb +37 -0
  44. data/lib/danger/ci_source/teamcity.rb +163 -0
  45. data/lib/danger/ci_source/travis.rb +51 -0
  46. data/lib/danger/ci_source/xcode_cloud.rb +38 -0
  47. data/lib/danger/ci_source/xcode_server.rb +48 -0
  48. data/lib/danger/clients/rubygems_client.rb +14 -0
  49. data/lib/danger/commands/dangerfile/gem.rb +43 -0
  50. data/lib/danger/commands/dangerfile/init.rb +30 -0
  51. data/lib/danger/commands/dry_run.rb +54 -0
  52. data/lib/danger/commands/init.rb +297 -0
  53. data/lib/danger/commands/init_helpers/interviewer.rb +92 -0
  54. data/lib/danger/commands/local.rb +83 -0
  55. data/lib/danger/commands/local_helpers/http_cache.rb +38 -0
  56. data/lib/danger/commands/local_helpers/local_setup.rb +48 -0
  57. data/lib/danger/commands/local_helpers/pry_setup.rb +32 -0
  58. data/lib/danger/commands/plugins/plugin_json.rb +44 -0
  59. data/lib/danger/commands/plugins/plugin_lint.rb +52 -0
  60. data/lib/danger/commands/plugins/plugin_readme.rb +42 -0
  61. data/lib/danger/commands/pr.rb +93 -0
  62. data/lib/danger/commands/runner.rb +94 -0
  63. data/lib/danger/commands/staging.rb +53 -0
  64. data/lib/danger/commands/systems.rb +41 -0
  65. data/lib/danger/comment_generators/bitbucket_server.md.erb +20 -0
  66. data/lib/danger/comment_generators/bitbucket_server_inline.md.erb +15 -0
  67. data/lib/danger/comment_generators/bitbucket_server_message_group.md.erb +12 -0
  68. data/lib/danger/comment_generators/github.md.erb +55 -0
  69. data/lib/danger/comment_generators/github_inline.md.erb +26 -0
  70. data/lib/danger/comment_generators/gitlab.md.erb +40 -0
  71. data/lib/danger/comment_generators/gitlab_inline.md.erb +21 -0
  72. data/lib/danger/comment_generators/vsts.md.erb +20 -0
  73. data/lib/danger/comment_generators/vsts_inline.md.erb +17 -0
  74. data/lib/danger/core_ext/file_list.rb +18 -0
  75. data/lib/danger/core_ext/string.rb +20 -0
  76. data/lib/danger/danger_core/dangerfile.rb +348 -0
  77. data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
  78. data/lib/danger/danger_core/dangerfile_generator.rb +11 -0
  79. data/lib/danger/danger_core/environment_manager.rb +126 -0
  80. data/lib/danger/danger_core/executor.rb +91 -0
  81. data/lib/danger/danger_core/message_aggregator.rb +50 -0
  82. data/lib/danger/danger_core/message_group.rb +68 -0
  83. data/lib/danger/danger_core/messages/base.rb +57 -0
  84. data/lib/danger/danger_core/messages/markdown.rb +41 -0
  85. data/lib/danger/danger_core/messages/violation.rb +53 -0
  86. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +142 -0
  87. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb +211 -0
  88. data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +274 -0
  89. data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +159 -0
  90. data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +264 -0
  91. data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +275 -0
  92. data/lib/danger/danger_core/plugins/dangerfile_local_only_plugin.rb +43 -0
  93. data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +220 -0
  94. data/lib/danger/danger_core/plugins/dangerfile_vsts_plugin.rb +191 -0
  95. data/lib/danger/danger_core/standard_error.rb +142 -0
  96. data/lib/danger/helpers/array_subclass.rb +61 -0
  97. data/lib/danger/helpers/comment.rb +32 -0
  98. data/lib/danger/helpers/comments_helper.rb +179 -0
  99. data/lib/danger/helpers/comments_parsing_helper.rb +71 -0
  100. data/lib/danger/helpers/emoji_mapper.rb +41 -0
  101. data/lib/danger/helpers/find_max_num_violations.rb +31 -0
  102. data/lib/danger/helpers/message_groups_array_helper.rb +31 -0
  103. data/lib/danger/plugin_support/gems_resolver.rb +77 -0
  104. data/lib/danger/plugin_support/plugin.rb +52 -0
  105. data/lib/danger/plugin_support/plugin_file_resolver.rb +30 -0
  106. data/lib/danger/plugin_support/plugin_linter.rb +162 -0
  107. data/lib/danger/plugin_support/plugin_parser.rb +199 -0
  108. data/lib/danger/plugin_support/templates/readme_table.html.erb +26 -0
  109. data/lib/danger/request_sources/bitbucket_cloud.rb +169 -0
  110. data/lib/danger/request_sources/bitbucket_cloud_api.rb +181 -0
  111. data/lib/danger/request_sources/bitbucket_server.rb +210 -0
  112. data/lib/danger/request_sources/bitbucket_server_api.rb +129 -0
  113. data/lib/danger/request_sources/code_insights_api.rb +142 -0
  114. data/lib/danger/request_sources/github/github.rb +535 -0
  115. data/lib/danger/request_sources/github/github_review.rb +127 -0
  116. data/lib/danger/request_sources/github/github_review_resolver.rb +17 -0
  117. data/lib/danger/request_sources/github/github_review_unsupported.rb +23 -0
  118. data/lib/danger/request_sources/gitlab.rb +557 -0
  119. data/lib/danger/request_sources/local_only.rb +50 -0
  120. data/lib/danger/request_sources/request_source.rb +97 -0
  121. data/lib/danger/request_sources/support/get_ignored_violation.rb +17 -0
  122. data/lib/danger/request_sources/vsts.rb +278 -0
  123. data/lib/danger/request_sources/vsts_api.rb +172 -0
  124. data/lib/danger/scm_source/git_repo.rb +198 -0
  125. data/lib/danger/version.rb +4 -0
  126. data/lib/danger.rb +45 -0
  127. metadata +351 -0
@@ -0,0 +1,274 @@
1
+ require "danger/plugin_support/plugin"
2
+
3
+ module Danger
4
+ # A way to interact with Danger herself. Offering APIs to import plugins,
5
+ # and Dangerfiles from multiple sources.
6
+ #
7
+ # @example Import a plugin available over HTTP
8
+ #
9
+ # device_grid = "https://raw.githubusercontent.com/fastlane/fastlane/master/danger-device_grid/lib/device_grid/plugin.rb"
10
+ # danger.import_plugin(device_grid)
11
+ #
12
+ # @example Import from a local file reference
13
+ #
14
+ # danger.import_plugin("danger/plugins/watch_plugin.rb")
15
+ #
16
+ # @example Import all files inside a folder
17
+ #
18
+ # danger.import_plugin("danger/plugins/*.rb")
19
+ #
20
+ # @example Run a Dangerfile from inside a sub-folder
21
+ #
22
+ # danger.import_dangerfile(path: "path/to/Dangerfile")
23
+ #
24
+ # @example Run a Dangerfile from inside a gem
25
+ #
26
+ # danger.import_dangerfile(gem: "ruby-grape-danger")
27
+ #
28
+ # @example Run a Dangerfile from inside a repo
29
+ #
30
+ # danger.import_dangerfile(gitlab_project_id: 1345)
31
+ #
32
+ # @example Run a Dangerfile from inside a repo branch and path
33
+ #
34
+ # danger.import_dangerfile(github: "ruby-grape/danger", branch: "custom", path: "path/to/Dangerfile")
35
+ #
36
+ # @example Import a plugin available over HTTP
37
+ #
38
+ # custom_url = "https://custom.bitbucket.com/project-name/Dangerfile?raw"
39
+ # danger.import_dangerfile(url: custom_url)
40
+ #
41
+ # @see danger/danger
42
+ # @tags core, plugins
43
+
44
+ class DangerfileDangerPlugin < Plugin
45
+ # The instance name used in the Dangerfile
46
+ # @return [String]
47
+ #
48
+ def self.instance_name
49
+ "danger"
50
+ end
51
+
52
+ # @!group Danger
53
+ # Download a local or remote plugin and make it usable inside the Dangerfile.
54
+ #
55
+ # @param [String] path_or_url
56
+ # a local path or a https URL to the Ruby file to import
57
+ # a danger plugin from.
58
+ # @return [void]
59
+ #
60
+ def import_plugin(path_or_url)
61
+ raise "`import_plugin` requires a string" unless path_or_url.kind_of?(String)
62
+
63
+ if path_or_url.start_with?("http")
64
+ import_url(path_or_url)
65
+ else
66
+ import_local(path_or_url)
67
+ end
68
+ end
69
+
70
+ # @!group Danger
71
+ # Import a Dangerfile.
72
+ #
73
+ # @param [Hash] opts
74
+ # @option opts [String] :github GitHub repo
75
+ # @option opts [String] :gitlab GitLab repo
76
+ # @option opts [String] :gem Gem name
77
+ # @option opts [String] :ref The name of branch, tag or commit
78
+ # @option opts [String] :branch Alias of :ref
79
+ # @option opts [String] :path Path to Dangerfile
80
+ # @return [void]
81
+ def import_dangerfile(opts)
82
+ if opts.kind_of?(String)
83
+ warn "Use `import_dangerfile(github: '#{opts}')` instead of `import_dangerfile '#{opts}'`."
84
+ import_dangerfile_from_github(opts)
85
+ elsif opts.kind_of?(Hash)
86
+ if opts.key?(:github)
87
+ import_dangerfile_from_github(opts[:github], opts[:ref] || opts[:branch], opts[:path])
88
+ elsif opts.key?(:gitlab)
89
+ import_dangerfile_from_gitlab(opts[:gitlab], opts[:ref] || opts[:branch], opts[:path])
90
+ elsif opts.key?(:path)
91
+ import_dangerfile_from_path(opts[:path])
92
+ elsif opts.key?(:gem)
93
+ import_dangerfile_from_gem(opts[:gem])
94
+ elsif opts.key?(:url)
95
+ import_dangerfile_from_url(opts[:url])
96
+ else
97
+ raise "`import` requires a Hash with either :github, :gitlab, :gem, :path or :url"
98
+ end
99
+ else
100
+ raise "`import` requires a Hash"
101
+ end
102
+ end
103
+
104
+ # @!group Danger
105
+ # Returns the name of the current SCM Provider being used.
106
+ # @return [Symbol] The name of the SCM Provider used for the active repository.
107
+ def scm_provider
108
+ return :unknown unless env.request_source
109
+
110
+ case env.request_source
111
+ when Danger::RequestSources::GitHub
112
+ :github
113
+ when Danger::RequestSources::GitLab
114
+ :gitlab
115
+ when Danger::RequestSources::BitbucketServer
116
+ :bitbucket_server
117
+ when Danger::RequestSources::BitbucketCloud
118
+ :bitbucket_cloud
119
+ when Danger::RequestSources::VSTS
120
+ :vsts
121
+ else
122
+ :unknown
123
+ end
124
+ end
125
+
126
+ private
127
+
128
+ # @!group Danger
129
+ # Read and execute a local Dangerfile.
130
+ #
131
+ # @param [String] path
132
+ # A path to a Dangerfile.
133
+ # @return [void]
134
+ #
135
+ def import_dangerfile_from_path(path)
136
+ raise "`import_dangerfile_from_path` requires a string" unless path.kind_of?(String)
137
+
138
+ local_path = File.file?(path) ? path : File.join(path, "Dangerfile")
139
+ @dangerfile.parse(Pathname.new(local_path))
140
+ end
141
+
142
+ # @!group Danger
143
+ # Read and execute a Dangerfile from a gem.
144
+ #
145
+ # @param [String] name
146
+ # The name of the gem that contains a Dangerfile.
147
+ # @return [void]
148
+ #
149
+ def import_dangerfile_from_gem(name)
150
+ raise "`import_dangerfile_from_gem` requires a string" unless name.kind_of?(String)
151
+
152
+ spec = Gem::Specification.find_by_name(name)
153
+ import_dangerfile_from_path(spec.gem_dir)
154
+ rescue Gem::MissingSpecError
155
+ raise "`import_dangerfile_from_gem` tried to load `#{name}` and failed, did you forget to include it in your Gemfile?"
156
+ end
157
+
158
+ # @!group Danger
159
+ # Download and execute a remote Dangerfile.
160
+ #
161
+ # @param [String] slug
162
+ # A slug that represents the repo where the Dangerfile is.
163
+ # @param [String] branch
164
+ # A branch from repo where the Dangerfile is.
165
+ # @param [String] path
166
+ # The path at the repo where Dangerfile is.
167
+ # @return [void]
168
+ #
169
+ def import_dangerfile_from_github(slug, branch = nil, path = nil)
170
+ raise "`import_dangerfile_from_github` requires a string" unless slug.kind_of?(String)
171
+
172
+ org, repo = slug.split("/")
173
+ download_url = env.request_source.file_url(organisation: org, repository: repo, branch: branch, path: path || "Dangerfile")
174
+ local_path = download(download_url)
175
+ @dangerfile.parse(Pathname.new(local_path))
176
+ end
177
+
178
+ # @!group Danger
179
+ # Download and execute a remote Dangerfile.
180
+ #
181
+ # @param [Int] slug_or_project_id
182
+ # The slug or id of the repo where the Dangerfile is.
183
+ # @param [String] branch
184
+ # A branch from repo where the Dangerfile is.
185
+ # @param [String] path
186
+ # The path at the repo where Dangerfile is.
187
+ # @return [void]
188
+ #
189
+ def import_dangerfile_from_gitlab(slug_or_project_id, branch = nil, path = nil)
190
+ download_url = env.request_source.file_url(repository: slug_or_project_id, branch: branch, path: path || "Dangerfile")
191
+ local_path = download(download_url)
192
+ @dangerfile.parse(Pathname.new(local_path))
193
+ end
194
+
195
+ # @!group Danger
196
+ # Download and execute a remote Dangerfile.
197
+ #
198
+ # @param [String] url
199
+ # A https url where the Dangerfile is.
200
+ # @return [void]
201
+ #
202
+ def import_dangerfile_from_url(url)
203
+ raise "`import_dangerfile_from_url` requires a string" unless url.kind_of?(String)
204
+
205
+ local_path = download(url)
206
+ @dangerfile.parse(Pathname.new(local_path))
207
+ end
208
+
209
+ # @!group Plugins
210
+ # Download a local or remote plugin or Dangerfile.
211
+ # This method will not import the file for you, use plugin.import instead
212
+ #
213
+ # @param [String] path_or_url
214
+ # a local path or a https URL to the Ruby file to import
215
+ # a danger plugin from.
216
+ # @return [String] The path to the downloaded Ruby file
217
+ #
218
+ def download(path_or_url)
219
+ raise "`download` requires a string" unless path_or_url.kind_of?(String)
220
+ raise "URL is not https, for security reasons `danger` only supports encrypted requests" if URI.parse(path_or_url).scheme != "https"
221
+
222
+ require "tmpdir"
223
+ require "faraday"
224
+
225
+ @http_client ||= Faraday.new do |b|
226
+ b.adapter :net_http
227
+ end
228
+ content = @http_client.get(path_or_url)
229
+
230
+ path = File.join(Dir.mktmpdir, "temporary_danger.rb")
231
+ File.write(path, content.body)
232
+ return path
233
+ end
234
+
235
+ # @!group Plugins
236
+ # Download a remote plugin and use it locally.
237
+ #
238
+ # @param [String] url
239
+ # https URL to the Ruby file to use
240
+ # @return [void]
241
+ def import_url(url)
242
+ path = download(url)
243
+ import_local(path)
244
+ end
245
+
246
+ # @!group Plugins
247
+ # Import one or more local plugins.
248
+ #
249
+ # @param [String] path
250
+ # The path to the file to import
251
+ # Can also be a pattern (./**/*plugin.rb)
252
+ # @return [void]
253
+ def import_local(path)
254
+ Dir[path].each do |file|
255
+ validate_file_contains_plugin!(file) do
256
+ # Without the expand_path it would fail if the path doesn't start with ./
257
+ require File.expand_path(file)
258
+ end
259
+
260
+ refresh_plugins
261
+ end
262
+ end
263
+
264
+ # Raises an error when the given block does not register a plugin.
265
+ def validate_file_contains_plugin!(file)
266
+ plugin_count_was = Danger::Plugin.all_plugins.length
267
+ yield
268
+
269
+ if Danger::Plugin.all_plugins.length == plugin_count_was
270
+ raise("#{file} doesn't contain any valid danger plugins.")
271
+ end
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,159 @@
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.parents.count > 1 }
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 # rubocop:disable Style/ClassEqualityComparison
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 + deleted_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
+
141
+ stats = @git.diff.stats[:files][file]
142
+ diff = @git.diff[file]
143
+ {
144
+ insertions: stats[:insertions],
145
+ deletions: stats[:deletions],
146
+ before: added_files.include?(file) || deleted_files.include?(file) ? nil : diff.blob(:src).contents,
147
+ after: added_files.include?(file) || deleted_files.include?(file) ? nil : diff.blob(:dst).contents
148
+ }
149
+ end
150
+
151
+ # @!group Git Metadata
152
+ # List of remote tags
153
+ # @return [String]
154
+ #
155
+ def tags
156
+ @git.tags.each_line
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,264 @@
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
+
80
+ super
81
+ end
82
+
83
+ def initialize(dangerfile)
84
+ super(dangerfile)
85
+
86
+ @github = dangerfile.env.request_source
87
+ end
88
+
89
+ # The instance name used in the Dangerfile
90
+ # @return [String]
91
+ #
92
+ def self.instance_name
93
+ "github"
94
+ end
95
+
96
+ # @!group PR Review
97
+ #
98
+ # In Beta. Provides access to creating a GitHub Review instead of a typical GitHub comment.
99
+ #
100
+ # To use you announce the start of your review, and the end via the `start` and `submit` functions,
101
+ # for example:
102
+ #
103
+ # github.review.start
104
+ # github.review.fail(message)
105
+ # github.review.warn(message)
106
+ # github.review.message(message)
107
+ # github.review.markdown(message)
108
+ # github.review.submit
109
+ #
110
+ # @return [ReviewDSL]
111
+ def review
112
+ @github.review
113
+ end
114
+
115
+ # @!group PR Metadata
116
+ # The title of the Pull Request.
117
+ # @return [String]
118
+ #
119
+ def pr_title
120
+ @github.pr_json["title"].to_s
121
+ end
122
+
123
+ # @!group PR Metadata
124
+ # The body text of the Pull Request.
125
+ # @return [String]
126
+ #
127
+ def pr_body
128
+ pr_json["body"].to_s
129
+ end
130
+
131
+ # @!group PR Metadata
132
+ # The username of the author of the Pull Request.
133
+ # @return [String]
134
+ #
135
+ def pr_author
136
+ pr_json["user"]["login"].to_s
137
+ end
138
+
139
+ # @!group PR Metadata
140
+ # The labels assigned to the Pull Request.
141
+ # @return [String]
142
+ #
143
+ def pr_labels
144
+ @github.issue_json["labels"].map { |l| l[:name] }
145
+ end
146
+
147
+ # @!group PR Metadata
148
+ # Whether the PR is a Draft.
149
+ # @return [Boolean]
150
+ #
151
+ def pr_draft?
152
+ pr_json["draft"] == true
153
+ end
154
+
155
+ # @!group PR Commit Metadata
156
+ # The branch to which the PR is going to be merged into.
157
+ # @return [String]
158
+ #
159
+ def branch_for_base
160
+ pr_json["base"]["ref"]
161
+ end
162
+
163
+ # @!group PR Commit Metadata
164
+ # The branch to which the PR is going to be merged from.
165
+ # @return [String]
166
+ #
167
+ def branch_for_head
168
+ pr_json["head"]["ref"]
169
+ end
170
+
171
+ # @!group PR Commit Metadata
172
+ # The base commit to which the PR is going to be merged as a parent.
173
+ # @return [String]
174
+ #
175
+ def base_commit
176
+ pr_json["base"]["sha"]
177
+ end
178
+
179
+ # @!group PR Commit Metadata
180
+ # The head commit to which the PR is requesting to be merged from.
181
+ # @return [String]
182
+ #
183
+ def head_commit
184
+ pr_json["head"]["sha"]
185
+ end
186
+
187
+ # @!group GitHub Misc
188
+ # The hash that represents the PR's JSON. For an example of what this looks like
189
+ # see the [Danger Fixture'd one](https://raw.githubusercontent.com/danger/danger/master/spec/fixtures/github_api/pr_response.json).
190
+ # @return [Hash]
191
+ #
192
+ def pr_json
193
+ @github.pr_json
194
+ end
195
+
196
+ # @!group GitHub Misc
197
+ # Provides access to the GitHub API client used inside Danger. Making
198
+ # it easy to use the GitHub API inside a Dangerfile.
199
+ # @return [Octokit::Client]
200
+ def api
201
+ @github.client
202
+ end
203
+
204
+ # @!group PR Content
205
+ # The unified diff produced by Github for this PR
206
+ # see [Unified diff](https://en.wikipedia.org/wiki/Diff_utility#Unified_format)
207
+ # @return [String]
208
+ def pr_diff
209
+ @github.pr_diff
210
+ end
211
+
212
+ # @!group GitHub Misc
213
+ # Returns a list of HTML anchors for a file, or files in the head repository. An example would be:
214
+ # `<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.
215
+ # @param [String or Array<String>] paths
216
+ # A list of strings to convert to github anchors
217
+ # @param [Bool] full_path
218
+ # Shows the full path as the link's text, defaults to `true`.
219
+ #
220
+ # @return [String]
221
+ def html_link(paths, full_path: true)
222
+ paths = [paths] unless paths.kind_of?(Array)
223
+ commit = head_commit
224
+ repo = pr_json["head"]["repo"]["html_url"]
225
+
226
+ paths = paths.map do |path|
227
+ url_path = path.start_with?("/") ? path : "/#{path}"
228
+ text = full_path ? path : File.basename(path)
229
+ create_link("#{repo}/blob/#{commit}#{url_path}", text)
230
+ end
231
+
232
+ return paths.first if paths.count < 2
233
+
234
+ paths.first(paths.count - 1).join(", ") + " & " + paths.last
235
+ end
236
+
237
+ # @!group GitHub Misc
238
+ # Use to ignore inline messages which lay outside a diff's range, thereby not posting them in the main comment.
239
+ # You can set hash to change behavior per each kinds. (ex. `{warning: true, error: false}`)
240
+ # @param [Bool] or [Hash<Symbol, Bool>] dismiss
241
+ # Ignore out of range inline messages, defaults to `true`
242
+ #
243
+ # @return [void]
244
+ def dismiss_out_of_range_messages(dismiss = true)
245
+ if dismiss.kind_of?(Hash)
246
+ @github.dismiss_out_of_range_messages = dismiss
247
+ elsif dismiss.kind_of?(TrueClass)
248
+ @github.dismiss_out_of_range_messages = true
249
+ elsif dismiss.kind_of?(FalseClass)
250
+ @github.dismiss_out_of_range_messages = false
251
+ end
252
+ end
253
+
254
+ %i(title body author labels json).each do |suffix|
255
+ alias_method "mr_#{suffix}".to_sym, "pr_#{suffix}".to_sym
256
+ end
257
+
258
+ private
259
+
260
+ def create_link(href, text)
261
+ "<a href='#{href}'>#{text}</a>"
262
+ end
263
+ end
264
+ end