danger 2.1.6 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8d17d6a72e1a01c0fd06eb51f4631d2aece6a2ba
4
- data.tar.gz: fedf8ba1df3832bfba137371bf9e6ba57ba3ca25
3
+ metadata.gz: 8b7dea6bfd9bb27c05eaf6b537f6aff16e682155
4
+ data.tar.gz: 7ec69647d0f2d827487aefd873be938fbf11a96f
5
5
  SHA512:
6
- metadata.gz: 1ac508837ab5be9fa9004e729c2c86887377e301263e9d4bf05343858852992b66e23d582d32ac7d51e59d6e121f4136d4df481b129d43c30e51626be9b16e6e
7
- data.tar.gz: 830458880fdaa3afb529fac362158a2210abd49d0b9a914e967007ee8a63226d40e5a98402e2639727048f23543aac8ac285fbe8eb28b284473b64cfa4319761
6
+ metadata.gz: 9f724c97c2cd8bd49e6c507ebf9d05885781600e0539c0fa06af9665247b61beec387b2386cfff32ce1a5c51a3392904bc9e288bb742029f98cd049b2566fd47
7
+ data.tar.gz: 3e17b9025db0a22d3af9f2c53be2e80a3bcd33e86cc24e8cb32ba810bcfe04456ca46385aa0e47abfbc870f1daf40db74845f948aa7e05fe1b28b4079e4b7b72
@@ -14,9 +14,16 @@ module Danger
14
14
  #
15
15
  # ### Token Setup
16
16
  #
17
+ # #### GitHub
18
+ #
17
19
  # As this is self-hosted, you will need to add the `DANGER_GITHUB_API_TOKEN` to your build user's ENV. The alternative
18
20
  # is to pass in the token as a prefix to the command `DANGER_GITHUB_API_TOKEN="123" bundle exec danger`.
19
21
  #
22
+ # #### GitLab
23
+ #
24
+ # As this is self-hosted, you will need to add the `DANGER_GITLAB_API_TOKEN` to your build user's ENV. The alternative
25
+ # is to pass in the token as a prefix to the command `DANGER_GITLAB_API_TOKEN="123" bundle exec danger`.
26
+ #
20
27
  class Buildkite < CI
21
28
  def self.validates_as_ci?(env)
22
29
  env.key? "BUILDKITE"
@@ -36,7 +43,7 @@ module Danger
36
43
  end
37
44
 
38
45
  def supported_request_sources
39
- @supported_request_sources ||= [Danger::RequestSources::GitHub]
46
+ @supported_request_sources ||= [Danger::RequestSources::GitHub, Danger::RequestSources::GitLab]
40
47
  end
41
48
  end
42
49
  end
@@ -29,7 +29,7 @@ module Danger
29
29
  end
30
30
 
31
31
  def supported_request_sources
32
- @supported_request_sources ||= [Danger::RequestSources::GitHub]
32
+ @supported_request_sources ||= [Danger::RequestSources::GitHub, Danger::RequestSources::GitLab]
33
33
  end
34
34
 
35
35
  def initialize(env)
@@ -0,0 +1,32 @@
1
+ # http://docs.gitlab.com/ce/ci/variables/README.html
2
+ require "uri"
3
+
4
+ module Danger
5
+ # ### CI Setup
6
+ # GitLab CI is currently not supported because GitLab's runners don't expose
7
+ # the required values in the environment. Namely CI_MERGE_REQUEST_ID does not
8
+ # exist as of yet, however there is an
9
+ # [MR](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5698) fixing this.
10
+ # If that has been merged and you are using either gitlab.com or a release
11
+ # with that change this CISource will wokr.
12
+ #
13
+ class GitLabCI < CI
14
+ def self.validates_as_ci?(env)
15
+ env.key? "GITLAB_CI"
16
+ end
17
+
18
+ def self.validates_as_pr?(env)
19
+ exists = ["CI_MERGE_REQUEST_ID", "CI_PROJECT_ID", "GITLAB_CI"].all? { |x| env[x] }
20
+ exists && env["CI_MERGE_REQUEST_ID"].to_i > 0
21
+ end
22
+
23
+ def supported_request_sources
24
+ @supported_request_sources ||= [Danger::RequestSources::GitLab]
25
+ end
26
+
27
+ def initialize(env)
28
+ self.repo_slug = env["CI_PROJECT_ID"]
29
+ self.pull_request_id = env["CI_MERGE_REQUEST_ID"]
30
+ end
31
+ end
32
+ end
@@ -5,9 +5,17 @@ module Danger
5
5
  # https://jenkins-ci.org
6
6
 
7
7
  # ### CI Setup
8
- #
9
8
  # Ah Jenkins, so many memories. So, if you're using Jenkins, you're hosting your own environment. You
10
- # will want to be using the [GitHub pull request builder plugin](https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin)
9
+ #
10
+ # #### GitHub
11
+ # You will want to be using the [GitHub pull request builder plugin](https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin)
12
+ # in order to ensure that you have the build environment set up for PR integration.
13
+ #
14
+ # With that set up, you can edit your job to add `bundle exec danger` at the build action.
15
+ #
16
+ # ### GitLab
17
+ #
18
+ # You will want to be using the [GitLabe Plugin](https://github.com/jenkinsci/gitlab-plugin)
11
19
  # in order to ensure that you have the build environment set up for PR integration.
12
20
  #
13
21
  # With that set up, you can edit your job to add `bundle exec danger` at the build action.
