danger 8.0.3
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 +7 -0
 - data/LICENSE +22 -0
 - data/README.md +94 -0
 - data/bin/danger +5 -0
 - data/lib/assets/DangerfileTemplate +13 -0
 - data/lib/danger.rb +44 -0
 - data/lib/danger/ci_source/appcenter.rb +55 -0
 - data/lib/danger/ci_source/appveyor.rb +60 -0
 - data/lib/danger/ci_source/azure_pipelines.rb +44 -0
 - data/lib/danger/ci_source/bamboo.rb +41 -0
 - data/lib/danger/ci_source/bitbucket_pipelines.rb +37 -0
 - data/lib/danger/ci_source/bitrise.rb +65 -0
 - data/lib/danger/ci_source/buddybuild.rb +62 -0
 - data/lib/danger/ci_source/buildkite.rb +51 -0
 - data/lib/danger/ci_source/ci_source.rb +37 -0
 - data/lib/danger/ci_source/circle.rb +94 -0
 - data/lib/danger/ci_source/circle_api.rb +51 -0
 - data/lib/danger/ci_source/cirrus.rb +31 -0
 - data/lib/danger/ci_source/code_build.rb +57 -0
 - data/lib/danger/ci_source/codefresh.rb +53 -0
 - data/lib/danger/ci_source/codeship.rb +44 -0
 - data/lib/danger/ci_source/dotci.rb +52 -0
 - data/lib/danger/ci_source/drone.rb +71 -0
 - data/lib/danger/ci_source/github_actions.rb +43 -0
 - data/lib/danger/ci_source/gitlab_ci.rb +86 -0
 - data/lib/danger/ci_source/jenkins.rb +149 -0
 - data/lib/danger/ci_source/local_git_repo.rb +119 -0
 - data/lib/danger/ci_source/local_only_git_repo.rb +47 -0
 - data/lib/danger/ci_source/screwdriver.rb +47 -0
 - data/lib/danger/ci_source/semaphore.rb +37 -0
 - data/lib/danger/ci_source/support/commits.rb +17 -0
 - data/lib/danger/ci_source/support/find_repo_info_from_logs.rb +35 -0
 - data/lib/danger/ci_source/support/find_repo_info_from_url.rb +42 -0
 - data/lib/danger/ci_source/support/local_pull_request.rb +14 -0
 - data/lib/danger/ci_source/support/no_pull_request.rb +7 -0
 - data/lib/danger/ci_source/support/no_repo_info.rb +5 -0
 - data/lib/danger/ci_source/support/pull_request_finder.rb +179 -0
 - data/lib/danger/ci_source/support/remote_pull_request.rb +15 -0
 - data/lib/danger/ci_source/support/repo_info.rb +10 -0
 - data/lib/danger/ci_source/surf.rb +37 -0
 - data/lib/danger/ci_source/teamcity.rb +159 -0
 - data/lib/danger/ci_source/travis.rb +51 -0
 - data/lib/danger/ci_source/vsts.rb +73 -0
 - data/lib/danger/ci_source/xcode_server.rb +48 -0
 - data/lib/danger/clients/rubygems_client.rb +14 -0
 - data/lib/danger/commands/dangerfile/gem.rb +43 -0
 - data/lib/danger/commands/dangerfile/init.rb +30 -0
 - data/lib/danger/commands/dry_run.rb +54 -0
 - data/lib/danger/commands/init.rb +297 -0
 - data/lib/danger/commands/init_helpers/interviewer.rb +92 -0
 - data/lib/danger/commands/local.rb +83 -0
 - data/lib/danger/commands/local_helpers/http_cache.rb +36 -0
 - data/lib/danger/commands/local_helpers/local_setup.rb +46 -0
 - data/lib/danger/commands/local_helpers/pry_setup.rb +31 -0
 - data/lib/danger/commands/plugins/plugin_json.rb +46 -0
 - data/lib/danger/commands/plugins/plugin_lint.rb +54 -0
 - data/lib/danger/commands/plugins/plugin_readme.rb +45 -0
 - data/lib/danger/commands/pr.rb +92 -0
 - data/lib/danger/commands/runner.rb +94 -0
 - data/lib/danger/commands/staging.rb +53 -0
 - data/lib/danger/commands/systems.rb +43 -0
 - data/lib/danger/comment_generators/bitbucket_server.md.erb +20 -0
 - data/lib/danger/comment_generators/bitbucket_server_inline.md.erb +15 -0
 - data/lib/danger/comment_generators/bitbucket_server_message_group.md.erb +12 -0
 - data/lib/danger/comment_generators/github.md.erb +55 -0
 - data/lib/danger/comment_generators/github_inline.md.erb +26 -0
 - data/lib/danger/comment_generators/gitlab.md.erb +40 -0
 - data/lib/danger/comment_generators/gitlab_inline.md.erb +26 -0
 - data/lib/danger/comment_generators/vsts.md.erb +20 -0
 - data/lib/danger/core_ext/file_list.rb +18 -0
 - data/lib/danger/core_ext/string.rb +20 -0
 - data/lib/danger/danger_core/dangerfile.rb +341 -0
 - data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
 - data/lib/danger/danger_core/dangerfile_generator.rb +11 -0
 - data/lib/danger/danger_core/environment_manager.rb +123 -0
 - data/lib/danger/danger_core/executor.rb +92 -0
 - data/lib/danger/danger_core/message_aggregator.rb +49 -0
 - data/lib/danger/danger_core/message_group.rb +68 -0
 - data/lib/danger/danger_core/messages/base.rb +56 -0
 - data/lib/danger/danger_core/messages/markdown.rb +42 -0
 - data/lib/danger/danger_core/messages/violation.rb +54 -0
 - data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +144 -0
 - data/lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb +211 -0
 - data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +248 -0
 - data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +158 -0
 - data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +254 -0
 - data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +240 -0
 - data/lib/danger/danger_core/plugins/dangerfile_local_only_plugin.rb +42 -0
 - data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +218 -0
 - data/lib/danger/danger_core/plugins/dangerfile_vsts_plugin.rb +191 -0
 - data/lib/danger/danger_core/standard_error.rb +143 -0
 - data/lib/danger/helpers/array_subclass.rb +61 -0
 - data/lib/danger/helpers/comment.rb +32 -0
 - data/lib/danger/helpers/comments_helper.rb +178 -0
 - data/lib/danger/helpers/comments_parsing_helper.rb +70 -0
 - data/lib/danger/helpers/emoji_mapper.rb +41 -0
 - data/lib/danger/helpers/find_max_num_violations.rb +31 -0
 - data/lib/danger/helpers/message_groups_array_helper.rb +31 -0
 - data/lib/danger/plugin_support/gems_resolver.rb +77 -0
 - data/lib/danger/plugin_support/plugin.rb +49 -0
 - data/lib/danger/plugin_support/plugin_file_resolver.rb +30 -0
 - data/lib/danger/plugin_support/plugin_linter.rb +161 -0
 - data/lib/danger/plugin_support/plugin_parser.rb +199 -0
 - data/lib/danger/plugin_support/templates/readme_table.html.erb +26 -0
 - data/lib/danger/request_sources/bitbucket_cloud.rb +171 -0
 - data/lib/danger/request_sources/bitbucket_cloud_api.rb +181 -0
 - data/lib/danger/request_sources/bitbucket_server.rb +105 -0
 - data/lib/danger/request_sources/bitbucket_server_api.rb +117 -0
 - data/lib/danger/request_sources/github/github.rb +530 -0
 - data/lib/danger/request_sources/github/github_review.rb +126 -0
 - data/lib/danger/request_sources/github/github_review_resolver.rb +19 -0
 - data/lib/danger/request_sources/github/github_review_unsupported.rb +25 -0
 - data/lib/danger/request_sources/gitlab.rb +525 -0
 - data/lib/danger/request_sources/local_only.rb +53 -0
 - data/lib/danger/request_sources/request_source.rb +85 -0
 - data/lib/danger/request_sources/support/get_ignored_violation.rb +17 -0
 - data/lib/danger/request_sources/vsts.rb +118 -0
 - data/lib/danger/request_sources/vsts_api.rb +138 -0
 - data/lib/danger/scm_source/git_repo.rb +181 -0
 - data/lib/danger/version.rb +4 -0
 - metadata +339 -0
 
