pronto 0.10.0 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,7 +10,7 @@ module Pronto
10
10
 
11
11
  approve_pull_request(comments.count, additions.count, client) if defined?(self.approve_pull_request)
12
12
 
13
- "#{additions.count} Pronto messages posted to #{pretty_name}"
13
+ "#{additions.count} Pronto messages posted to #{pretty_name} (#{existing.count} existing)"
14
14
  end
15
15
 
16
16
  def client_module
@@ -0,0 +1,24 @@
1
+ require_relative 'github_status_formatter/status_builder'
2
+
3
+ module Pronto
4
+ module Formatter
5
+ class GithubCombinedStatusFormatter
6
+ def format(messages, repo, _)
7
+ client = Github.new(repo)
8
+ head = repo.head_commit_sha
9
+
10
+ create_status(client, head, messages.uniq || [])
11
+ end
12
+
13
+ private
14
+
15
+ def create_status(client, sha, messages)
16
+ builder = GithubStatusFormatter::StatusBuilder.new(nil, messages)
17
+ status = Status.new(sha, builder.state,
18
+ 'pronto', builder.description)
19
+
20
+ client.create_commit_status(status)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -10,7 +10,7 @@ module Pronto
10
10
  end
11
11
 
12
12
  def submit_comments(client, comments)
13
- client.create_pull_request_review(comments)
13
+ client.publish_pull_request_comments(comments)
14
14
  rescue Octokit::UnprocessableEntity, HTTParty::Error => e
15
15
  $stderr.puts "Failed to post: #{e.message}"
16
16
  end
@@ -0,0 +1,29 @@
1
+ module Pronto
2
+ module Formatter
3
+ class GitlabMergeRequestReviewFormatter < PullRequestFormatter
4
+ def client_module
5
+ Gitlab
6
+ end
7
+
8
+ def pretty_name
9
+ 'Gitlab'
10
+ end
11
+
12
+ def existing_comments(_, client, repo)
13
+ sha = repo.head_commit_sha
14
+ comments = client.pull_comments(sha)
15
+ grouped_comments(comments)
16
+ end
17
+
18
+ def submit_comments(client, comments)
19
+ client.create_pull_request_review(comments)
20
+ rescue => e
21
+ $stderr.puts "Failed to post: #{e.message}"
22
+ end
23
+
24
+ def line_number(message, _)
25
+ message.line.line.new_lineno if message.line
26
+ end
27
+ end
28
+ end
29
+ end
@@ -3,7 +3,7 @@ require 'json'
3
3
  module Pronto
4
4
  module Formatter
5
5
  class JsonFormatter < Base
6
- def format(messages, _, _)
6
+ def format(messages, _repo, _patches)
7
7
  messages.map do |message|
8
8
  lineno = message.line.new_lineno if message.line
9
9
 
@@ -1,7 +1,7 @@
1
1
  module Pronto
2
2
  module Formatter
3
3
  class NullFormatter < Base
4
- def format(_, _, _); end
4
+ def format(_messages, _repo, _patches); end
5
5
  end
6
6
  end
7
7
  end
@@ -3,7 +3,7 @@ require 'pronto/formatter/text_message_decorator'
3
3
  module Pronto
4
4
  module Formatter
5
5
  class TextFormatter < Base
6
- def format(messages, _, _)
6
+ def format(messages, _repo, _patches)
7
7
  messages.map do |message|
8
8
  message_format = config.message_format(self.class.name)
9
9
  message_data = TextMessageDecorator.new(message).to_h
@@ -37,8 +37,6 @@ module Pronto
37
37
  repo.path.join(new_file_path)
38
38
  end
39
39
 
40
- private
41
-
42
40
  def new_file_path
43
41
  delta.new_file[:path]
44
42
  end
@@ -13,6 +13,18 @@ module Pronto
13
13
  [head_commit_sha, @repo.index.diff(options)]
14
14
  when :staged
15
15
  [head_commit_sha, head.diff(@repo.index, options)]
16
+ when :workdir
17
+ [
18
+ head_commit_sha,
19
+ @repo.diff_workdir(
20
+ head,
21
+ {
22
+ include_untracked: true,
23
+ include_untracked_content: true,
24
+ recurse_untracked_dirs: true
25
+ }.merge(options || {})
26
+ )
27
+ ]
16
28
  else
17
29
  merge_base = merge_base(commit)
18
30
  patches = @repo.diff(merge_base, head, options)
@@ -38,7 +50,7 @@ module Pronto
38
50
 