@@ -22,19 +30,28 @@ module Danger
22
30
  end
23
31
 
24
32
  def self.validates_as_pr?(env)
25
- ["ghprbPullId"].all? { |x| env[x] }
33
+ id = pull_request_id(env)
34
+ !id.nil? && !id.empty?
26
35
  end
27
36
 
28
37
  def supported_request_sources
29
- @supported_request_sources ||= [Danger::RequestSources::GitHub]
38
+ @supported_request_sources ||= [Danger::RequestSources::GitHub, Danger::RequestSources::GitLab]
30
39
  end
31
40
 
32
41
  def initialize(env)
33
42
  self.repo_url = env["GIT_URL"]
34
- self.pull_request_id = env["ghprbPullId"]
43
+ self.pull_request_id = self.class.pull_request_id(env)
35
44
 
36
45
  repo_matches = self.repo_url.match(%r{([\/:])([^\/]+\/[^\/.]+)(?:.git)?$})
37
46
  self.repo_slug = repo_matches[2] unless repo_matches.nil?
38
47
  end
48
+
49
+ def self.pull_request_id(env)
50
+ if env["ghprPullId"]
51
+ env["ghprPullId"]
52
+ else
53
+ env["gitlabMergeRequestId"]
54
+ end
55
+ end
39
56
  end
40
57
  end
@@ -9,34 +9,71 @@ module Danger
9
9
  #
10
10
  # ### Token + Environment Setup
11
11
  #
12
+ # #### GitHub
13
+ #
12
14
  # As this is self-hosted, you will need to add the `DANGER_GITHUB_API_TOKEN` to your build user's ENV. The alternative
13
15
  # is to pass in the token as a prefix to the command `DANGER_GITHUB_API_TOKEN="123" bundle exec danger`.
14
16
  #
15
17
  # However, you will need to find a way to add the environment vars: `GITHUB_REPO_SLUG`, `GITHUB_PULL_REQUEST_ID` and
16
18
  # `GITHUB_REPO_URL`. These are not added by default. You could do this via the GitHub API potentially.
17
19
  #
20
+ # #### GitLab
21
+ #
22
+ # As this is self-hosted, you will need to add the `DANGER_GITLAB_API_TOKEN` to your build user's ENV. The alternative
23
+ # is to pass in the token as a prefix to the command `DANGER_GITLAB_API_TOKEN="123" bundle exec danger`.
24
+ #
25
+ # However, you will need to find a way to add the environment vars: `GITLAB_REPO_SLUG`, `GITLAB_PULL_REQUEST_ID` and
26
+ # `GITLAB_REPO_URL`. These are not added by default. You could do this via the GitLab API potentially.
27
+ #
18
28
  # We would love some advice on improving this setup.
19
29
  #
20
30
  class TeamCity < CI
31
+ class << self
32
+ def validates_as_github_pr?(env)
33
+ ["GITHUB_PULL_REQUEST_ID", "GITHUB_REPO_URL", "GITHUB_REPO_URL"].all? { |x| env[x] && !env[x].empty? }
34
+ end
35
+
36
+ def validates_as_gitlab_pr?(env)
37
+ ["GITLAB_REPO_SLUG", "GITLAB_PULL_REQUEST_ID", "GITLAB_REPO_URL"].all? { |x| env[x] && !env[x].empty? }
38
+ end
39
+ end
40
+
21
41
  def self.validates_as_ci?(env)
22
42
  env.key? "TEAMCITY_VERSION"
23
43
  end
24
44
 
25
45
  def self.validates_as_pr?(env)
26
- ["GITHUB_PULL_REQUEST_ID", "GITHUB_REPO_URL", "GITHUB_REPO_SLUG"].all? { |x| env[x] }
46
+ validates_as_github_pr?(env) || validates_as_gitlab_pr?(env)
27
47
  end
28
48
 
29
49
  def supported_request_sources
30
- @supported_request_sources ||= [Danger::RequestSources::GitHub]
50
+ @supported_request_sources ||= [Danger::RequestSources::GitHub, Danger::RequestSources::GitLab]
31
51
  end
32
52
 
33
53
  def initialize(env)
34
54
  # NB: Unfortunately TeamCity doesn't provide these variables
35
55
  # automatically so you have to add these variables manually to your
36
56
  # project or build configuration
57
+
58
+ if self.class.validates_as_github_pr?(env)
59
+ extract_github_variables!(env)
60
+ elsif self.class.validates_as_gitlab_pr?(env)
61
+ extract_gitlab_variables!(env)
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def extract_github_variables!(env)
37
68
  self.repo_slug = env["GITHUB_REPO_SLUG"]
38
69
  self.pull_request_id = env["GITHUB_PULL_REQUEST_ID"].to_i
39
70
  self.repo_url = env["GITHUB_REPO_URL"]
40
71
  end
72
+
73
+ def extract_gitlab_variables!(env)
74
+ self.repo_slug = env["GITLAB_REPO_SLUG"]
75
+ self.pull_request_id = env["GITLAB_PULL_REQUEST_ID"].to_i
76
+ self.repo_url = env["GITLAB_REPO_URL"]
77
+ end
41
78
  end
42
79
  end
@@ -36,7 +36,6 @@ module Danger
36
36
  parser = PluginParser.new(paths)
37
37
  parser.parse
38
38
 
39
- self.markdown = Kramdown::Document.new(text, input: "GFM").to_html
40
39
  self.json = JSON.parse(parser.to_json_string)