| 
         @@ -0,0 +1,126 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # coding: utf-8
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "octokit"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "danger/ci_source/ci_source"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "danger/request_sources/github/github_review_resolver"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require "danger/danger_core/messages/violation"
         
     | 
| 
      
 7 
     | 
    
         
            +
            require "danger/danger_core/messages/markdown"
         
     | 
| 
      
 8 
     | 
    
         
            +
            require "danger/helpers/comments_helper"
         
     | 
| 
      
 9 
     | 
    
         
            +
            require "danger/helpers/comment"
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            module Danger
         
     | 
| 
      
 12 
     | 
    
         
            +
              module RequestSources
         
     | 
| 
      
 13 
     | 
    
         
            +
                module GitHubSource
         
     | 
| 
      
 14 
     | 
    
         
            +
                  class Review
         
     | 
| 
      
 15 
     | 
    
         
            +
                    include Danger::Helpers::CommentsHelper
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    # @see https://developer.github.com/v3/pulls/reviews/ for all possible events
         
     | 
| 
      
 18 
     | 
    
         
            +
                    EVENT_APPROVE = "APPROVE".freeze
         
     | 
| 
      
 19 
     | 
    
         
            +
                    EVENT_REQUEST_CHANGES = "REQUEST_CHANGES".freeze
         
     | 
| 
      
 20 
     | 
    
         
            +
                    EVENT_COMMENT = "COMMENT".freeze
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    # Current review status, if the review has not been submitted yet -> STATUS_PENDING
         
     | 
| 
      
 23 
     | 
    
         
            +
                    STATUS_APPROVED = "APPROVED".freeze
         
     | 
| 
      
 24 
     | 
    
         
            +
                    STATUS_REQUESTED_CHANGES = "CHANGES_REQUESTED".freeze
         
     | 
| 
      
 25 
     | 
    
         
            +
                    STATUS_COMMENTED = "COMMENTED".freeze
         
     | 
| 
      
 26 
     | 
    
         
            +
                    STATUS_PENDING = "PENDING".freeze
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    attr_reader :id, :body, :status, :review_json
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    def initialize(client, ci_source, review_json = nil)
         
     | 
| 
      
 31 
     | 
    
         
            +
                      @ci_source = ci_source
         
     | 
| 
      
 32 
     | 
    
         
            +
                      @client = client
         
     | 
| 
      
 33 
     | 
    
         
            +
                      @review_json = review_json
         
     | 
| 
      
 34 
     | 
    
         
            +
                    end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                    def id
         
     | 
| 
      
 37 
     | 
    
         
            +
                      return nil unless self.review_json
         
     | 
| 
      
 38 
     | 
    
         
            +
                      self.review_json["id"]
         
     | 
| 
      
 39 
     | 
    
         
            +
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    def body
         
     | 
| 
      
 42 
     | 
    
         
            +
                      return "" unless self.review_json
         
     | 
| 
      
 43 
     | 
    
         
            +
                      self.review_json["body"]
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                    def status
         
     | 
| 
      
 47 
     | 
    
         
            +
                      return STATUS_PENDING if self.review_json.nil?
         
     | 
| 
      
 48 
     | 
    
         
            +
                      return self.review_json["state"]
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                    # Starts the new review process
         
     | 
| 
      
 52 
     | 
    
         
            +
                    def start
         
     | 
| 
      
 53 
     | 
    
         
            +
                      @warnings = []
         
     | 
| 
      
 54 
     | 
    
         
            +
                      @errors = []
         
     | 
| 
      
 55 
     | 
    
         
            +
                      @messages = []
         
     | 
| 
      
 56 
     | 
    
         
            +
                      @markdowns = []
         
     | 
| 
      
 57 
     | 
    
         
            +
                    end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                    # Submits the prepared review
         
     | 
| 
      
 60 
     | 
    
         
            +
                    def submit
         
     | 
| 
      
 61 
     | 
    
         
            +
                      general_violations = generate_general_violations
         
     | 
| 
      
 62 
     | 
    
         
            +
                      submission_body = generate_body
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                      # If the review resolver says that there is nothing to submit we skip submission
         
     | 
| 
      
 65 
     | 
    
         
            +
                      return unless ReviewResolver.should_submit?(self, submission_body)
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                      @review_json = @client.create_pull_request_review(@ci_source.repo_slug, @ci_source.pull_request_id, event: generate_event(general_violations), body: submission_body)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                    def generated_by_danger?(danger_id = "danger")
         
     | 
| 
      
 71 
     | 
    
         
            +
                      self.review_json["body"].include?("generated_by_#{danger_id}")
         
     | 
| 
      
 72 
     | 
    
         
            +
                    end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                    def message(message, sticky = true, file = nil, line = nil)
         
     | 
| 
      
 75 
     | 
    
         
            +
                      @messages << Violation.new(message, sticky, file, line)
         
     | 
| 
      
 76 
     | 
    
         
            +
                    end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                    def warn(message, sticky = true, file = nil, line = nil)
         
     | 
| 
      
 79 
     | 
    
         
            +
                      @warnings << Violation.new(message, sticky, file, line)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                    def fail(message, sticky = true, file = nil, line = nil)
         
     | 
| 
      
 83 
     | 
    
         
            +
                      @errors << Violation.new(message, sticky, file, line)
         
     | 
| 
      
 84 
     | 
    
         
            +
                    end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                    def markdown(message, file = nil, line = nil)
         
     | 
| 
      
 87 
     | 
    
         
            +
                      @markdowns << Markdown.new(message, file, line)
         
     | 
| 
      
 88 
     | 
    
         
            +
                    end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                    private
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                    # The only reason to request changes for the PR is to have errors from Danger
         
     | 
| 
      
 93 
     | 
    
         
            +
                    # otherwise let's just notify user and we're done
         
     | 
| 
      
 94 
     | 
    
         
            +
                    def generate_event(violations)
         
     | 
| 
      
 95 
     | 
    
         
            +
                      violations[:errors].empty? ? EVENT_APPROVE : EVENT_REQUEST_CHANGES
         
     | 
| 
      
 96 
     | 
    
         
            +
                    end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                    def generate_body(danger_id: "danger")
         
     | 
| 
      
 99 
     | 
    
         
            +
                      previous_violations = parse_comment(body)
         
     | 
| 
      
 100 
     | 
    
         
            +
                      general_violations = generate_general_violations
         
     | 
| 
      
 101 
     | 
    
         
            +
                      new_body = generate_comment(warnings: general_violations[:warnings],
         
     | 
| 
      
 102 
     | 
    
         
            +
                                                  errors: general_violations[:errors],
         
     | 
| 
      
 103 
     | 
    
         
            +
                                                  messages: general_violations[:messages],
         
     | 
| 
      
 104 
     | 
    
         
            +
                                                  markdowns: general_violations[:markdowns],
         
     | 
| 
      
 105 
     | 
    
         
            +
                                                  previous_violations: previous_violations,
         
     | 
| 
      
 106 
     | 
    
         
            +
                                                  danger_id: danger_id,
         
     | 
| 
      
 107 
     | 
    
         
            +
                                                  template: "github")
         
     | 
| 
      
 108 
     | 
    
         
            +
                      return new_body
         
     | 
| 
      
 109 
     | 
    
         
            +
                    end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                    def generate_general_violations
         
     | 
| 
      
 112 
     | 
    
         
            +
                      general_warnings = @warnings.reject(&:inline?)
         
     | 
| 
      
 113 
     | 
    
         
            +
                      general_errors = @errors.reject(&:inline?)
         
     | 
| 
      
 114 
     | 
    
         
            +
                      general_messages = @messages.reject(&:inline?)
         
     | 
| 
      
 115 
     | 
    
         
            +
                      general_markdowns = @markdowns.reject(&:inline?)
         
     | 