39
51
  def commits_until(sha)
40
52
  result = []
41
- @repo.walk(head, Rugged::SORT_TOPO).take_while do |commit|
53
+ @repo.walk(head_commit_sha, Rugged::SORT_TOPO).take_while do |commit|
42
54
  result << commit.oid
43
55
  !commit.oid.start_with?(sha)
44
56
  end
@@ -46,7 +58,7 @@ module Pronto
46
58
  end
47
59
 
48
60
  def path
49
- Pathname.new(@repo.path).parent
61
+ Pathname.new(@repo.workdir).cleanpath
50
62
  end
51
63
 
52
64
  def blame(path, lineno)
data/lib/pronto/github.rb CHANGED
@@ -45,17 +45,12 @@ module Pronto
45
45
  end
46
46
  end
47
47
 
48
- def create_pull_request_review(comments)
49
- return if comments.empty?
50
-
51
- options = {
52
- event: 'COMMENT',
53
- accept: 'application/vnd.github.v3.diff+json', # https://developer.github.com/v3/pulls/reviews/#create-a-pull-request-review
54
- comments: comments.map do |c|
55
- { path: c.path, position: c.position, body: c.body }
56
- end
57
- }
58
- client.create_pull_request_review(slug, pull_id, options)
48
+ def publish_pull_request_comments(comments)
49
+ comments_left = comments.clone
50
+ while comments_left.any?
51
+ comments_to_publish = comments_left.slice!(0, warnings_per_review)
52
+ create_pull_request_review(comments_to_publish)
53
+ end
59
54
  end
60
55
 
61
56
  def create_commit_status(status)
@@ -68,6 +63,21 @@ module Pronto
68
63
 
69
64
  private
70
65
 
66
+ def create_pull_request_review(comments)
67
+ options = {
68
+ event: @config.github_review_type,
69
+ accept: 'application/vnd.github.v3.diff+json', # https://developer.github.com/v3/pulls/reviews/#create-a-pull-request-review
70
+ comments: comments.map do |comment|
71
+ {
72
+ path: comment.path,
73
+ position: comment.position,
74
+ body: comment.body
75
+ }
76
+ end
77
+ }
78
+ client.create_pull_request_review(slug, pull_id, options)
79
+ end
80
+
71
81
  def slug
72
82
  return @config.github_slug if @config.github_slug
73
83
  @slug ||= begin
@@ -103,5 +113,9 @@ module Pronto
103
113
  @github_pull.pull_by_commit(@repo.head_commit_sha)
104
114
  end
105
115
  end
116
+
117
+ def warnings_per_review
118
+ @warnings_per_review ||= @config.warnings_per_review
119
+ end
106
120
  end
107
121
  end
data/lib/pronto/gitlab.rb CHANGED
@@ -2,12 +2,49 @@ module Pronto
2
2
  class Gitlab < Client
3
3
  def commit_comments(sha)
4
4
  @comment_cache[sha.to_s] ||= begin
5
- client.commit_comments(slug, sha, per_page: 500).map do |comment|
5
+ client.commit_comments(slug, sha).auto_paginate.map do |comment|
6
6
  Comment.new(sha, comment.note, comment.path, comment.line)
7
7
  end
8
8
  end
9
9
  end
10
10
 
11
+ def pull_comments(sha)
12
+ @comment_cache["#{slug}/#{pull_id}"] ||= begin
13
+ arr = []
14
+ client.merge_request_discussions(slug, pull_id).auto_paginate.each do |comment|
15
+ comment.notes.each do |note|
16
+ next unless note['position']
17
+
18
+ arr << Comment.new(
19
+ sha,
20
+ note['body'],
21
+ note['position']['new_path'],
22
+ note['position']['new_line']
23
+ )
24
+ end
25
+ end
26
+ arr
27
+ end
28
+ end
29
+
30
+ def create_pull_request_review(comments)
31
+ return if comments.empty?
32
+
33
+ comments.each do |comment|
34
+ options = {
35
+ body: comment.body,
36
+ position: position_sha.dup.merge(
37
+ new_path: comment.path,
38
+ position_type: 'text',
39
+ new_line: comment.position,
40
+ old_line: nil,
41
+ )
42
+ }
43
+
44
+ client.create_merge_request_discussion(slug, pull_id, options)
45
+ end
46
+ end
47
+
11
48
  def create_commit_comment(comment)
12
49
  @config.logger.log("Creating commit comment on #{comment.sha}")