41
40
 
42
41
  template = File.join(Danger.gem_path, "lib/danger/plugin_support/templates/readme_table.html.erb")
@@ -4,7 +4,7 @@
4
4
  <thead>
5
5
  <tr>
6
6
  <th width="50"></th>
7
- <th width="100%" data-kind="<%= table[:name] %>">
7
+ <th width="100%" data-danger-table="true" data-kind="<%= table[:name] %>">
8
8
  <%- if table[:count] > 0 -%>
9
9
  <%= table[:count] %> <%= table[:name] %><%= "s" unless table[:count] == 1 %>
10
10
  <%- else -%>
@@ -0,0 +1,40 @@
1
+ <%- @tables.each do |table| -%>
2
+ <%- if table[:content].any? || table[:resolved].any? -%>
3
+ <table>
4
+ <thead>
5
+ <tr>
6
+ <th width="5%"></th>
7
+ <th width="95%" data-danger-table="true" data-kind="<%= table[:name] %>">
8
+ <%- if table[:count] > 0 -%>
9
+ <%= table[:count] %> <%= table[:name] %><%= "s" unless table[:count] == 1 %>
10
+ <%- else -%>
11
+ :white_check_mark: <%= random_compliment %>
12
+ <%- end -%>
13
+ </th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ <%- table[:content].each do |violation| -%>
18
+ <tr>
19
+ <td>:<%= table[:emoji] %>:</td>
20
+ <td data-sticky="<%= violation.sticky %>"><%= violation.message %></td>
21
+ </tr>
22
+ <%- end -%>
23
+ <%- table[:resolved].each do |message| -%>
24
+ <tr>
25
+ <td>:white_check_mark:</td>
26
+ <td data-sticky="true"><del><%= message %></del></td>
27
+ </tr>
28
+ <%- end -%>
29
+ </tbody>
30
+ </table>
31
+ <%- end -%>
32
+ <%- end -%>
33
+
34
+ <%- @markdowns.each do |current| -%>
35
+ <%= current %>
36
+ <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %>
37
+ <%- end -%>
38
+ <p align="right" data-meta="generated_by_<%= @danger_id %>">
39
+ Generated by :no_entry_sign: <a href="https://github.com/danger/danger/">danger</a>
40
+ </p>
@@ -4,9 +4,10 @@ require "danger/danger_core/dangerfile_dsl"
4
4
  require "danger/danger_core/standard_error"
5
5
 
6
6
  require "danger/danger_core/plugins/dangerfile_messaging_plugin"
7
- require "danger/danger_core/plugins/dangerfile_import_plugin"
7
+ require "danger/danger_core/plugins/dangerfile_danger_plugin"
8
8
  require "danger/danger_core/plugins/dangerfile_git_plugin"
9
9
  require "danger/danger_core/plugins/dangerfile_github_plugin"
10
+ require "danger/danger_core/plugins/dangerfile_gitlab_plugin"
10
11
 
11
12
  module Danger
12
13
  class Dangerfile
@@ -34,7 +35,7 @@ module Danger
34
35
 
35
36
  # The ones that everything would break without
36
37
  def self.essential_plugin_classes
37
- [DangerfileMessagingPlugin, DangerfileGitPlugin, DangerfileImportPlugin, DangerfileGitHubPlugin]
38
+ [DangerfileMessagingPlugin, DangerfileGitPlugin, DangerfileDangerPlugin, DangerfileGitHubPlugin, DangerfileGitLabPlugin]
38
39
  end
39
40
 
40
41
  # Both of these methods exist on all objects
@@ -22,8 +22,9 @@ module Danger
22
22
  RequestSources::RequestSource.available_request_sources.each do |klass|
23
23
  next unless self.ci_source.supports?(klass)
24
24
 
25
- request_source = klass.new(self.ci_source, ENV)
25
+ request_source = klass.new(self.ci_source, env)
26
26
  next unless request_source.validates_as_ci?
27
+ next unless request_source.validates_as_api_source?
27
28
  self.request_source = request_source
28
29
  end
29
30
 
@@ -38,14 +38,9 @@ module Danger
38
38
  ci_head = head || EnvironmentManager.danger_head_branch
39
39
  dm.env.scm.diff_for_folder(".", from: ci_base, to: ci_head)
40
40
 
41
+ # Parse the local Dangerfile
41
42
  dm.parse(Pathname.new(dangerfile_path))
42
43
 
43
- if dm.env.request_source.organisation && !dm.env.request_source.danger_repo? && (danger_repo = dm.env.request_source.fetch_danger_repo)
44
- url = dm.env.request_source.file_url(repository: danger_repo.name, path: "Dangerfile")
45
- path = dm.plugin.download(url)
46
- dm.parse(Pathname.new(path))
47
- end
48
-
49
44
  post_results(dm, danger_id)
50
45
  dm.print_results
51
46
  ensure
@@ -10,36 +10,36 @@ module Danger
10
10
  # @example Import a plugin available over HTTP
11
11
  #
12
12
  # device_grid = "https://raw.githubusercontent.com/fastlane/fastlane/master/danger-device_grid/lib/device_grid/plugin.rb"
13
- # plugin.import device_grid
13
+ # danger.import_plugin(device_grid)
14
14
  #
15
15
  # @example Import from a local file reference
16
16
  #
17
- # plugin.import "danger/plugins/watch_plugin.rb"
17
+ # danger.import_plugin("danger/plugins/watch_plugin.rb")
18
18
  #