| 
      
 116 
     | 
    
         
            +
                      {
         
     | 
| 
      
 117 
     | 
    
         
            +
                        warnings: general_warnings,
         
     | 
| 
      
 118 
     | 
    
         
            +
                        markdowns: general_markdowns,
         
     | 
| 
      
 119 
     | 
    
         
            +
                        errors: general_errors,
         
     | 
| 
      
 120 
     | 
    
         
            +
                        messages: general_messages
         
     | 
| 
      
 121 
     | 
    
         
            +
                      }
         
     | 
| 
      
 122 
     | 
    
         
            +
                    end
         
     | 
| 
      
 123 
     | 
    
         
            +
                  end
         
     | 
| 
      
 124 
     | 
    
         
            +
                end
         
     | 
| 
      
 125 
     | 
    
         
            +
              end
         
     | 
| 
      
 126 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # coding: utf-8
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "danger/request_sources/github/github_review"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Danger
         
     | 
| 
      
 6 
     | 
    
         
            +
              module RequestSources
         
     | 
| 
      
 7 
     | 
    
         
            +
                module GitHubSource
         
     | 
| 
      
 8 
     | 
    
         
            +
                  class ReviewResolver
         
     | 
| 
      
 9 
     | 
    
         
            +
                    def self.should_submit?(review, body)
         
     | 
| 
      
 10 
     | 
    
         
            +
                      return !same_body?(body, review.body)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    def self.same_body?(body1, body2)
         
     | 
| 
      
 14 
     | 
    
         
            +
                      return !body1.nil? && !body2.nil? && body1 == body2
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # coding: utf-8
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Danger
         
     | 
| 
      
 4 
     | 
    
         
            +
              module RequestSources
         
     | 
| 
      
 5 
     | 
    
         
            +
                module GitHubSource
         
     | 
| 
      
 6 
     | 
    
         
            +
                  class ReviewUnsupported
         
     | 
| 
      
 7 
     | 
    
         
            +
                    attr_reader :id, :body, :status, :review_json
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                    def initialize; end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    def start; end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    def submit; end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                    def message(message, sticky = true, file = nil, line = nil); end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    def warn(message, sticky = true, file = nil, line = nil); end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    def fail(message, sticky = true, file = nil, line = nil); end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    def markdown(message, file = nil, line = nil); end
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,525 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # coding: utf-8
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "uri"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "danger/helpers/comments_helper"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "danger/helpers/comment"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "danger/request_sources/support/get_ignored_violation"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module Danger
         
     | 
| 
      
 8 
     | 
    
         
            +
              module RequestSources
         
     | 
| 
      
 9 
     | 
    
         
            +
                class GitLab < RequestSource
         
     | 
| 
      
 10 
     | 
    
         
            +
                  include Danger::Helpers::CommentsHelper
         
     | 
| 
      
 11 
     | 
    
         
            +
                  attr_accessor :mr_json, :commits_json
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  FIRST_GITLAB_GEM_WITH_VERSION_CHECK = Gem::Version.new("4.6.0")
         
     | 
| 
      
 14 
     | 
    
         
            +
                  FIRST_VERSION_WITH_INLINE_COMMENTS = Gem::Version.new("10.8.0")
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  def self.env_vars
         
     | 
| 
      
 17 
     | 
    
         
            +
                    ["DANGER_GITLAB_API_TOKEN"]
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def self.optional_env_vars
         
     | 
| 
      
 21 
     | 
    
         
            +
                    ["DANGER_GITLAB_HOST", "DANGER_GITLAB_API_BASE_URL"]
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def initialize(ci_source, environment)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    self.ci_source = ci_source
         
     | 
| 
      
 26 
     | 
    
         
            +
                    self.environment = environment
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    @token = @environment["DANGER_GITLAB_API_TOKEN"]
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def client
         
     | 
| 
      
 32 
     | 
    
         
            +
                    token = @environment["DANGER_GITLAB_API_TOKEN"]
         
     | 
| 
      
 33 
     | 
    
         
            +
                    raise "No API token given, please provide one using `DANGER_GITLAB_API_TOKEN`" unless token
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                    # The require happens inline so that it won't cause exceptions when just using the `danger` gem.
         
     | 
| 
      
 36 
     | 
    
         
            +
                    require "gitlab"
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    @client ||= Gitlab.client(endpoint: endpoint, private_token: token)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  rescue LoadError => e
         
     | 
| 
      
 40 
     | 
    
         
            +
                    if e.path == "gitlab"
         
     | 
| 
      
 41 
     | 
    
         
            +
                      puts "The GitLab gem was not installed, you will need to change your Gem from `danger` to `danger-gitlab`.".red
         
     | 
| 
      
 42 
     | 
    
         
            +
                      puts "\n - See https://github.com/danger/danger/blob/master/CHANGELOG.md#400"
         
     | 
| 
      
 43 
     | 
    
         
            +
                    else
         
     | 
| 
      
 44 
     | 
    
         
            +
                      puts "Error: #{e}".red
         
     | 
| 
      
 45 
     | 
    
         
            +
                    end
         
     | 
| 
      
 46 
     | 
    
         
            +
                    abort
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  def validates_as_ci?
         
     | 
| 
      
 50 
     | 
    
         
            +
                    includes_port = self.host.include? ":"
         
     | 
| 
      
 51 
     | 
    
         
            +
                    raise "Port number included in `DANGER_GITLAB_HOST`, this will fail with GitLab CI Runners" if includes_port
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    # We don't call super because in some cases the Git remote doesn't match the GitLab instance host.
         
     | 
| 
      
 54 
     | 
    
         
            +
                    # In Danger::EnvironmentManager#initialize we still check that the request source is #validates_as_api_source?
         
     | 
| 
      
 55 
     | 
    
         
            +
                    # so that should be sufficient to validate GitLab as request source.
         
     | 
| 
      
 56 
     | 
    
         
            +
                    # See https://github.com/danger/danger/issues/1231 and https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/10069.
         
     | 
| 
      
 57 
     | 
    
         
            +
                    true
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  def validates_as_api_source?
         
     | 
| 
      
 61 
     | 
    
         
            +
                    @token && !@token.empty?
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  def scm
         
     | 
| 
      
 65 
     | 
    
         
            +
                    @scm ||= GitRepo.new
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  def endpoint
         
     | 
| 
      
 69 
     | 
    
         
            +
                    @endpoint ||= @environment["DANGER_GITLAB_API_BASE_URL"] || @environment["CI_API_V4_URL"] || "https://gitlab.com/api/v4"
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  def host
         
     | 
| 
      
 73 
     | 
    
         
            +
                    @host ||= @environment["DANGER_GITLAB_HOST"] || URI.parse(endpoint).host || "gitlab.com"
         
     | 
| 
      
 74 
     | 
    
         
            +
                  end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                  def base_commit
         
     | 
| 
      
 77 
     | 
    
         
            +
                    @base_commit ||= self.mr_json.diff_refs.base_sha
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  def mr_comments
         
     | 
| 
      
 81 
     | 
    
         
            +
                    # @raw_comments contains what we got back from the server.
         
     | 
| 
      
 82 
     | 
    
         
            +
                    # @comments contains Comment objects (that have less information)
         
     | 
| 
      
 83 
     | 
    
         
            +
                    @comments ||= begin
         
     | 
| 
      
 84 
     | 
    
         
            +
                      if supports_inline_comments
         
     | 
| 
      
 85 
     | 
    
         
            +
                        @raw_comments = mr_discussions
         
     | 
| 
      
 86 
     | 
    
         
            +
                          .auto_paginate
         
     | 
| 
      
 87 
     | 
    
         
            +
                          .flat_map { |discussion| discussion.notes.map { |note| note.merge({"discussion_id" => discussion.id}) } }
         
     | 
| 
      
 88 
     | 
    
         
            +
                        @raw_comments
         
     | 
| 
      
 89 
     | 
    
         
            +
                          .map { |comment| Comment.from_gitlab(comment) }
         
     | 
