helper.rb 4.7 KB
Newer Older
N
Nick Thomas 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
# frozen_string_literal: true
require 'net/http'
require 'json'

require_relative 'teammate'

module Gitlab
  module Danger
    module Helper
      ROULETTE_DATA_URL = URI.parse('https://about.gitlab.com/roulette.json').freeze

      # Returns a list of all files that have been added, modified or renamed.
      # `git.modified_files` might contain paths that already have been renamed,
      # so we need to remove them from the list.
      #
      # Considering these changes:
      #
      # - A new_file.rb
      # - D deleted_file.rb
      # - M modified_file.rb
      # - R renamed_file_before.rb -> renamed_file_after.rb
      #
      # it will return
      # ```
      # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
      # ```
      #
      # @return [Array<String>]
      def all_changed_files
        Set.new
          .merge(git.added_files.to_a)
          .merge(git.modified_files.to_a)
          .merge(git.renamed_files.map { |x| x[:after] })
          .subtract(git.renamed_files.map { |x| x[:before] })
          .to_a
          .sort
      end

      def ee?
        ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('../../CHANGELOG-EE.md')
      end

      def project_name
        ee? ? 'gitlab-ee' : 'gitlab-ce'
      end

      # Looks up the current list of GitLab team members and parses it into a
      # useful form
      #
      # @return [Array<Teammate>]
      def team
        @team ||=
          begin
            rsp = Net::HTTP.get_response(ROULETTE_DATA_URL)
            raise "Failed to read #{ROULETTE_DATA_URL}: #{rsp.code} #{rsp.message}" unless
              rsp.is_a?(Net::HTTPSuccess)

            data = JSON.parse(rsp.body)
            data.map { |hash| ::Gitlab::Danger::Teammate.new(hash) }
          rescue JSON::ParserError
            raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
          end
      end

      # Like +team+, but only returns teammates in the current project, based on
      # project_name.
      #
      # @return [Array<Teammate>]
      def project_team
        team.select { |member| member.in_project?(project_name) }
      end

      # @return [Hash<String,Array<String>>]
      def changes_by_category
        all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
          hash[category_for_file(file)] << file
        end
      end

      # Determines the category a file is in, e.g., `:frontend` or `:backend`
      # @return[Symbol]
      def category_for_file(file)
        _, category = CATEGORIES.find { |regexp, _| regexp.match?(file) }

        category || :unknown
      end

      # Returns the GFM for a category label, making its best guess if it's not
      # a category we know about.
      #
      # @return[String]
      def label_for_category(category)
        CATEGORY_LABELS.fetch(category, "~#{category}")
      end

      CATEGORY_LABELS = {
        docs: "~Documentation",
98
        none: "",
N
Nick Thomas 已提交
99 100 101 102 103 104 105 106 107 108
        qa: "~QA"
      }.freeze

      # rubocop:disable Style/RegexpLiteral
      CATEGORIES = {
        %r{\Adoc/} => :docs,
        %r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :docs,

        %r{\A(ee/)?app/(assets|views)/} => :frontend,
        %r{\A(ee/)?public/} => :frontend,
109
        %r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend,
N
Nick Thomas 已提交
110
        %r{\A(ee/)?vendor/assets/} => :frontend,
111
        %r{\Ascripts/frontend/} => :frontend,
112
        %r{(\A|/)(
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
          \.babelrc |
          \.eslintignore |
          \.eslintrc(\.yml)? |
          \.nvmrc |
          \.prettierignore |
          \.prettierrc |
          \.scss-lint.yml |
          \.stylelintrc |
          babel\.config\.js |
          jest\.config\.js |
          karma\.config\.js |
          webpack\.config\.js |
          package\.json |
          yarn\.lock
        )\z}x => :frontend,
N
Nick Thomas 已提交
128 129 130

        %r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
        %r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend,
131
        %r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend,
N
Nick Thomas 已提交
132 133
        %r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend,
        %r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend,
134
        %r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile|\.gitlab-ci\.yml)\z} => :backend,
N
Nick Thomas 已提交
135 136 137 138 139
        %r{\A[A-Z_]+_VERSION\z} => :backend,

        %r{\A(ee/)?db/} => :database,
        %r{\A(ee/)?qa/} => :qa,

140 141
        # Files that don't fit into any category are marked with :none
        %r{\A(ee/)?changelogs/} => :none,
N
Nick Thomas 已提交
142
        %r{\Alocale/gitlab\.pot\z} => :none,
143

N
Nick Thomas 已提交
144 145 146 147 148 149 150 151 152
        # Fallbacks in case the above patterns miss anything
        %r{\.rb\z} => :backend,
        %r{\.(md|txt)\z} => :docs,
        %r{\.js\z} => :frontend
      }.freeze
      # rubocop:enable Style/RegexpLiteral
    end
  end
end