19
19
  # @example Import all files inside a folder
20
20
  #
21
- # plugin.import "danger/plugins/*.rb"
21
+ # danger.import_plugin("danger/plugins/*.rb")
22
22
  #
23
23
  # @see danger/danger
24
24
  # @tags core, plugins
25
25
 
26
- class DangerfileImportPlugin < Plugin
26
+ class DangerfileDangerPlugin < Plugin
27
27
  # The instance name used in the Dangerfile
28
28
  # @return [String]
29
29
  #
30
30
  def self.instance_name
31
- "plugin"
31
+ "danger"
32
32
  end
33
33
 
34
- # @!group Plugins
35
- # Download a local or remote plugin and use it inside the Dangerfile.
34
+ # @!group Danger
35
+ # Download a local or remote plugin and make it usable inside the Dangerfile.
36
36
  #
37
37
  # @param [String] path_or_url
38
38
  # a local path or a https URL to the Ruby file to import
39
39
  # a danger plugin from.
40
40
  # @return [void]
41
41
  #
42
- def import(path_or_url)
42
+ def import_plugin(path_or_url)
43
43
  raise "`import` requires a string" unless path_or_url.kind_of?(String)
44
44
 
45
45
  if path_or_url.start_with?("http")
@@ -49,8 +49,25 @@ module Danger
49
49
  end
50
50
  end
51
51
 
52
+ # @!group Danger
53
+ # Download and execute a remote Dangerfile.
54
+ #
55
+ # @param [String] repo slug
56
+ # A slug that represents the repo where the Dangerfile is.
57
+ # @return [void]
58
+ #
59
+ def import_dangerfile(slug)
60
+ raise "`import` requires a string" unless slug.kind_of?(String)
61
+ org, repo = slug.split("/")
62
+ download_url = env.request_source.file_url(organisation: org, repository: repo, branch: "master", path: "Dangerfile")
63
+ local_path = import_url(download_url)
64
+ dangerfile.parse(Pathname.new(local_path))
65
+ end
66
+
67
+ private
68
+
52
69
  # @!group Plugins
53
- # Download a local or remote plugin or Dangerfile
70
+ # Download a local or remote plugin or Dangerfile.
54
71
  # This method will not import the file for you, use plugin.import instead
55
72
  #
56
73
  # @param [String] path_or_url
@@ -75,8 +92,6 @@ module Danger
75
92
  return path
76
93
  end
77
94
 
78
- private
79
-
80
95
  # @!group Plugins
81
96
  # Download a remote plugin and use it locally.
82
97
  #
@@ -14,7 +14,7 @@ module Danger
14
14
  #
15
15
  # @example Ensure that labels have been used on the PR
16
16
  #
17
- # fail "Please add labels to this PR" if github.labels.empty?
17
+ # fail "Please add labels to this PR" if github.pr_labels.empty?
18
18
  #
19
19
  # @example Check if a user is in a specific GitHub org, and message them if so
20
20
  #
@@ -205,6 +205,10 @@ module Danger
205
205
  paths.first(paths.count - 1).join(", ") + " & " + paths.last
206
206
  end
207
207
 
208
+ [:title, :body, :author, :labels, :json].each do |suffix|
209
+ alias_method "mr_#{suffix}".to_sym, "pr_#{suffix}".to_sym
210
+ end
211
+
208
212
  private
209
213
 
210
214
  def create_link(href, text)