| 
      
 90 
     | 
    
         
            +
                      else
         
     | 
| 
      
 91 
     | 
    
         
            +
                        @raw_comments = client.merge_request_comments(ci_source.repo_slug, ci_source.pull_request_id, per_page: 100)
         
     | 
| 
      
 92 
     | 
    
         
            +
                          .auto_paginate
         
     | 
| 
      
 93 
     | 
    
         
            +
                        @raw_comments
         
     | 
| 
      
 94 
     | 
    
         
            +
                          .map { |comment| Comment.from_gitlab(comment) }
         
     | 
| 
      
 95 
     | 
    
         
            +
                      end
         
     | 
| 
      
 96 
     | 
    
         
            +
                    end
         
     | 
| 
      
 97 
     | 
    
         
            +
                  end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                  def mr_discussions
         
     | 
| 
      
 100 
     | 
    
         
            +
                    @mr_discussions ||= client.merge_request_discussions(ci_source.repo_slug, ci_source.pull_request_id)
         
     | 
| 
      
 101 
     | 
    
         
            +
                  end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                  def mr_diff
         
     | 
| 
      
 104 
     | 
    
         
            +
                    @mr_diff ||= begin
         
     | 
| 
      
 105 
     | 
    
         
            +
                      diffs = mr_changes.changes.map do |change|
         
     | 
| 
      
 106 
     | 
    
         
            +
                        diff = change["diff"]
         
     | 
| 
      
 107 
     | 
    
         
            +
                        if diff.start_with?('--- a/')
         
     | 
| 
      
 108 
     | 
    
         
            +
                          diff
         
     | 
| 
      
 109 
     | 
    
         
            +
                        else
         
     | 
| 
      
 110 
     | 
    
         
            +
                          "--- a/#{change["old_path"]}\n+++ b/#{change["new_path"]}\n#{diff}"
         
     | 
| 
      
 111 
     | 
    
         
            +
                        end
         
     | 
| 
      
 112 
     | 
    
         
            +
                      end
         
     | 
| 
      
 113 
     | 
    
         
            +
                      diffs.join("\n")
         
     | 
| 
      
 114 
     | 
    
         
            +
                    end
         
     | 
| 
      
 115 
     | 
    
         
            +
                  end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                  def mr_changed_paths
         
     | 
| 
      
 118 
     | 
    
         
            +
                    @mr_changed_paths ||= begin
         
     | 
| 
      
 119 
     | 
    
         
            +
                      mr_changes
         
     | 
| 
      
 120 
     | 
    
         
            +
                        .changes.map { |change| change["new_path"] }
         
     | 
| 
      
 121 
     | 
    
         
            +
                    end
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                    @mr_changed_paths
         
     | 
| 
      
 124 
     | 
    
         
            +
                  end
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                  def mr_changes
         
     | 
| 
      
 127 
     | 
    
         
            +
                    @mr_changes ||= begin
         
     | 
| 
      
 128 
     | 
    
         
            +
                      client.merge_request_changes(ci_source.repo_slug, ci_source.pull_request_id)
         
     | 
| 
      
 129 
     | 
    
         
            +
                    end
         
     | 
| 
      
 130 
     | 
    
         
            +
                  end
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                  def setup_danger_branches
         
     | 
| 
      
 133 
     | 
    
         
            +
                    # we can use a GitLab specific feature here:
         
     | 
| 
      
 134 
     | 
    
         
            +
                    base_branch = self.mr_json.source_branch
         
     | 
| 
      
 135 
     | 
    
         
            +
                    base_commit = self.mr_json.diff_refs.base_sha
         
     | 
| 
      
 136 
     | 
    
         
            +
                    head_branch = self.mr_json.target_branch
         
     | 
| 
      
 137 
     | 
    
         
            +
                    head_commit = self.mr_json.diff_refs.head_sha
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                    # Next, we want to ensure that we have a version of the current branch at a known location
         
     | 
| 
      
 140 
     | 
    
         
            +
                    scm.ensure_commitish_exists_on_branch! base_branch, base_commit
         
     | 
| 
      
 141 
     | 
    
         
            +
                    self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{base_commit}"
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                    # OK, so we want to ensure that we have a known head branch, this will always represent
         
     | 
| 
      
 144 
     | 
    
         
            +
                    # the head of the PR ( e.g. the most recent commit that will be merged. )
         
     | 
| 
      
 145 
     | 
    
         
            +
                    scm.ensure_commitish_exists_on_branch! head_branch, head_commit
         
     | 
| 
      
 146 
     | 
    
         
            +
                    self.scm.exec "branch #{EnvironmentManager.danger_head_branch} #{head_commit}"
         
     | 
| 
      
 147 
     | 
    
         
            +
                  end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                  def fetch_details
         
     | 
| 
      
 150 
     | 
    
         
            +
                    self.mr_json = client.merge_request(ci_source.repo_slug, self.ci_source.pull_request_id)
         
     | 
| 
      
 151 
     | 
    
         
            +
                    self.ignored_violations = ignored_violations_from_pr
         
     | 
| 
      
 152 
     | 
    
         
            +
                  end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                  def ignored_violations_from_pr
         
     | 
| 
      
 155 
     | 
    
         
            +
                    GetIgnoredViolation.new(self.mr_json.description).call
         
     | 
| 
      
 156 
     | 
    
         
            +
                  end
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                  def supports_inline_comments
         
     | 
| 
      
 159 
     | 
    
         
            +
                    @supports_inline_comments ||= begin
         
     | 
| 
      
 160 
     | 
    
         
            +
                      # If we can't check GitLab's version, we assume we don't support inline comments
         
     | 
| 
      
 161 
     | 
    
         
            +
                      if Gem.loaded_specs["gitlab"].version < FIRST_GITLAB_GEM_WITH_VERSION_CHECK
         
     | 
| 
      
 162 
     | 
    
         
            +
                        false
         
     | 
| 
      
 163 
     | 
    
         
            +
                      else
         
     | 
| 
      
 164 
     | 
    
         
            +
                        current_version = Gem::Version.new(client.version.version)
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                        current_version >= FIRST_VERSION_WITH_INLINE_COMMENTS
         
     | 
| 
      
 167 
     | 
    
         
            +
                      end
         
     | 
| 
      
 168 
     | 
    
         
            +
                    end
         
     | 
| 
      
 169 
     | 
    
         
            +
                  end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                  def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
         
     | 
| 
      
 172 
     | 
    
         
            +
                    if supports_inline_comments
         
     | 
| 
      
 173 
     | 
    
         
            +
                      update_pull_request_with_inline_comments!(warnings: warnings, errors: errors, messages: messages, markdowns: markdowns, danger_id: danger_id, new_comment: new_comment, remove_previous_comments: remove_previous_comments)
         
     | 
| 
      
 174 
     | 
    
         
            +
                    else
         
     | 
| 
      
 175 
     | 
    
         
            +
                      update_pull_request_without_inline_comments!(warnings: warnings, errors: errors, messages: messages, markdowns: markdowns, danger_id: danger_id, new_comment: new_comment, remove_previous_comments: remove_previous_comments)
         
     | 
| 
      
 176 
     | 
    
         
            +
                    end
         
     | 
| 
      
 177 
     | 
    
         
            +
                  end
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                  def update_pull_request_with_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
         
     | 
| 
      
 180 
     | 
    
         
            +
                    editable_regular_comments = mr_comments
         
     | 
| 
      
 181 
     | 
    
         
            +
                      .select { |comment| comment.generated_by_danger?(danger_id) }
         
     | 
| 
      
 182 
     | 
    
         
            +
                      .reject(&:inline?)
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                    last_comment = editable_regular_comments.last
         
     | 
| 
      
 185 
     | 
    
         
            +
                    should_create_new_comment = new_comment || last_comment.nil? || remove_previous_comments
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                    previous_violations =
         
     | 
| 
      
 188 
     | 
    
         
            +
                      if should_create_new_comment
         
     | 
| 
      
 189 
     | 
    
         
            +
                        {}
         
     | 
