1# frozen_string_literal: true 2 3module Gitlab 4 class PushOptions 5 VALID_OPTIONS = HashWithIndifferentAccess.new({ 6 merge_request: { 7 keys: [ 8 :assign, 9 :create, 10 :description, 11 :label, 12 :merge_when_pipeline_succeeds, 13 :milestone, 14 :remove_source_branch, 15 :target, 16 :title, 17 :unassign, 18 :unlabel 19 ] 20 }, 21 ci: { 22 keys: [:skip, :variable] 23 } 24 }).freeze 25 26 MULTI_VALUE_OPTIONS = [ 27 %w[ci variable], 28 %w[merge_request label], 29 %w[merge_request unlabel], 30 %w[merge_request assign], 31 %w[merge_request unassign] 32 ].freeze 33 34 NAMESPACE_ALIASES = HashWithIndifferentAccess.new({ 35 mr: :merge_request 36 }).freeze 37 38 OPTION_MATCHER = /(?<namespace>[^\.]+)\.(?<key>[^=]+)=?(?<value>.*)/.freeze 39 40 CI_SKIP = 'ci.skip' 41 42 attr_reader :options 43 44 def initialize(options = []) 45 @options = parse_options(options) 46 end 47 48 def get(*args) 49 options.dig(*args) 50 end 51 52 # Allow #to_json serialization 53 def as_json(*_args) 54 options 55 end 56 57 private 58 59 def parse_options(raw_options) 60 options = HashWithIndifferentAccess.new 61 62 Array.wrap(raw_options).each do |option| 63 namespace, key, value = parse_option(option) 64 65 next if [namespace, key].any?(&:nil?) 66 67 store_option_info(options, namespace, key, value) 68 end 69 70 options 71 end 72 73 def store_option_info(options, namespace, key, value) 74 options[namespace] ||= HashWithIndifferentAccess.new 75 76 if option_multi_value?(namespace, key) 77 options[namespace][key] ||= HashWithIndifferentAccess.new(0) 78 options[namespace][key][value] += 1 79 else 80 options[namespace][key] = value 81 end 82 end 83 84 def option_multi_value?(namespace, key) 85 MULTI_VALUE_OPTIONS.any? { |arr| arr == [namespace, key] } 86 end 87 88 def parse_option(option) 89 parts = OPTION_MATCHER.match(option) 90 return unless parts 91 92 namespace, key, value = parts.values_at(:namespace, :key, :value).map(&:strip) 93 namespace = NAMESPACE_ALIASES[namespace] if NAMESPACE_ALIASES[namespace] 94 value = value.presence || true 95 96 return unless valid_option?(namespace, key) 97 98 [namespace, key, value] 99 end 100 101 def valid_option?(namespace, key) 102 keys = VALID_OPTIONS.dig(namespace, :keys) 103 keys && keys.include?(key.to_sym) 104 end 105 end 106end 107