13
50
  client.create_commit_comment(slug, comment.sha, comment.body,
@@ -17,6 +54,15 @@ module Pronto
17
54
 
18
55
  private
19
56
 
57
+ def position_sha
58
+ # Better to get those informations from Gitlab API directly than trying to look for them here.
59
+ # (FYI you can't use `pull` method because index api does not contains those informations)
60
+ @position_sha ||= begin
61
+ data = client.merge_request(slug, pull_id)
62
+ data.diff_refs.to_h
63
+ end
64
+ end
65
+
20
66
  def slug
21
67
  return @config.gitlab_slug if @config.gitlab_slug
22
68
  @slug ||= begin
@@ -27,11 +73,24 @@ module Pronto
27
73
  end
28
74
  end
29
75
 
76
+ def pull_id
77
+ env_pull_id || raise(Pronto::Error, "Unable to determine merge request id. Specify either `PRONTO_PULL_REQUEST_ID` or `CI_MERGE_REQUEST_IID`.")
78
+ end
79
+
80
+ def env_pull_id
81
+ pull_request = super
82
+
83
+ pull_request ||= ENV['CI_MERGE_REQUEST_IID']
84
+ pull_request.to_i if pull_request
85
+ end
86
+
30
87
  def slug_regex(url)
31
88
  if url =~ %r{^ssh:\/\/}
32
89
  %r{.*#{host}(:[0-9]+)?(:|\/)(?<slug>.*).git}
33
- else
90
+ elsif url =~ /#{host}/
34
91
  %r{.*#{host}(:|\/)(?<slug>.*).git}
92
+ else
93
+ %r{\/\/.*?(\/)(?<slug>.*).git}
35
94
  end
36
95
  end
37
96
 
data/lib/pronto/runner.rb CHANGED
@@ -28,7 +28,10 @@ module Pronto
28
28
  end
29
29
 
30
30
  def ruby_file?(path)
31
- rb_file?(path) || rake_file?(path) || ruby_executable?(path)
31
+ rb_file?(path) ||
32
+ rake_file?(path) ||
33
+ gem_file?(path) ||
34
+ ruby_executable?(path)
32
35
  end
33
36
 
34
37
  def repo_path
@@ -45,6 +48,10 @@ module Pronto
45
48
  File.extname(path) == '.rake'
46
49
  end
47
50
 
51
+ def gem_file?(path)
52
+ File.basename(path) == 'Gemfile' || File.extname(path) == '.gemspec'
53
+ end
54
+
48
55
  def ruby_executable?(path)
49
56
  return false if File.directory?(path)
50
57
  line = File.open(path, &:readline)
@@ -6,25 +6,42 @@ module Pronto
6
6
  end
7
7
 
8
8
  def run(patches)
9
- patches = reject_excluded(@config.excluded_files('all'), patches)
9
+ patches = reject_excluded(config.excluded_files('all'), patches)
10
10
  return [] if patches.none?
11
11
 
12
12
  result = []
13
- @runners.each do |runner|
13
+ active_runners.each do |runner|
14
14
  next if exceeds_max?(result)
15
- @config.logger.log("Running #{runner}")
15
+ config.logger.log("Running #{runner}")
16
16
  runner_patches = reject_excluded(
17
- @config.excluded_files(runner.title), patches
17
+ config.excluded_files(runner.title), patches
18
18
  )
19
19
  next if runner_patches.none?
20
20
  result += runner.new(runner_patches, patches.commit).run.flatten.compact
21
21
  end
22
- result = result.take(@config.max_warnings) if @config.max_warnings
22
+ result = result.take(config.max_warnings) if config.max_warnings
23
23
  result
24
24
  end
25
25
 
26
26
  private
27
27
 
28
+ attr_reader :config, :runners
29
+
30
+ def active_runners
31
+ runners.select { |runner| active_runner?(runner) }
32
+ end
33
+
34
+ def active_runner?(runner)
35
+ return true if config.runners.empty? && config.skip_runners.empty?
36
+
37
+ if config.runners.empty?
38
+ !config.skip_runners.include?(runner.title)
39
+ else
40
+ active_runner_names = config.runners - config.skip_runners
41
+ active_runner_names.include?(runner.title)
42
+ end
43
+ end
44
+
28
45
  def reject_excluded(excluded_files, patches)
29
46
  return patches unless excluded_files.any?
30
47
 
@@ -34,7 +51,7 @@ module Pronto
34
51
  end
35
52
 
36
53
  def exceeds_max?(warnings)
37
- @config.max_warnings && warnings.count >= @config.max_warnings
54
+ config.max_warnings && warnings.count >= config.max_warnings
38
55
  end
39
56
  end
40
57
  end
@@ -1,6 +1,6 @@
1
1
  module Pronto
2
2
  module Version
3
- STRING = '0.10.0'.freeze
3
+ STRING = '0.11.1'.freeze
4
4
 
5
5
  MSG = '%s (running on %s %s %s)'.freeze
6
6
 
data/lib/pronto.rb CHANGED
@@ -42,9 +42,11 @@ require 'pronto/formatter/commit_formatter'
42
42
  require 'pronto/formatter/pull_request_formatter'
43
43
  require 'pronto/formatter/github_formatter'
44
44
  require 'pronto/formatter/github_status_formatter'
45
+ require 'pronto/formatter/github_combined_status_formatter'
45
46
  require 'pronto/formatter/github_pull_request_formatter'
46
47
  require 'pronto/formatter/github_pull_request_review_formatter'
47
48
  require 'pronto/formatter/gitlab_formatter'
49
+ require 'pronto/formatter/gitlab_merge_request_review_formatter'
48
50
  require 'pronto/formatter/bitbucket_formatter'
49
51
  require 'pronto/formatter/bitbucket_pull_request_formatter'
50
52
  require 'pronto/formatter/bitbucket_server_pull_request_formatter'
@@ -53,9 +55,9 @@ require 'pronto/formatter/null_formatter'
53
55
  require 'pronto/formatter/formatter'
54
56
 
55
57
  module Pronto
56
- def self.run(commit = 'master', repo_path = '.',
58
+ def self.run(commit = nil, repo_path = '.',
57
59
  formatters = [Formatter::TextFormatter.new], file = nil)
58
- commit ||= 'master'
60
+ commit ||= default_commit
59
61
 
60
62
  repo = Git::Repository.new(repo_path)
61
63
  options = { paths: [file] } if file
@@ -70,4 +72,8 @@ module Pronto
70
72
 
71
73
  result
72
74
  end
75
+
76
+ def self.default_commit
77
+ Config.new.default_commit
78
+ end
73
79
  end
data/pronto.gemspec CHANGED
@@ -40,18 +40,20 @@ Gem::Specification.new do |s|
40
40
  s.require_paths = ['lib']
41
41
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
42
42
 
43
- s.add_runtime_dependency('gitlab', '~> 4.0', '>= 4.0.0')
44
- s.add_runtime_dependency('httparty', '>= 0.13.7')
45
- s.add_runtime_dependency('octokit', '~> 4.7', '>= 4.7.0')
43
+ s.add_runtime_dependency('gitlab', '>= 4.4.0', '< 5.0')
44
+ s.add_runtime_dependency('httparty', '>= 0.13.7', '< 1.0')
45
+ s.add_runtime_dependency('octokit', '>= 4.7.0', '< 7.0')
46
46
  s.add_runtime_dependency('rainbow', '>= 2.2', '< 4.0')
47
- s.add_runtime_dependency('rugged', '~> 0.24', '>= 0.23.0')
48
- s.add_runtime_dependency('thor', '~> 0.20.0')
47
+ s.add_runtime_dependency('rexml', '>= 3.2.5', '< 4.0')
48
+ s.add_runtime_dependency('rugged', '>= 0.23.0', '< 2.0')
49
+ s.add_runtime_dependency('thor', '>= 0.20.3', '< 2.0')
49
50
  s.add_development_dependency('bundler', '>= 1.15')
50
- s.add_development_dependency('pronto-rubocop', '~> 0.9.0')
51
+ s.add_development_dependency('pronto-rubocop', '~> 0.10.0')
51
52
  s.add_development_dependency('rake', '~> 12.0')
52
53
  s.add_development_dependency('rspec', '~> 3.4')
53
54
  s.add_development_dependency('rspec-its', '~> 1.2')
54
55
  s.add_development_dependency('rspec-expectations', '~> 3.4')
55
56
  s.add_development_dependency('rubocop', '~> 0.58')
56
- s.add_development_dependency('simplecov', '~> 0.14')
57
+ s.add_development_dependency('simplecov', '~> 0.17', '!= 0.18.0', '!= 0.18.1', '!= 0.18.2', '!= 0.18.3', '!= 0.18.4',
58
+ '!= 0.18.5', '!= 0.19.0', '!= 0.19.1') # see https://docs.codeclimate.com/docs/configuring-test-coverage
57
59
  end