| 
      
 190 
     | 
    
         
            +
                      else
         
     | 
| 
      
 191 
     | 
    
         
            +
                        parse_comment(last_comment.body)
         
     | 
| 
      
 192 
     | 
    
         
            +
                      end
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
                    regular_violations = regular_violations_group(
         
     | 
| 
      
 195 
     | 
    
         
            +
                      warnings: warnings,
         
     | 
| 
      
 196 
     | 
    
         
            +
                      errors: errors,
         
     | 
| 
      
 197 
     | 
    
         
            +
                      messages: messages,
         
     | 
| 
      
 198 
     | 
    
         
            +
                      markdowns: markdowns
         
     | 
| 
      
 199 
     | 
    
         
            +
                    )
         
     | 
| 
      
 200 
     | 
    
         
            +
             
     | 
| 
      
 201 
     | 
    
         
            +
                    inline_violations = inline_violations_group(
         
     | 
| 
      
 202 
     | 
    
         
            +
                      warnings: warnings,
         
     | 
| 
      
 203 
     | 
    
         
            +
                      errors: errors,
         
     | 
| 
      
 204 
     | 
    
         
            +
                      messages: messages,
         
     | 
| 
      
 205 
     | 
    
         
            +
                      markdowns: markdowns
         
     | 
| 
      
 206 
     | 
    
         
            +
                    )
         
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
      
 208 
     | 
    
         
            +
                    rest_inline_violations = submit_inline_comments!({
         
     | 
| 
      
 209 
     | 
    
         
            +
                      danger_id: danger_id,
         
     | 
| 
      
 210 
     | 
    
         
            +
                      previous_violations: previous_violations
         
     | 
| 
      
 211 
     | 
    
         
            +
                    }.merge(inline_violations))
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
      
 213 
     | 
    
         
            +
                    main_violations = merge_violations(
         
     | 
| 
      
 214 
     | 
    
         
            +
                      regular_violations, rest_inline_violations
         
     | 
| 
      
 215 
     | 
    
         
            +
                    )
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
                    main_violations_sum = main_violations.values.inject(:+)
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
                    if (previous_violations.empty? && main_violations_sum.empty?) || remove_previous_comments
         
     | 
| 
      
 220 
     | 
    
         
            +
                      # Just remove the comment, if there's nothing to say or --remove-previous-comments CLI was set.
         
     | 
| 
      
 221 
     | 
    
         
            +
                      delete_old_comments!(danger_id: danger_id)
         
     | 
| 
      
 222 
     | 
    
         
            +
                    end
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
                    # If there are still violations to show
         
     | 
| 
      
 225 
     | 
    
         
            +
                    if main_violations_sum.any?
         
     | 
| 
      
 226 
     | 
    
         
            +
                      body = generate_comment({
         
     | 
| 
      
 227 
     | 
    
         
            +
                        template: "gitlab",
         
     | 
| 
      
 228 
     | 
    
         
            +
                        danger_id: danger_id,
         
     | 
| 
      
 229 
     | 
    
         
            +
                        previous_violations: previous_violations
         
     | 
| 
      
 230 
     | 
    
         
            +
                      }.merge(main_violations))
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
                      comment_result =
         
     | 
| 
      
 233 
     | 
    
         
            +
                        if should_create_new_comment
         
     | 
| 
      
 234 
     | 
    
         
            +
                          client.create_merge_request_note(ci_source.repo_slug, ci_source.pull_request_id, body)
         
     | 
| 
      
 235 
     | 
    
         
            +
                        else
         
     | 
| 
      
 236 
     | 
    
         
            +
                          client.edit_merge_request_note(ci_source.repo_slug, ci_source.pull_request_id, last_comment.id, body)
         
     | 
| 
      
 237 
     | 
    
         
            +
                        end
         
     | 
| 
      
 238 
     | 
    
         
            +
                    end
         
     | 
| 
      
 239 
     | 
    
         
            +
                  end
         
     | 
| 
      
 240 
     | 
    
         
            +
             
     | 
| 
      
 241 
     | 
    
         
            +
                  def update_pull_request_without_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
         
     | 
| 
      
 242 
     | 
    
         
            +
                    editable_comments = mr_comments.select { |comment| comment.generated_by_danger?(danger_id) }
         
     | 
| 
      
 243 
     | 
    
         
            +
             
     | 
| 
      
 244 
     | 
    
         
            +
                    should_create_new_comment = new_comment || editable_comments.empty? || remove_previous_comments
         
     | 
| 
      
 245 
     | 
    
         
            +
             
     | 
| 
      
 246 
     | 
    
         
            +
                    if should_create_new_comment
         
     | 
| 
      
 247 
     | 
    
         
            +
                      previous_violations = {}
         
     | 
| 
      
 248 
     | 
    
         
            +
                    else
         
     | 
| 
      
 249 
     | 
    
         
            +
                      comment = editable_comments.first.body
         
     | 
| 
      
 250 
     | 
    
         
            +
                      previous_violations = parse_comment(comment)
         
     | 
| 
      
 251 
     | 
    
         
            +
                    end
         
     | 
| 
      
 252 
     | 
    
         
            +
             
     | 
| 
      
 253 
     | 
    
         
            +
                    if (previous_violations.empty? && (warnings + errors + messages + markdowns).empty?) || remove_previous_comments
         
     | 
| 
      
 254 
     | 
    
         
            +
                      # Just remove the comment, if there's nothing to say or --remove-previous-comments CLI was set.
         
     | 
| 
      
 255 
     | 
    
         
            +
                      delete_old_comments!(danger_id: danger_id)
         
     | 
| 
      
 256 
     | 
    
         
            +
                    else
         
     | 
| 
      
 257 
     | 
    
         
            +
                      body = generate_comment(warnings: warnings,
         
     | 
| 
      
 258 
     | 
    
         
            +
                                                errors: errors,
         
     | 
| 
      
 259 
     | 
    
         
            +
                                              messages: messages,
         
     | 
| 
      
 260 
     | 
    
         
            +
                                             markdowns: markdowns,
         
     | 
| 
      
 261 
     | 
    
         
            +
                                   previous_violations: previous_violations,
         
     | 
| 
      
 262 
     | 
    
         
            +
                                             danger_id: danger_id,
         
     | 
| 
      
 263 
     | 
    
         
            +
                                              template: "gitlab")
         
     | 
| 
      
 264 
     | 
    
         
            +
             
     | 
| 
      
 265 
     | 
    
         
            +
                      if editable_comments.empty? or should_create_new_comment
         
     | 
| 
      
 266 
     | 
    
         
            +
                        client.create_merge_request_comment(
         
     | 
| 
      
 267 
     | 
    
         
            +
                          ci_source.repo_slug, ci_source.pull_request_id, body
         
     | 
| 
      
 268 
     | 
    
         
            +
                        )
         
     | 
| 
      
 269 
     | 
    
         
            +
                      else
         
     | 
| 
      
 270 
     | 
    
         
            +
                        original_id = editable_comments.first.id
         
     | 
| 
      
 271 
     | 
    
         
            +
                        client.edit_merge_request_comment(
         
     | 
| 
      
 272 
     | 
    
         
            +
                          ci_source.repo_slug,
         
     | 
| 
      
 273 
     | 
    
         
            +
                          ci_source.pull_request_id,
         
     | 
| 
      
 274 
     | 
    
         
            +
                          original_id,
         
     | 
| 
      
 275 
     | 
    
         
            +
                          { body: body }
         
     | 
| 
      
 276 
     | 
    
         
            +
                        )
         
     | 
| 
      
 277 
     | 
    
         
            +
                      end
         
     | 
| 
      
 278 
     | 
    
         
            +
                    end
         
     | 
| 
      
 279 
     | 
    
         
            +
                  end
         
     | 
| 
      
 280 
     | 
    
         
            +
             
     | 
| 
      
 281 
     | 
    
         
            +
                  def delete_old_comments!(except: nil, danger_id: "danger")
         
     | 
| 
      
 282 
     | 
    
         
            +
                    @raw_comments.each do |raw_comment|
         
     | 