@@ -0,0 +1,190 @@
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
+ # fail "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
+ # fail "Please provide a summary in the Pull Request description" if gitlab.mr_body.length < 5
26
+ #
27
+ # @example Only accept MRs to the develop branch
28
+ #
29
+ # fail "Please re-submit this MR to develop, we may have already fixed your issue." if gitlab.branch_for_base != "develop"
30
+ #
31
+ # @example Note when MRs don't reference a milestone, which goes away when it does
32
+ #
33
+ # has_milestone = gitlab.mr_json["milestone"] != nil
34
+ # warn("This MR does not refer to an existing milestone", sticky: false) unless has_milestone
35
+ #
36
+ # @example Note when a MR cannot be manually merged, which goes away when you can
37
+ #
38
+ # can_merge = gitlab.mr_json["mergeable"]
39
+ # warn("This MR cannot be merged yet.", sticky: false) unless can_merge
40
+ #
41
+ # @example Highlight when a celebrity makes a pull 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
+ #
57
+ # @see danger/danger
58
+ # @tags core, gitlab
59
+ #
60
+ class DangerfileGitLabPlugin < Plugin
61
+ # So that this init can fail.
62
+ def self.new(dangerfile)
63
+ return nil if dangerfile.env.request_source.class != Danger::RequestSources::GitLab
64
+ super
65
+ end
66
+
67
+ # The instance name used in the Dangerfile
68
+ # @return [String]
69
+ #
70
+ def self.instance_name
71
+ "gitlab"
72
+ end
73
+
74
+ def initialize(dangerfile)
75
+ super(dangerfile)
76
+
77
+ @gitlab = dangerfile.env.request_source
78
+ end
79
+
80
+ # @!group MR Metadata
81
+ # The title of the Merge Request
82
+ # @return [String]
83
+ #
84
+ def mr_title
85
+ @gitlab.mr_json.title.to_s
86
+ end
87
+
88
+ # @!group MR Metadata
89
+ # The body text of the Merge Request
90
+ # @return [String]
91
+ #
92
+ def mr_body
93
+ @gitlab.mr_json.description.to_s
94
+ end
95
+
96
+ # @!group MR Metadata
97
+ # The username of the author of the Merge Request
98
+ # @return [String]
99
+ #
100
+ def mr_author
101
+ @gitlab.mr_json.author.username.to_s
102
+ end
103
+
104
+ # @!group MR Metadata
105
+ # The labels assigned to the Merge Request
106
+ # @return [String]
107
+ #
108
+ def mr_labels
109
+ @gitlab.mr_json.labels
110
+ end
111
+
112
+ # @!group MR Commit Metadata
113
+ # The branch to which the MR is going to be merged into
114
+ # @return [String]
115
+ #
116
+ def branch_for_merge
117
+ @gitlab.mr_json.target_branch
118
+ end
119
+
120
+ # @!group MR Commit Metadata
121
+ # The base commit to which the MR is going to be merged as a parent
122
+ # @return [String]
123
+ #
124
+ def base_commit
125
+ @gitlab.base_commit
126
+ end
127
+
128
+ # @!group MR Commit Metadata
129
+ # The head commit to which the MR is requesting to be merged from
130
+ # @return [String]
131
+ #
132
+ def head_commit
133
+ @gitlab.commits_json.first.id
134
+ end
135
+
136
+ # @!group GitLab Misc
137
+ # The hash that represents the MR's JSON. See documentation for the
138
+ # structure [here](http://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr)
139
+ # @return [Hash]
140
+ #
141
+ def mr_json
142
+ @gitlab.mr_json.to_hash
143
+ end
144
+
145
+ # @!group GitLab Misc
146
+ # Provides access to the GitLab API client used inside Danger. Making
147
+ # it easy to use the GitLab API inside a Dangerfile.
148
+ # @return [GitLab::Client]
149
+ def api
150
+ @gitlab.client
151
+ end
152
+
153
+ # @!group GitLab Misc
154
+ # Returns a list of HTML anchors for a file, or files in the head repository. An example would be:
155
+ # `<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.
156
+ # @param [String or Array<String>] paths
157
+ # A list of strings to convert to gitlab anchors
158
+ # @param [Bool] full_path
159
+ # Shows the full path as the link's text, defaults to `true`.
160
+ #
161
+ # @return [String]
162
+ def html_link(paths, full_path: true)
163
+ paths = [paths] unless paths.kind_of?(Array)
164
+ commit = head_commit
165
+ same_repo = mr_json[:project_id] == mr_json[:source_project_id]
166
+ sender_repo = ci_source.repo_slug.split("/").first + "/" + mr_json[:author][:username]
167
+ repo = same_repo ? ci_source.repo_slug : sender_repo
168
+ host = @gitlab.host
169
+
170
+ paths = paths.map do |path|
171
+ url_path = path.start_with?("/") ? path : "/#{path}"
172
+ text = full_path ? path : File.basename(path)
173
+ create_link("https://#{host}/#{repo}/blob/#{commit}#{url_path}", text)
174
+ end
175
+
176
+ return paths.first if paths.count < 2
177
+ paths.first(paths.count - 1).join(", ") + " & " + paths.last
178
+ end
179
+
180
+ [:title, :body, :author, :labels, :json].each do |suffix|
181
+ alias_method "pr_#{suffix}".to_sym, "mr_#{suffix}".to_sym
182
+ end
183
+
184
+ private
185
+
186
+ def create_link(href, text)
187
+ "<a href='#{href}'>#{text}</a>"
188
+ end
189
+ end
190
+ end
@@ -28,8 +28,9 @@ module Danger
28
28
  tables = parse_tables_from_comment(comment)
29
29
  violations = {}
30
30
  tables.each do |table|
31
- next unless table =~ %r{<th width="100%"(.*?)</th>}im
32
- title = Regexp.last_match(1)
31
+ match = danger_table?(table)
32
+ next unless match
33
+ title = match[1]
33
34
  kind = table_kind_from_title(title)
34
35
  next unless kind
35
36
 
@@ -101,6 +102,44 @@ module Danger
101
102
  "Yay.", "Jolly good show.", "Good on 'ya.", "Nice work."]
102
103
  compliment.sample
103
104
  end
105
+
106
+ private
107
+
108
+ GITHUB_OLD_REGEX = %r{<th width="100%"(.*?)</th>}im
109
+ NEW_REGEX = %r{<th.*data-danger-table="true"(.*?)</th>}im
110
+
111
+ def danger_table?(table)
112
+ # The old GitHub specific method relied on
113
+ # the width of a `th` element to find the table
114
+ # title and determine if it was a danger table.
115
+ # The new method uses a more robust data-danger-table
116
+ # tag instead.
117
+ match = GITHUB_OLD_REGEX.match(table)
118
+ return match if match
119
+
120
+ return NEW_REGEX.match(table)
121
+ end
122
+
123
+ class Comment
124
+ attr_reader :id, :body
125
+
126
+ def initialize(id, body)
127
+ @id = id
128
+ @body = body
129
+ end
130
+
131
+ def self.from_github(comment)
132
+ self.new(comment[:id], comment[:body])
133
+ end
134
+
135
+ def self.from_gitlab(comment)
136
+ self.new(comment.id, comment.body)
137
+ end
138
+
139
+ def generated_by_danger?(danger_id)
140
+ body.include?("generated_by_#{danger_id}")
141
+ end
142
+ end
104
143
  end
105
144
  end
106
145
  end
@@ -16,11 +16,15 @@ module Danger
16
16
 
