danger 2.1.6 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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