| 
      
 283 
     | 
    
         
            +
             
     | 
| 
      
 284 
     | 
    
         
            +
                      comment = Comment.from_gitlab(raw_comment)
         
     | 
| 
      
 285 
     | 
    
         
            +
                      next unless comment.generated_by_danger?(danger_id)
         
     | 
| 
      
 286 
     | 
    
         
            +
                      next if comment.id == except
         
     | 
| 
      
 287 
     | 
    
         
            +
                      next unless raw_comment.is_a?(Hash) && raw_comment["position"].nil?
         
     | 
| 
      
 288 
     | 
    
         
            +
             
     | 
| 
      
 289 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 290 
     | 
    
         
            +
                        client.delete_merge_request_comment(
         
     | 
| 
      
 291 
     | 
    
         
            +
                          ci_source.repo_slug,
         
     | 
| 
      
 292 
     | 
    
         
            +
                          ci_source.pull_request_id,
         
     | 
| 
      
 293 
     | 
    
         
            +
                          comment.id
         
     | 
| 
      
 294 
     | 
    
         
            +
                        )
         
     | 
| 
      
 295 
     | 
    
         
            +
                      rescue
         
     | 
| 
      
 296 
     | 
    
         
            +
                      end
         
     | 
| 
      
 297 
     | 
    
         
            +
                    end
         
     | 
| 
      
 298 
     | 
    
         
            +
                  end
         
     | 
| 
      
 299 
     | 
    
         
            +
             
     | 
| 
      
 300 
     | 
    
         
            +
                  # @return [String] The organisation name, is nil if it can't be detected
         
     | 
| 
      
 301 
     | 
    
         
            +
                  def organisation
         
     | 
| 
      
 302 
     | 
    
         
            +
                    nil # TODO: Implement this
         
     | 
| 
      
 303 
     | 
    
         
            +
                  end
         
     | 
| 
      
 304 
     | 
    
         
            +
             
     | 
| 
      
 305 
     | 
    
         
            +
                  # @return [String] A URL to the specific file, ready to be downloaded
         
     | 
| 
      
 306 
     | 
    
         
            +
                  def file_url(organisation: nil, repository: nil, branch: nil, path: nil)
         
     | 
| 
      
 307 
     | 
    
         
            +
                    branch ||= 'master'
         
     | 
| 
      
 308 
     | 
    
         
            +
                    token = @environment["DANGER_GITLAB_API_TOKEN"]
         
     | 
| 
      
 309 
     | 
    
         
            +
                    # According to GitLab Repositories API docs path and id(slug) should be encoded.
         
     | 
| 
      
 310 
     | 
    
         
            +
                    path = URI.encode_www_form_component(path)
         
     | 
| 
      
 311 
     | 
    
         
            +
                    repository = URI.encode_www_form_component(repository)
         
     | 
| 
      
 312 
     | 
    
         
            +
                    "#{endpoint}/projects/#{repository}/repository/files/#{path}/raw?ref=#{branch}&private_token=#{token}"
         
     | 
| 
      
 313 
     | 
    
         
            +
                  end
         
     | 
| 
      
 314 
     | 
    
         
            +
             
     | 
| 
      
 315 
     | 
    
         
            +
                  def regular_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
         
     | 
| 
      
 316 
     | 
    
         
            +
                    {
         
     | 
| 
      
 317 
     | 
    
         
            +
                      warnings: warnings.reject(&:inline?),
         
     | 
| 
      
 318 
     | 
    
         
            +
                      errors: errors.reject(&:inline?),
         
     | 
| 
      
 319 
     | 
    
         
            +
                      messages: messages.reject(&:inline?),
         
     | 
| 
      
 320 
     | 
    
         
            +
                      markdowns: markdowns.reject(&:inline?)
         
     | 
| 
      
 321 
     | 
    
         
            +
                    }
         
     | 
| 
      
 322 
     | 
    
         
            +
                  end
         
     | 
| 
      
 323 
     | 
    
         
            +
             
     | 
| 
      
 324 
     | 
    
         
            +
                  def inline_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
         
     | 
| 
      
 325 
     | 
    
         
            +
                    cmp = proc do |a, b|
         
     | 
| 
      
 326 
     | 
    
         
            +
                      next -1 unless a.file && a.line
         
     | 
| 
      
 327 
     | 
    
         
            +
                      next 1 unless b.file && b.line
         
     | 
| 
      
 328 
     | 
    
         
            +
             
     | 
| 
      
 329 
     | 
    
         
            +
                      next a.line <=> b.line if a.file == b.file
         
     | 
| 
      
 330 
     | 
    
         
            +
                      next a.file <=> b.file
         
     | 
| 
      
 331 
     | 
    
         
            +
                    end
         
     | 
| 
      
 332 
     | 
    
         
            +
             
     | 
| 
      
 333 
     | 
    
         
            +
                    # Sort to group inline comments by file
         
     | 
| 
      
 334 
     | 
    
         
            +
                    {
         
     | 
| 
      
 335 
     | 
    
         
            +
                      warnings: warnings.select(&:inline?).sort(&cmp),
         
     | 
| 
      
 336 
     | 
    
         
            +
                      errors: errors.select(&:inline?).sort(&cmp),
         
     | 
| 
      
 337 
     | 
    
         
            +
                      messages: messages.select(&:inline?).sort(&cmp),
         
     | 
| 
      
 338 
     | 
    
         
            +
                      markdowns: markdowns.select(&:inline?).sort(&cmp)
         
     | 
| 
      
 339 
     | 
    
         
            +
                    }
         
     | 
| 
      
 340 
     | 
    
         
            +
                  end
         
     | 
| 
      
 341 
     | 
    
         
            +
             
     | 
| 
      
 342 
     | 
    
         
            +
                  def merge_violations(*violation_groups)
         
     | 
| 
      
 343 
     | 
    
         
            +
                    violation_groups.inject({}) do |accumulator, group|
         
     | 
| 
      
 344 
     | 
    
         
            +
                      accumulator.merge(group) { |_, old, fresh| old + fresh }
         
     | 
| 
      
 345 
     | 
    
         
            +
                    end
         
     | 
| 
      
 346 
     | 
    
         
            +
                  end
         
     | 
| 
      
 347 
     | 
    
         
            +
             
     | 
| 
      
 348 
     | 
    
         
            +
                  def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger")
         
     | 
| 
      
 349 
     | 
    
         
            +
                    comments = mr_discussions
         
     | 
| 
      
 350 
     | 
    
         
            +
                      .auto_paginate
         
     | 
| 
      
 351 
     | 
    
         
            +
                      .flat_map { |discussion| discussion.notes.map { |note| note.merge({"discussion_id" => discussion.id}) } }
         
     | 
| 
      
 352 
     | 
    
         
            +
                      .select { |comment| Comment.from_gitlab(comment).inline? }
         
     | 
| 
      
 353 
     | 
    
         
            +
             
     | 
| 
      
 354 
     | 
    
         
            +
                    danger_comments = comments.select { |comment| Comment.from_gitlab(comment).generated_by_danger?(danger_id) }
         
     | 
| 
      
 355 
     | 
    
         
            +
                    non_danger_comments = comments - danger_comments
         
     | 
| 
      
 356 
     | 
    
         
            +
             
     | 
| 
      
 357 
     | 
    
         
            +
                    diff_lines = []
         
     | 
| 
      
 358 
     | 
    
         
            +
             
     | 
| 
      
 359 
     | 
    
         
            +
                    warnings = submit_inline_comments_for_kind!(:warning, warnings, diff_lines, danger_comments, previous_violations["warning"], danger_id: danger_id)
         
     | 
| 
      
 360 
     | 
    
         
            +
                    errors = submit_inline_comments_for_kind!(:error, errors, diff_lines, danger_comments, previous_violations["error"], danger_id: danger_id)
         
     | 
| 
      
 361 
     | 
    
         
            +
                    messages = submit_inline_comments_for_kind!(:message, messages, diff_lines, danger_comments, previous_violations["message"], danger_id: danger_id)
         
     | 