17
17
  Octokit.auto_paginate = true
18
18
  @token = @environment["DANGER_GITHUB_API_TOKEN"]
19
- if @environment["DANGER_GITHUB_API_HOST"]
20
- Octokit.api_endpoint = @environment["DANGER_GITHUB_API_HOST"]
19
+ if api_url
20
+ Octokit.api_endpoint = api_url
21
21
  end
22
22
  end
23
23
 
24
+ def validates_as_api_source?
25
+ @token && !@token.empty?
26
+ end
27
+
24
28
  def scm
25
29
  @scm ||= GitRepo.new
26
30
  end
@@ -29,6 +33,13 @@ module Danger
29
33
  @host = @environment["DANGER_GITHUB_HOST"] || "github.com"
30
34
  end
31
35
 
36
+ def api_url
37
+ # `DANGER_GITHUB_API_HOST` is the old name kept for legacy reasons and
38
+ # backwards compatibility. `DANGER_GITHUB_API_BASE_URL` is the new
39
+ # correctly named variable.
40
+ @environment["DANGER_GITHUB_API_HOST"] || @environment["DANGER_GITHUB_API_BASE_URL"]
41
+ end
42
+
32
43
  def client
33
44
  raise "No API token given, please provide one using `DANGER_GITHUB_API_TOKEN`" if !@token && !support_tokenless_auth
34
45
  @client ||= Octokit::Client.new(access_token: @token)
@@ -68,17 +79,20 @@ module Danger
68
79
  self.issue_json = client.get(href)
69
80
  end
70
81
 
82
+ def issue_comments
83
+ @comments ||= client.issue_comments(ci_source.repo_slug, ci_source.pull_request_id)
84
+ .map { |comment| Comment.from_github(comment) }
85
+ end
86
+
71
87
  # Sending data to GitHub
72
88
  def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger")
73
89
  comment_result = {}
74
-
75
- comments = client.issue_comments(ci_source.repo_slug, ci_source.pull_request_id)
76
- editable_comments = comments.select { |comment| danger_comment?(comment, danger_id) }
90
+ editable_comments = issue_comments.select { |comment| comment.generated_by_danger?(danger_id) }
77
91
 
78
92
  if editable_comments.empty?
79
93
  previous_violations = {}
80
94
  else
81
- comment = editable_comments.first[:body]
95
+ comment = editable_comments.first.body
82
96
  previous_violations = parse_comment(comment)
83
97
  end
84
98
 
@@ -97,7 +111,7 @@ module Danger
97
111
  if editable_comments.empty?
98
112
  comment_result = client.add_comment(ci_source.repo_slug, ci_source.pull_request_id, body)
99
113
  else
100
- original_id = editable_comments.first[:id]
114
+ original_id = editable_comments.first.id
101
115
  comment_result = client.update_comment(ci_source.repo_slug, original_id, body)
102
116
  end
103
117
  end
@@ -145,11 +159,10 @@ module Danger
145
159
 
146
160
  # Get rid of the previously posted comment, to only have the latest one
147
161
  def delete_old_comments!(except: nil, danger_id: "danger")
148
- comments = client.issue_comments(ci_source.repo_slug, ci_source.pull_request_id)
149
- comments.each do |comment|
150
- next unless danger_comment?(comment, danger_id)
151
- next if comment[:id] == except
152
- client.delete_comment(ci_source.repo_slug, comment[:id])
162
+ issue_comments.each do |comment|
163
+ next unless comment.generated_by_danger?(danger_id)
164
+ next if comment.id == except
165
+ client.delete_comment(ci_source.repo_slug, comment.id)
153
166
  end
154
167
  end
155
168
 
@@ -161,45 +174,11 @@ module Danger
161
174
  nil
162
175
  end
163
176
 
164
- # @return [Hash] with the information about the repo
165
- # returns nil if the repo is not available
166
- def fetch_repository(organisation: nil, repository: nil)
167
- organisation ||= self.organisation
168
- repository ||= self.ci_source.repo_slug.split("/").last
169
- self.client.repo("#{organisation}/#{repository}")
170
- rescue Octokit::NotFound
171
- nil # repo doesn't exist
172
- end
173
-
174
- # @return [Hash] with the information about the repo.
175
- # This will automatically detect if the repo is capitalised
176
- # returns nil if there is no danger repo
177
- def fetch_danger_repo(organisation: nil)
178
- data = nil
179
- data ||= fetch_repository(organisation: organisation, repository: DANGER_REPO_NAME.downcase)
180
- data ||= fetch_repository(organisation: organisation, repository: DANGER_REPO_NAME.capitalize)
181
- data
182
- end
183
-
184
- # @return [Bool] is this repo the danger repo of the org?
185
- def danger_repo?(organisation: nil, repository: nil)
186
- repo = fetch_repository(organisation: organisation, repository: repository)
187
- repo[:name].casecmp(DANGER_REPO_NAME).zero? || repo[:parent] && repo[:parent][:full_name] == "danger/danger"
188
- rescue
189
- false
190
- end
191
-
192
177
  # @return [String] A URL to the specific file, ready to be downloaded
193
178
  def file_url(organisation: nil, repository: nil, branch: "master", path: nil)
194
179
  organisation ||= self.organisation
195
180
  "https://raw.githubusercontent.com/#{organisation}/#{repository}/#{branch}/#{path}"
196
181
  end