| 
      
 362 
     | 
    
         
            +
                    markdowns = submit_inline_comments_for_kind!(:markdown, markdowns, diff_lines, danger_comments, [], danger_id: danger_id)
         
     | 
| 
      
 363 
     | 
    
         
            +
             
     | 
| 
      
 364 
     | 
    
         
            +
                    # submit removes from the array all comments that are still in force
         
     | 
| 
      
 365 
     | 
    
         
            +
                    # so we strike out all remaining ones
         
     | 
| 
      
 366 
     | 
    
         
            +
                    danger_comments.each do |comment|
         
     | 
| 
      
 367 
     | 
    
         
            +
                      violation = violations_from_table(comment["body"]).first
         
     | 
| 
      
 368 
     | 
    
         
            +
                      if !violation.nil? && violation.sticky
         
     | 
| 
      
 369 
     | 
    
         
            +
                        body = generate_inline_comment_body("white_check_mark", violation, danger_id: danger_id, resolved: true, template: "gitlab")
         
     | 
| 
      
 370 
     | 
    
         
            +
                        client.update_merge_request_discussion_note(ci_source.repo_slug, ci_source.pull_request_id, comment["discussion_id"], comment["id"], body: body)
         
     | 
| 
      
 371 
     | 
    
         
            +
                      else
         
     | 
| 
      
 372 
     | 
    
         
            +
                        # We remove non-sticky violations that have no replies
         
     | 
| 
      
 373 
     | 
    
         
            +
                        # Since there's no direct concept of a reply in GH, we simply consider
         
     | 
| 
      
 374 
     | 
    
         
            +
                        # the existence of non-danger comments in that line as replies
         
     | 
| 
      
 375 
     | 
    
         
            +
                        replies = non_danger_comments.select do |potential|
         
     | 
| 
      
 376 
     | 
    
         
            +
                          potential["path"] == comment["path"] &&
         
     | 
| 
      
 377 
     | 
    
         
            +
                            potential["position"] == comment["position"] &&
         
     | 
| 
      
 378 
     | 
    
         
            +
                            potential["commit_id"] == comment["commit_id"]
         
     | 
| 
      
 379 
     | 
    
         
            +
                        end
         
     | 
| 
      
 380 
     | 
    
         
            +
             
     | 
| 
      
 381 
     | 
    
         
            +
                        client.delete_merge_request_comment(ci_source.repo_slug, ci_source.pull_request_id, comment["id"]) if replies.empty?
         
     | 
| 
      
 382 
     | 
    
         
            +
                      end
         
     | 
| 
      
 383 
     | 
    
         
            +
                    end
         
     | 
| 
      
 384 
     | 
    
         
            +
             
     | 
| 
      
 385 
     | 
    
         
            +
                    {
         
     | 
| 
      
 386 
     | 
    
         
            +
                      warnings: warnings,
         
     | 
| 
      
 387 
     | 
    
         
            +
                      errors: errors,
         
     | 
| 
      
 388 
     | 
    
         
            +
                      messages: messages,
         
     | 
| 
      
 389 
     | 
    
         
            +
                      markdowns: markdowns
         
     | 
| 
      
 390 
     | 
    
         
            +
                    }
         
     | 
| 
      
 391 
     | 
    
         
            +
                  end
         
     | 
| 
      
 392 
     | 
    
         
            +
             
     | 
| 
      
 393 
     | 
    
         
            +
                  def submit_inline_comments_for_kind!(kind, messages, diff_lines, danger_comments, previous_violations, danger_id: "danger")
         
     | 
| 
      
 394 
     | 
    
         
            +
                    previous_violations ||= []
         
     | 
| 
      
 395 
     | 
    
         
            +
                    is_markdown_content = kind == :markdown
         
     | 
| 
      
 396 
     | 
    
         
            +
                    emoji = { warning: "warning", error: "no_entry_sign", message: "book" }[kind]
         
     | 
| 
      
 397 
     | 
    
         
            +
             
     | 
| 
      
 398 
     | 
    
         
            +
                    messages.reject do |m|
         
     | 
| 
      
 399 
     | 
    
         
            +
                      next false unless m.file && m.line
         
     | 
| 
      
 400 
     | 
    
         
            +
             
     | 
| 
      
 401 
     | 
    
         
            +
                      # Keep the change it's in a file changed in this diff
         
     | 
| 
      
 402 
     | 
    
         
            +
                      next if !mr_changed_paths.include?(m.file)
         
     | 
| 
      
 403 
     | 
    
         
            +
             
     | 
| 
      
 404 
     | 
    
         
            +
                      # Once we know we're gonna submit it, we format it
         
     | 
| 
      
 405 
     | 
    
         
            +
                      if is_markdown_content
         
     | 
| 
      
 406 
     | 
    
         
            +
                        body = generate_inline_markdown_body(m, danger_id: danger_id, template: "gitlab")
         
     | 
| 
      
 407 
     | 
    
         
            +
                      else
         
     | 
| 
      
 408 
     | 
    
         
            +
                        # Hide the inline link behind a span
         
     | 
| 
      
 409 
     | 
    
         
            +
                        m = process_markdown(m, true)
         
     | 
| 
      
 410 
     | 
    
         
            +
                        body = generate_inline_comment_body(emoji, m, danger_id: danger_id, template: "gitlab")
         
     | 
| 
      
 411 
     | 
    
         
            +
                        # A comment might be in previous_violations because only now it's part of the unified diff
         
     | 
| 
      
 412 
     | 
    
         
            +
                        # We remove from the array since it won't have a place in the table anymore
         
     | 
| 
      
 413 
     | 
    
         
            +
                        previous_violations.reject! { |v| messages_are_equivalent(v, m) }
         
     | 
| 
      
 414 
     | 
    
         
            +
                      end
         
     | 
| 
      
 415 
     | 
    
         
            +
             
     | 
| 
      
 416 
     | 
    
         
            +
                      matching_comments = danger_comments.select do |comment_data|
         
     | 
| 
      
 417 
     | 
    
         
            +
                        position = comment_data["position"]
         
     | 
| 
      
 418 
     | 
    
         
            +
             
     | 
| 
      
 419 
     | 
    
         
            +
                        if position.nil?
         
     | 
| 
      
 420 
     | 
    
         
            +
                          false
         
     | 
| 
      
 421 
     | 
    
         
            +
                        else
         
     | 
| 
      
 422 
     | 
    
         
            +
                          position["new_path"] == m.file && position["new_line"] == m.line
         
     | 
| 
      
 423 
     | 
    
         
            +
                        end
         
     | 
| 
      
 424 
     | 
    
         
            +
                      end
         
     | 
| 
      
 425 
     | 
    
         
            +
             
     | 
| 
      
 426 
     | 
    
         
            +
                      if matching_comments.empty?
         
     | 
| 
      
 427 
     | 
    
         
            +
                        old_position = find_old_position_in_diff mr_changes.changes, m
         
     | 
| 
      
 428 
     | 
    
         
            +
                        next false if old_position.nil?
         
     | 
| 
      
 429 
     | 
    
         
            +
             
     | 
| 
      
 430 
     | 
    
         
            +
                        params = {
         
     | 
| 
      
 431 
     | 
    
         
            +
                          body: body,
         
     | 
| 
      
 432 
     | 
    
         
            +
                          position: {
         
     | 
| 
      
 433 
     | 
    
         
            +
                            position_type: 'text',
         
     | 
| 
      
 434 
     | 
    
         
            +
                            new_path: m.file,
         
     | 
| 
      
 435 
     | 
    
         
            +
                            new_line: m.line,
         
     | 
| 
      
 436 
     | 
    
         
            +
                            old_path: old_position[:path],
         
     | 
| 
      
 437 
     | 
    
         
            +
                            old_line: old_position[:line],
         
     | 
| 
      
 438 
     | 
    
         
            +
                            base_sha: self.mr_json.diff_refs.base_sha,
         
     | 
| 
      
 439 
     | 
    
         
            +
                            start_sha: self.mr_json.diff_refs.start_sha,
         
     | 
| 
      
 440 
     | 
    
         
            +
                            head_sha: self.mr_json.diff_refs.head_sha
         
     | 
| 
      
 441 
     | 
    
         
            +
                          }
         
     | 
| 
      
 442 
     | 
    
         
            +
                        }
         
     | 
| 
      
 443 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 444 
     | 
    
         
            +
                          client.create_merge_request_discussion(ci_source.repo_slug, ci_source.pull_request_id, params)
         
     | 
| 
      
 445 
     | 
    
         
            +
                        rescue Gitlab::Error::Error => e
         
     | 
| 
      
 446 
     | 
    
         
            +
                          message = [e, "body: #{body}", "position: #{params[:position].inspect}"].join("\n")
         
     | 
| 
      
 447 
     | 
    
         
            +
                          puts message
         
     | 
| 
      
 448 
     | 
    
         
            +
             
     | 
| 
      
 449 
     | 
    
         
            +
                          next false
         
     | 
| 
      
 450 
     | 
    
         
            +
                        end
         
     | 
| 
      
 451 
     | 
    
         
            +
                      else
         
     | 
| 
      
 452 
     | 
    
         
            +
                        # Remove the surviving comment so we don't strike it out
         
     | 
| 
      
 453 
     | 
    
         
            +
                        danger_comments.reject! { |c| matching_comments.include? c }
         
     | 
| 
      
 454 
     | 
    
         
            +
             
     | 
| 
      
 455 
     | 
    
         
            +
                        # Update the comment to remove the strikethrough if present
         
     | 
| 
      
 456 
     | 
    
         
            +
                        comment = matching_comments.first
         
     | 
| 
      
 457 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 458 
     | 
    
         
            +
                          client.update_merge_request_discussion_note(ci_source.repo_slug, ci_source.pull_request_id, comment["discussion_id"], comment["id"], body: body)
         
     | 
| 
      
 459 
     | 
    
         
            +
                        rescue Gitlab::Error::Error => e
         
     | 
| 
      
 460 
     | 
    
         
            +
                          message = [e, "body: #{body}"].join("\n")
         
     | 
| 
      
 461 
     | 
    
         
            +
                          puts message
         
     | 
| 
      
 462 
     | 
    
         
            +
             
     | 
| 
      
 463 
     | 
    
         
            +
                          next false
         
     | 
| 
      
 464 
     | 
    
         
            +
                        end
         
     | 
| 
      
 465 
     | 
    
         
            +
                      end
         
     | 
| 
      
 466 
     | 
    
         
            +
             
     | 
| 
      
 467 
     | 
    
         
            +
                      # Remove this element from the array
         
     | 
| 
      
 468 
     | 
    
         
            +
                      next true
         
     | 
| 
      
 469 
     | 
    
         
            +
                    end
         
     | 
| 
      
 470 
     | 
    
         
            +
                  end
         
     | 
| 
      
 471 
     | 
    
         
            +
             
     | 
| 
      
 472 
     | 
    
         
            +
                  def find_old_position_in_diff(changes, message)
         
     | 
| 
      
 473 
     | 
    
         
            +
                    range_header_regexp = /@@ -(?<old>[0-9]+)(,([0-9]+))? \+(?<new>[0-9]+)(,([0-9]+))? @@.*/
         
     | 
| 
      
 474 
     | 
    
         
            +
             
     | 
| 
      
 475 
     | 
    
         
            +
                    change = changes.find { |c| c["new_path"] == message.file }
         
     | 
| 
      
 476 
     | 
    
         
            +
             
     | 
| 
      
 477 
     | 
    
         
            +
                    # If there is no changes or rename only or deleted, return nil.
         
     | 
| 
      
 478 
     | 
    
         
            +
                    return nil if change.nil? || change["diff"].empty? || change["deleted_file"]
         
     | 
| 
      
 479 
     | 
    
         
            +
             
     | 
| 
      
 480 
     | 
    
         
            +
                    modified_position = {
         
     | 
| 
      
 481 
     | 
    
         
            +
                      path: change["old_path"],
         
     | 
| 
      
 482 
     | 
    
         
            +
                      line: nil
         
     | 
| 
      
 483 
     | 
    
         
            +
                    }
         
     | 
| 
      
 484 
     | 
    
         
            +
             
     | 
| 
      
 485 
     | 
    
         
            +
                    # If the file is new one, old line number must be nil.
         
     | 
| 
      
 486 
     | 
    
         
            +
                    return modified_position if change["new_file"]
         
     | 
| 
      
 487 
     | 
    
         
            +
             
     | 
| 
      
 488 
     | 
    
         
            +
                    current_old_line = 0
         
     | 
| 
      
 489 
     | 
    
         
            +
                    current_new_line = 0
         
     | 
| 
      
 490 
     | 
    
         
            +
             
     | 
| 
      
 491 
     | 
    
         
            +
                    change["diff"].each_line do |line|
         
     | 
| 
      
 492 
     | 
    
         
            +
                      match = line.match range_header_regexp
         
     | 
| 
      
 493 
     | 
    
         
            +
             
     | 
| 
      
 494 
     | 
    
         
            +
                      if match
         
     | 
| 
      
 495 
     | 
    
         
            +
                        # If the message line is at before next diffs, break from loop.
         
     | 
| 
      
 496 
     | 
    
         
            +
                        break if message.line.to_i < match[:new].to_i
         
     | 
| 
      
 497 
     | 
    
         
            +
             
     | 
| 
      
 498 
     | 
    
         
            +
                        # The match [:old] line does not appear yet at the header position, so reduce line number.
         
     | 
| 
      
 499 
     | 
    
         
            +
                        current_old_line = match[:old].to_i - 1
         
     | 
| 
      
 500 
     | 
    
         
            +
                        current_new_line = match[:new].to_i - 1
         
     | 
| 
      
 501 
     | 
    
         
            +
                        next
         
     | 
| 
      
 502 
     | 
    
         
            +
                      end
         
     | 
| 
      
 503 
     | 
    
         
            +
             
     | 
| 
      
 504 
     | 
    
         
            +
                      if line.start_with?("-")
         
     | 
| 
      
 505 
     | 
    
         
            +
                        current_old_line += 1
         
     | 
| 
      
 506 
     | 
    
         
            +
                      elsif line.start_with?("+")
         
     | 
| 
      
 507 
     | 
    
         
            +
                        current_new_line += 1
         
     | 
| 
      
 508 
     | 
    
         
            +
                        # If the message line starts with '+', old line number must be nil.
         
     | 
| 
      
 509 
     | 
    
         
            +
                        return modified_position if current_new_line == message.line.to_i
         
     | 
| 
      
 510 
     | 
    
         
            +
                      elsif !line.eql?("\\n")
         
     | 
| 
      
 511 
     | 
    
         
            +
                        current_old_line += 1
         
     | 
| 
      
 512 
     | 
    
         
            +
                        current_new_line += 1
         
     | 
| 
      
 513 
     | 
    
         
            +
                        # If the message line doesn't start with '+', old line number must be specified.
         
     | 
| 
      
 514 
     | 
    
         
            +
                        break if current_new_line == message.line.to_i
         
     | 
| 
      
 515 
     | 
    
         
            +
                      end
         
     | 
| 
      
 516 
     | 
    
         
            +
                    end
         
     | 
| 
      
 517 
     | 
    
         
            +
             
     | 
| 
      
 518 
     | 
    
         
            +
                    {
         
     | 
| 
      
 519 
     | 
    
         
            +
                      path: change["old_path"],
         
     | 
| 
      
 520 
     | 
    
         
            +
                      line: current_old_line - current_new_line + message.line.to_i
         
     | 
| 
      
 521 
     | 
    
         
            +
                    }
         
     | 
| 
      
 522 
     | 
    
         
            +
                  end
         
     | 
| 
      
 523 
     | 
    
         
            +
                end
         
     | 
| 
      
 524 
     | 
    
         
            +
              end
         
     | 
| 
      
 525 
     | 
    
         
            +
            end
         
     |