197
-
198
- private
199
-
200
- def danger_comment?(comment, danger_id)
201
- comment[:body].include?("generated_by_#{danger_id}")
202
- end
203
182
  end
204
183
  end
205
184
  end
@@ -0,0 +1,133 @@
1
+ # coding: utf-8
2
+ require "gitlab"
3
+ require "danger/helpers/comments_helper"
4
+
5
+ module Danger
6
+ module RequestSources
7
+ class GitLab < RequestSource
8
+ include Danger::Helpers::CommentsHelper
9
+ attr_accessor :mr_json, :commits_json
10
+
11
+ def initialize(ci_source, environment)
12
+ self.ci_source = ci_source
13
+ self.environment = environment
14
+
15
+ @token = @environment["DANGER_GITLAB_API_TOKEN"]
16
+ end
17
+
18
+ def client
19
+ token = @environment["DANGER_GITLAB_API_TOKEN"]
20
+ raise "No API token given, please provide one using `DANGER_GITLAB_API_TOKEN`" unless token
21
+ params = { private_token: token }
22
+ params[:endpoint] = endpoint
23
+
24
+ @client ||= Gitlab.client(params)
25
+ end
26
+
27
+ def validates_as_api_source?
28
+ @token && !@token.empty?
29
+ end
30
+
31
+ def scm
32
+ @scm ||= GitRepo.new
33
+ end
34
+
35
+ def endpoint
36
+ @endpoint ||= @environment["DANGER_GITLAB_API_BASE_URL"] || "https://gitlab.com/api/v3"
37
+ end
38
+
39
+ def host
40
+ @host ||= @environment["DANGER_GITLAB_HOST"] || "gitlab.com"
41
+ end
42
+
43
+ def base_commit
44
+ first_commit_in_branch = self.commits_json.last.id
45
+ @base_comit ||= self.scm.exec "rev-parse #{first_commit_in_branch}^1"
46
+ end
47
+
48
+ def mr_comments
49
+ @comments ||= client.merge_request_comments(escaped_ci_slug, ci_source.pull_request_id)
50
+ .map { |comment| Comment.from_gitlab(comment) }
51
+ end
52
+
53
+ def escaped_ci_slug
54
+ @escaped_ci_slug ||= CGI.escape(ci_source.repo_slug)
55
+ end
56
+
57
+ def setup_danger_branches
58
+ head_commit = self.scm.head_commit
59
+
60
+ # Next, we want to ensure that we have a version of the current branch at a known location
61
+ self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{base_commit}"
62
+
63
+ # OK, so we want to ensure that we have a known head branch, this will always represent
64
+ # the head of the PR ( e.g. the most recent commit that will be merged. )
65
+ self.scm.exec "branch #{EnvironmentManager.danger_head_branch} #{head_commit}"
66
+ end
67
+
68
+ def fetch_details
69
+ self.mr_json = client.merge_request(escaped_ci_slug, self.ci_source.pull_request_id)
70
+ self.commits_json = client.merge_request_commits(escaped_ci_slug, self.ci_source.pull_request_id)
71
+ self.ignored_violations = ignored_violations_from_pr(self.mr_json)
72
+ end
73
+
74
+ def ignored_violations_from_pr(mr_json)
75
+ pr_body = mr_json.description
76
+ return [] if pr_body.nil?
77
+ pr_body.chomp.scan(/>\s*danger\s*:\s*ignore\s*"(.*)"/i).flatten
78
+ end
79
+
80
+ def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger")
81
+ editable_comments = mr_comments.select { |comment| comment.generated_by_danger?(danger_id) }
82
+
83
+ if editable_comments.empty?
84
+ previous_violations = {}
85
+ else
86
+ comment = editable_comments.first.body
87
+ previous_violations = parse_comment(comment)
88
+ end
89
+
90
+ if previous_violations.empty? && (warnings + errors + messages + markdowns).empty?
91
+ # Just remove the comment, if there"s nothing to say.
92
+ delete_old_comments!(danger_id: danger_id)
93
+ else
94
+ body = generate_comment(warnings: warnings,
95
+ errors: errors,
96
+ messages: messages,
97
+ markdowns: markdowns,
98
+ previous_violations: previous_violations,
99
+ danger_id: danger_id,
100
+ template: "gitlab")
101
+
102
+ if editable_comments.empty?
103
+ client.create_merge_request_comment(
104
+ escaped_ci_slug, ci_source.pull_request_id, body
105
+ )
106
+ else
107
+ original_id = editable_comments.first.id
108
+ client.edit_merge_request_comment(
109
+ escaped_ci_slug, ci_source.pull_request_id, original_id, body
110
+ )
111
+ end
112
+ end
113
+ end
114
+
115
+ def delete_old_comments!(except: nil, danger_id: "danger")
116
+ mr_comments.each do |comment|
117
+ next unless comment.generated_by_danger?(danger_id)
118
+ next if comment.id == except
119
+ client.delete_merge_request_comment(
120
+ escaped_ci_slug,
121
+ ci_source.pull_request_id,
122
+ comment.id
123
+ )
124
+ end
125
+ end
126
+
127
+ # @return [String] The organisation name, is nil if it can't be detected
128
+ def organisation
129
+ nil # TODO: Implement this
130
+ end
131
+ end
132
+ end
133
+ end
@@ -23,6 +23,10 @@ module Danger
23
23
  !!self.scm.origins.match(%r{#{Regexp.escape self.host}(:|/)(?<repo_slug>.+/.+?)(?:\.git)?$})
24
24
  end
25
25
 
26
+ def validates_as_api_source?
27
+ raise "Subclass and overwrite validates_as_api_source?"
28
+ end
29
+
26
30
  def scm
27
31
  @scm ||= nil
28
32
  end
@@ -51,18 +55,6 @@ module Danger
51
55
  raise "Subclass and overwrite organisation"
52
56
  end
53
57
 
54
- def fetch_repository(_organisation: nil, _repository: nil)
55
- raise "Subclass and overwrite fetch_repository"
56
- end
57
-
58
- def fetch_danger_repo(_organisation: nil)
59
- raise "Subclass and overwrite fetch_danger_repo"
60
- end
61
-
62
- def danger_repo?(_organisation: nil, _repository: nil)
63
- raise "Subclass and overwrite danger_repo?"
64
- end
65
-
66
58
  def file_url(_organisation: nil, _repository: nil, _branch: "master", _path: nil)
67
59
  raise "Subclass and overwrite file_url"
68
60
  end
@@ -1,4 +1,4 @@
1
1
  module Danger
2
- VERSION = "2.1.6".freeze
2
+ VERSION = "3.0.0".freeze
3
3
  DESCRIPTION = "Automate your PR etiquette.".freeze
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: danger
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.6
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Orta Therox
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-08-19 00:00:00.000000000 Z
12
+ date: 2016-08-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: claide
@@ -73,14 +73,14 @@ dependencies:
73
73
  requirements:
74
74
  - - "~>"
75
75
  - !ruby/object:Gem::Version
76
- version: '0'
76
+ version: '0.9'
77
77
  type: :runtime
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - "~>"
82
82
  - !ruby/object:Gem::Version
83
- version: '0'
83
+ version: '0.9'
84
84
  - !ruby/object:Gem::Dependency
85
85
  name: faraday-http-cache
86
86
  requirement: !ruby/object:Gem::Requirement
@@ -151,6 +151,20 @@ dependencies:
151
151
  - - "~>"
152
152
  - !ruby/object:Gem::Version
153
153
  version: '0.1'
154
+ - !ruby/object:Gem::Dependency
155
+ name: gitlab
156
+ requirement: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - "~>"
159
+ - !ruby/object:Gem::Version
160
+ version: 3.7.0
161
+ type: :runtime
162
+ prerelease: false
163
+ version_requirements: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - "~>"
166
+ - !ruby/object:Gem::Version
167
+ version: 3.7.0
154
168
  - !ruby/object:Gem::Dependency
155
169
  name: bundler
156
170
  requirement: !ruby/object:Gem::Requirement
@@ -319,6 +333,20 @@ dependencies:
319
333
  - - "~>"
320
334
  - !ruby/object:Gem::Version
321
335
  version: '1.2'
336
+ - !ruby/object:Gem::Dependency
337
+ name: simplecov
338
+ requirement: !ruby/object:Gem::Requirement
339
+ requirements:
340
+ - - "~>"
341
+ - !ruby/object:Gem::Version
342
+ version: 0.12.0
343
+ type: :development
344
+ prerelease: false
345
+ version_requirements: !ruby/object:Gem::Requirement
346
+ requirements:
347
+ - - "~>"
348
+ - !ruby/object:Gem::Version
349
+ version: 0.12.0
322
350
  description: Stop Saying 'You Forgot To…' in Code Review
323
351
  email:
324
352
  - orta.therox@gmail.com
@@ -339,6 +367,7 @@ files:
339
367
  - lib/danger/ci_source/circle.rb
340
368
  - lib/danger/ci_source/circle_api.rb
341
369
  - lib/danger/ci_source/drone.rb
370
+ - lib/danger/ci_source/gitlab_ci.rb
342
371
  - lib/danger/ci_source/jenkins.rb
343
372
  - lib/danger/ci_source/local_git_repo.rb
344
373
  - lib/danger/ci_source/semaphore.rb
@@ -356,15 +385,17 @@ files:
356
385
  - lib/danger/commands/runner.rb
357
386
  - lib/danger/commands/systems.rb
358
387
  - lib/danger/comment_generators/github.md.erb
388
+ - lib/danger/comment_generators/gitlab.md.erb
359
389
  - lib/danger/core_ext/file_list.rb
360
390
  - lib/danger/core_ext/string.rb
361
391
  - lib/danger/danger_core/dangerfile.rb
362
392
  - lib/danger/danger_core/dangerfile_dsl.rb
363
393
  - lib/danger/danger_core/environment_manager.rb
364
394
  - lib/danger/danger_core/executor.rb
395
+ - lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb
365
396
  - lib/danger/danger_core/plugins/dangerfile_git_plugin.rb
366
397
  - lib/danger/danger_core/plugins/dangerfile_github_plugin.rb
367
- - lib/danger/danger_core/plugins/dangerfile_import_plugin.rb
398
+ - lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb
368
399
  - lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb
369
400
  - lib/danger/danger_core/standard_error.rb
370
401
  - lib/danger/danger_core/violation.rb
@@ -375,6 +406,7 @@ files:
375
406
  - lib/danger/plugin_support/plugin_parser.rb
376
407
  - lib/danger/plugin_support/templates/readme_table.html.erb
377
408
  - lib/danger/request_source/github.rb
409
+ - lib/danger/request_source/gitlab.rb
378
410
  - lib/danger/request_source/request_source.rb
379
411
  - lib/danger/scm_source/git_repo.rb
380
412
  - lib/danger/version.rb