diff options
Diffstat (limited to 'app/models')
62 files changed, 335 insertions, 155 deletions
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 2a99c6e5c59..e39d655325f 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -4,7 +4,6 @@ class ApplicationSetting < ApplicationRecord include CacheableAttributes include CacheMarkdownField include TokenAuthenticatable - include IgnorableColumn include ChronicDurationAttribute add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption, default_enabled: true) ? :optional : :required } @@ -18,19 +17,28 @@ class ApplicationSetting < ApplicationRecord # fix a lot of tests using allow_any_instance_of include ApplicationSettingImplementation + attr_encrypted :asset_proxy_secret_key, + mode: :per_attribute_iv, + insecure_mode: true, + key: Settings.attr_encrypted_db_key_base_truncated, + algorithm: 'aes-256-cbc' + serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize serialize :domain_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize + serialize :asset_proxy_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize - ignore_column :koding_url - ignore_column :koding_enabled - ignore_column :sentry_enabled - ignore_column :sentry_dsn - ignore_column :clientside_sentry_enabled - ignore_column :clientside_sentry_dsn + self.ignored_columns += %i[ + clientside_sentry_dsn + clientside_sentry_enabled + koding_enabled + koding_url + sentry_dsn + sentry_enabled + ] cache_markdown_field :sign_in_text cache_markdown_field :help_page_text @@ -75,11 +83,11 @@ class ApplicationSetting < ApplicationRecord validates :recaptcha_site_key, presence: true, - if: :recaptcha_enabled + if: :recaptcha_or_login_protection_enabled validates :recaptcha_private_key, presence: true, - if: :recaptcha_enabled + if: :recaptcha_or_login_protection_enabled validates :akismet_api_key, presence: true, @@ -192,6 +200,17 @@ class ApplicationSetting < ApplicationRecord allow_nil: true, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: 65536 } + validates :asset_proxy_url, + presence: true, + allow_blank: false, + url: true, + if: :asset_proxy_enabled? + + validates :asset_proxy_secret_key, + presence: true, + allow_blank: false, + if: :asset_proxy_enabled? + SUPPORTED_KEY_TYPES.each do |type| validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type } end @@ -292,4 +311,8 @@ class ApplicationSetting < ApplicationRecord def self.cache_backend Gitlab::ThreadMemoryCache.cache_backend end + + def recaptcha_or_login_protection_enabled + recaptcha_enabled || login_recaptcha_protection_enabled + end end diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 55ac1e129cf..f402c0e2775 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -23,8 +23,9 @@ module ApplicationSettingImplementation akismet_enabled: false, allow_local_requests_from_web_hooks_and_services: false, allow_local_requests_from_system_hooks: true, - dns_rebinding_protection_enabled: true, + asset_proxy_enabled: false, authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand + commit_email_hostname: default_commit_email_hostname, container_registry_token_expire_delay: 5, default_artifacts_expire_in: '30 days', default_branch_protection: Settings.gitlab['default_branch_protection'], @@ -33,7 +34,9 @@ module ApplicationSettingImplementation default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_projects_limit: Settings.gitlab['default_projects_limit'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], + diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES, disabled_oauth_sign_in_sources: [], + dns_rebinding_protection_enabled: true, domain_whitelist: Settings.gitlab['domain_whitelist'], dsa_key_restriction: 0, ecdsa_key_restriction: 0, @@ -52,9 +55,11 @@ module ApplicationSettingImplementation housekeeping_gc_period: 200, housekeeping_incremental_repack_period: 10, import_sources: Settings.gitlab['import_sources'], + local_markdown_version: 0, max_artifacts_size: Settings.artifacts['max_size'], max_attachment_size: Settings.gitlab['max_attachment_size'], mirror_available: true, + outbound_local_requests_whitelist: [], password_authentication_enabled_for_git: true, password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'], performance_bar_allowed_group_id: nil, @@ -63,7 +68,10 @@ module ApplicationSettingImplementation plantuml_url: nil, polling_interval_multiplier: 1, project_export_enabled: true, + protected_ci_variables: false, + raw_blob_request_limit: 300, recaptcha_enabled: false, + login_recaptcha_protection_enabled: false, repository_checks_enabled: true, repository_storages: ['default'], require_two_factor_authentication: false, @@ -95,16 +103,10 @@ module ApplicationSettingImplementation user_default_internal_regex: nil, user_show_add_ssh_key_message: true, usage_stats_set_by_user_id: nil, - diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES, - commit_email_hostname: default_commit_email_hostname, snowplow_collector_hostname: nil, snowplow_cookie_domain: nil, snowplow_enabled: false, - snowplow_site_id: nil, - protected_ci_variables: false, - local_markdown_version: 0, - outbound_local_requests_whitelist: [], - raw_blob_request_limit: 300 + snowplow_site_id: nil } end @@ -198,6 +200,15 @@ module ApplicationSettingImplementation end end + def asset_proxy_whitelist=(values) + values = domain_strings_to_array(values) if values.is_a?(String) + + # make sure we always whitelist the running host + values << Gitlab.config.gitlab.host unless values.include?(Gitlab.config.gitlab.host) + + self[:asset_proxy_whitelist] = values + end + def repository_storages Array(read_attribute(:repository_storages)) end @@ -306,6 +317,7 @@ module ApplicationSettingImplementation values .split(DOMAIN_LIST_SEPARATOR) + .map(&:strip) .reject(&:empty?) .uniq end diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb index 0ab302a0f3e..24fcb97db6e 100644 --- a/app/models/award_emoji.rb +++ b/app/models/award_emoji.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class AwardEmoji < ApplicationRecord - DOWNVOTE_NAME = "thumbsdown".freeze - UPVOTE_NAME = "thumbsup".freeze + DOWNVOTE_NAME = "thumbsdown" + UPVOTE_NAME = "thumbsup" include Participable include GhostUser diff --git a/app/models/blob_viewer/base.rb b/app/models/blob_viewer/base.rb index df6b9bb2f0b..1c3a6599f36 100644 --- a/app/models/blob_viewer/base.rb +++ b/app/models/blob_viewer/base.rb @@ -2,7 +2,7 @@ module BlobViewer class Base - PARTIAL_PATH_PREFIX = 'projects/blob/viewers'.freeze + PARTIAL_PATH_PREFIX = 'projects/blob/viewers' class_attribute :partial_name, :loading_partial_name, :type, :extensions, :file_types, :load_async, :binary, :switcher_icon, :switcher_title, :collapse_limit, :size_limit diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index da4584228ce..1338a585c9e 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -16,7 +16,7 @@ class BroadcastMessage < ApplicationRecord default_value_for :color, '#E75E40' default_value_for :font, '#FFFFFF' - CACHE_KEY = 'broadcast_message_current_json'.freeze + CACHE_KEY = 'broadcast_message_current_json' after_commit :flush_redis_cache diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 7930bef5cf2..d558f66154e 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -11,19 +11,20 @@ module Ci include ObjectStorage::BackgroundMove include Presentable include Importable - include IgnorableColumn include Gitlab::Utils::StrongMemoize include Deployable include HasRef BuildArchivedError = Class.new(StandardError) - ignore_column :commands - ignore_column :artifacts_file - ignore_column :artifacts_metadata - ignore_column :artifacts_file_store - ignore_column :artifacts_metadata_store - ignore_column :artifacts_size + self.ignored_columns += %i[ + artifacts_file + artifacts_file_store + artifacts_metadata + artifacts_metadata_store + artifacts_size + commands + ] belongs_to :project, inverse_of: :builds belongs_to :runner @@ -444,7 +445,7 @@ module Ci end end - CI_REGISTRY_USER = 'gitlab-ci-token'.freeze + CI_REGISTRY_USER = 'gitlab-ci-token' def persisted_variables Gitlab::Ci::Variables::Collection.new.tap do |variables| diff --git a/app/models/ci/build_runner_session.rb b/app/models/ci/build_runner_session.rb index 997bf298025..8075c15bbaf 100644 --- a/app/models/ci/build_runner_session.rb +++ b/app/models/ci/build_runner_session.rb @@ -6,7 +6,7 @@ module Ci class BuildRunnerSession < ApplicationRecord extend Gitlab::Ci::Model - TERMINAL_SUBPROTOCOL = 'terminal.gitlab.com'.freeze + TERMINAL_SUBPROTOCOL = 'terminal.gitlab.com' self.table_name = 'ci_builds_runner_session' diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index e132cb045e2..b4497d8af09 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -87,6 +87,8 @@ module Ci scope :expired, -> (limit) { where('expire_at < ?', Time.now).limit(limit) } + scope :scoped_project, -> { where('ci_job_artifacts.project_id = projects.id') } + delegate :filename, :exists?, :open, to: :file enum file_type: { diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 0a943a33bbb..64e372878e6 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -203,6 +203,7 @@ module Ci scope :for_sha, -> (sha) { where(sha: sha) } scope :for_source_sha, -> (source_sha) { where(source_sha: source_sha) } scope :for_sha_or_source_sha, -> (sha) { for_sha(sha).or(for_source_sha(sha)) } + scope :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) } scope :triggered_by_merge_request, -> (merge_request) do where(source: :merge_request_event, merge_request: merge_request) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 1c1c7a5ae7a..e0e905ebfa8 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -4,7 +4,6 @@ module Ci class Runner < ApplicationRecord extend Gitlab::Ci::Model include Gitlab::SQL::Pattern - include IgnorableColumn include RedisCacheable include ChronicDurationAttribute include FromUnion @@ -36,7 +35,7 @@ module Ci FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze - ignore_column :is_shared + self.ignored_columns = %i[is_shared] has_many :builds has_many :runner_projects, inverse_of: :runner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb index 6bd7473c8ff..27d4180e5b9 100644 --- a/app/models/clusters/applications/cert_manager.rb +++ b/app/models/clusters/applications/cert_manager.rb @@ -3,7 +3,8 @@ module Clusters module Applications class CertManager < ApplicationRecord - VERSION = 'v0.5.2'.freeze + VERSION = 'v0.9.1' + CRD_VERSION = '0.9' self.table_name = 'clusters_applications_cert_managers' @@ -21,16 +22,22 @@ module Clusters validates :email, presence: true def chart - 'stable/cert-manager' + 'certmanager/cert-manager' + end + + def repository + 'https://charts.jetstack.io' end def install_command Gitlab::Kubernetes::Helm::InstallCommand.new( name: 'certmanager', + repository: repository, version: VERSION, rbac: cluster.platform_kubernetes_rbac?, chart: chart, files: files.merge(cluster_issuer_file), + preinstall: pre_install_script, postinstall: post_install_script ) end @@ -46,16 +53,30 @@ module Clusters private + def pre_install_script + [ + apply_file("https://raw.githubusercontent.com/jetstack/cert-manager/release-#{CRD_VERSION}/deploy/manifests/00-crds.yaml"), + "kubectl label --overwrite namespace #{Gitlab::Kubernetes::Helm::NAMESPACE} certmanager.k8s.io/disable-validation=true" + ] + end + def post_install_script - ["kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml"] + [retry_command(apply_file('/data/helm/certmanager/config/cluster_issuer.yaml'))] + end + + def retry_command(command) + "for i in $(seq 1 30); do #{command} && break; sleep 1s; echo \"Retrying ($i)...\"; done" end def post_delete_script [ delete_private_key, delete_crd('certificates.certmanager.k8s.io'), + delete_crd('certificaterequests.certmanager.k8s.io'), + delete_crd('challenges.certmanager.k8s.io'), delete_crd('clusterissuers.certmanager.k8s.io'), - delete_crd('issuers.certmanager.k8s.io') + delete_crd('issuers.certmanager.k8s.io'), + delete_crd('orders.certmanager.k8s.io') ].compact end @@ -75,6 +96,10 @@ module Clusters Gitlab::Kubernetes::KubectlCmd.delete("crd", definition, "--ignore-not-found") end + def apply_file(filename) + Gitlab::Kubernetes::KubectlCmd.apply_file(filename) + end + def cluster_issuer_file { 'cluster_issuer.yaml': cluster_issuer_yaml_content diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb index 1430b82c2f2..50def3ba38c 100644 --- a/app/models/clusters/applications/ingress.rb +++ b/app/models/clusters/applications/ingress.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Ingress < ApplicationRecord - VERSION = '1.1.2'.freeze + VERSION = '1.1.2' self.table_name = 'clusters_applications_ingress' diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb index 9ede0615fa3..fb74d96efe3 100644 --- a/app/models/clusters/applications/jupyter.rb +++ b/app/models/clusters/applications/jupyter.rb @@ -5,7 +5,7 @@ require 'securerandom' module Clusters module Applications class Jupyter < ApplicationRecord - VERSION = '0.9-174bbd5'.freeze + VERSION = '0.9-174bbd5' self.table_name = 'clusters_applications_jupyter' diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb index 244fe738396..a9b9374622d 100644 --- a/app/models/clusters/applications/knative.rb +++ b/app/models/clusters/applications/knative.rb @@ -3,9 +3,9 @@ module Clusters module Applications class Knative < ApplicationRecord - VERSION = '0.6.0'.freeze - REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze - METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml'.freeze + VERSION = '0.6.0' + REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts' + METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml' FETCH_IP_ADDRESS_DELAY = 30.seconds API_RESOURCES_PATH = 'config/knative/api_resources.yml' diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index 6533b7a186e..2d6af8f4f0b 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Runner < ApplicationRecord - VERSION = '0.7.0'.freeze + VERSION = '0.8.0' self.table_name = 'clusters_applications_runners' diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 97d39491b73..444e1a82c97 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -20,8 +20,8 @@ module Clusters Applications::Runner.application_name => Applications::Runner, Applications::Prometheus.application_name => Applications::Prometheus }.merge(PROJECT_ONLY_APPLICATIONS).freeze - DEFAULT_ENVIRONMENT = '*'.freeze - KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'.freeze + DEFAULT_ENVIRONMENT = '*' + KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN' belongs_to :user diff --git a/app/models/commit.rb b/app/models/commit.rb index 0889ce7e287..1470b50f396 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -35,6 +35,7 @@ class Commit MIN_SHA_LENGTH = Gitlab::Git::Commit::MIN_SHA_LENGTH COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze + EXACT_COMMIT_SHA_PATTERN = /\A#{COMMIT_SHA_PATTERN}\z/.freeze # Used by GFM to match and present link extensions on node texts and hrefs. LINK_EXTENSION_PATTERN = /(patch)/.freeze @@ -90,7 +91,7 @@ class Commit end def valid_hash?(key) - !!(/\A#{COMMIT_SHA_PATTERN}\z/ =~ key) + !!(EXACT_COMMIT_SHA_PATTERN =~ key) end def lazy(project, oid) @@ -139,6 +140,10 @@ class Commit '@' end + def self.reference_valid?(reference) + !!(reference =~ EXACT_COMMIT_SHA_PATTERN) + end + # Pattern used to extract commit references from text # # This pattern supports cross-project references. diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb index 0c800621a55..d459af23a2f 100644 --- a/app/models/concerns/cacheable_attributes.rb +++ b/app/models/concerns/cacheable_attributes.rb @@ -11,7 +11,7 @@ module CacheableAttributes class_methods do def cache_key - "#{name}:#{Gitlab::VERSION}:#{Rails.version}".freeze + "#{name}:#{Gitlab::VERSION}:#{Rails.version}" end # Can be overridden diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 71ebb586c13..cf88076ac74 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -3,7 +3,7 @@ module HasStatus extend ActiveSupport::Concern - DEFAULT_STATUS = 'created'.freeze + DEFAULT_STATUS = 'created' BLOCKED_STATUS = %w[manual scheduled].freeze AVAILABLE_STATUSES = %w[created preparing pending running success failed canceled skipped manual scheduled].freeze STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze diff --git a/app/models/concerns/ignorable_column.rb b/app/models/concerns/ignorable_column.rb deleted file mode 100644 index 3bec44dc79b..00000000000 --- a/app/models/concerns/ignorable_column.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -# Module that can be included into a model to make it easier to ignore database -# columns. -# -# Example: -# -# class User < ApplicationRecord -# include IgnorableColumn -# -# ignore_column :updated_at -# end -# -module IgnorableColumn - extend ActiveSupport::Concern - - class_methods do - def columns - super.reject { |column| ignored_columns.include?(column.name) } - end - - def ignored_columns - @ignored_columns ||= Set.new - end - - def ignore_column(*names) - ignored_columns.merge(names.map(&:to_s)) - end - end -end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index db46d7afbb9..eefe9f00836 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -73,6 +73,7 @@ module Issuable validates :author, presence: true validates :title, presence: true, length: { maximum: 255 } + validates :description, length: { maximum: Gitlab::Database::MAX_TEXT_SIZE_LIMIT }, allow_blank: true validate :milestone_is_valid scope :authored, ->(user) { where(author_id: user) } diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb index 4b428b0af83..6a44bc7c401 100644 --- a/app/models/concerns/noteable.rb +++ b/app/models/concerns/noteable.rb @@ -73,6 +73,10 @@ module Noteable .discussions(self) end + def capped_notes_count(max) + notes.limit(max).count + end + def grouped_diff_discussions(*args) # Doesn't use `discussion_notes`, because this may include commit diff notes # besides MR diff notes, that we do not want to display on the MR Changes tab. diff --git a/app/models/concerns/protected_ref_access.rb b/app/models/concerns/protected_ref_access.rb index 583751ea6ac..208937f2aff 100644 --- a/app/models/concerns/protected_ref_access.rb +++ b/app/models/concerns/protected_ref_access.rb @@ -4,9 +4,9 @@ module ProtectedRefAccess extend ActiveSupport::Concern HUMAN_ACCESS_LEVELS = { - Gitlab::Access::MAINTAINER => "Maintainers".freeze, - Gitlab::Access::DEVELOPER => "Developers + Maintainers".freeze, - Gitlab::Access::NO_ACCESS => "No one".freeze + Gitlab::Access::MAINTAINER => "Maintainers", + Gitlab::Access::DEVELOPER => "Developers + Maintainers", + Gitlab::Access::NO_ACCESS => "No one" }.freeze class_methods do diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index 116e8967651..3a486632800 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -33,8 +33,17 @@ module Routable # # Returns a single object, or nil. def find_by_full_path(path, follow_redirects: false) - order_sql = Arel.sql("(CASE WHEN routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)") - found = where_full_path_in([path]).reorder(order_sql).take + increment_counter(:routable_find_by_full_path, 'Number of calls to Routable.find_by_full_path') + + if Feature.enabled?(:routable_two_step_lookup) + # Case sensitive match first (it's cheaper and the usual case) + # If we didn't have an exact match, we perform a case insensitive search + found = joins(:route).find_by(routes: { path: path }) || where_full_path_in([path]).take + else + order_sql = Arel.sql("(CASE WHEN routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)") + found = where_full_path_in([path]).reorder(order_sql).take + end + return found if found if follow_redirects @@ -52,12 +61,23 @@ module Routable def where_full_path_in(paths) return none if paths.empty? + increment_counter(:routable_where_full_path_in, 'Number of calls to Routable.where_full_path_in') + wheres = paths.map do |path| "(LOWER(routes.path) = LOWER(#{connection.quote(path)}))" end joins(:route).where(wheres.join(' OR ')) end + + # Temporary instrumentation of method calls + def increment_counter(counter, description) + @counters[counter] ||= Gitlab::Metrics.counter(counter, description) + + @counters[counter].increment + rescue + # ignore the error + end end def full_name diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index 8b536a123fc..98842242eb6 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -9,8 +9,8 @@ require 'task_list/filter' # # Used by MergeRequest and Issue module Taskable - COMPLETED = 'completed'.freeze - INCOMPLETE = 'incomplete'.freeze + COMPLETED = 'completed' + INCOMPLETE = 'incomplete' COMPLETE_PATTERN = /(\[[xX]\])/.freeze INCOMPLETE_PATTERN = /(\[[\s]\])/.freeze ITEM_PATTERN = %r{ diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index 0bd90bd28e3..22ab326a0ab 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class DeployKey < Key - include IgnorableColumn include FromUnion has_many :deploy_keys_projects, inverse_of: :deploy_key, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent @@ -11,7 +10,7 @@ class DeployKey < Key scope :are_public, -> { where(public: true) } scope :with_projects, -> { includes(deploy_keys_projects: { project: [:route, :namespace] }) } - ignore_column :can_push + self.ignored_columns += %i[can_push] accepts_nested_attributes_for :deploy_keys_projects diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb index 33f0be91632..20e1d802178 100644 --- a/app/models/deploy_token.rb +++ b/app/models/deploy_token.rb @@ -5,10 +5,10 @@ class DeployToken < ApplicationRecord include TokenAuthenticatable include PolicyActor include Gitlab::Utils::StrongMemoize - add_authentication_token_field :token + add_authentication_token_field :token, encrypted: :optional AVAILABLE_SCOPES = %i(read_repository read_registry).freeze - GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token'.freeze + GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token' default_value_for(:expires_at) { Forever.date } diff --git a/app/models/diff_viewer/base.rb b/app/models/diff_viewer/base.rb index 527ee33b83b..22c8fe73563 100644 --- a/app/models/diff_viewer/base.rb +++ b/app/models/diff_viewer/base.rb @@ -2,7 +2,7 @@ module DiffViewer class Base - PARTIAL_PATH_PREFIX = 'projects/diffs/viewers'.freeze + PARTIAL_PATH_PREFIX = 'projects/diffs/viewers' class_attribute :partial_name, :type, :extensions, :file_types, :binary, :switcher_icon, :switcher_title diff --git a/app/models/event.rb b/app/models/event.rb index 738080eb584..392d7368033 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -2,7 +2,6 @@ class Event < ApplicationRecord include Sortable - include IgnorableColumn include FromUnion default_scope { reorder(nil) } diff --git a/app/models/gpg_key.rb b/app/models/gpg_key.rb index 116beac5c2a..995baf8565c 100644 --- a/app/models/gpg_key.rb +++ b/app/models/gpg_key.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class GpgKey < ApplicationRecord - KEY_PREFIX = '-----BEGIN PGP PUBLIC KEY BLOCK-----'.freeze - KEY_SUFFIX = '-----END PGP PUBLIC KEY BLOCK-----'.freeze + KEY_PREFIX = '-----BEGIN PGP PUBLIC KEY BLOCK-----' + KEY_SUFFIX = '-----END PGP PUBLIC KEY BLOCK-----' include ShaAttribute diff --git a/app/models/group.rb b/app/models/group.rb index 6c868b1d1f0..abe93cf3c84 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -15,6 +15,8 @@ class Group < Namespace include WithUploads include Gitlab::Utils::StrongMemoize + ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10 + has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent alias_method :members, :group_members has_many :users, through: :group_members @@ -365,6 +367,8 @@ class Group < Namespace end def max_member_access_for_user(user) + return GroupMember::NO_ACCESS unless user + return GroupMember::OWNER if user.admin? members_with_parents @@ -427,6 +431,10 @@ class Group < Namespace super || ::Gitlab::Access::OWNER_SUBGROUP_ACCESS end + def access_request_approvers_to_be_notified + members.owners.order_recent_sign_in.limit(ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT) + end + private def update_two_factor_requirement diff --git a/app/models/instance_configuration.rb b/app/models/instance_configuration.rb index a9b1962f24c..f401c23e453 100644 --- a/app/models/instance_configuration.rb +++ b/app/models/instance_configuration.rb @@ -4,8 +4,8 @@ require 'resolv' class InstanceConfiguration SSH_ALGORITHMS = %w(DSA ECDSA ED25519 RSA).freeze - SSH_ALGORITHMS_PATH = '/etc/ssh/'.freeze - CACHE_KEY = 'instance_configuration'.freeze + SSH_ALGORITHMS_PATH = '/etc/ssh/' + CACHE_KEY = 'instance_configuration' EXPIRATION_TIME = 24.hours def settings diff --git a/app/models/issue.rb b/app/models/issue.rb index caea8eadd18..75d4fc8c1c5 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -178,7 +178,7 @@ class Issue < ApplicationRecord end def moved? - !moved_to.nil? + !moved_to_id.nil? end def can_move?(user, to_project = nil) diff --git a/app/models/label.rb b/app/models/label.rb index d9455b36242..dc9f0a3d1a9 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -199,7 +199,11 @@ class Label < ApplicationRecord end def title=(value) - write_attribute(:title, sanitize_title(value)) if value.present? + write_attribute(:title, sanitize_value(value)) if value.present? + end + + def description=(value) + write_attribute(:description, sanitize_value(value)) if value.present? end ## @@ -260,7 +264,7 @@ class Label < ApplicationRecord end end - def sanitize_title(value) + def sanitize_value(value) CGI.unescapeHTML(Sanitize.clean(value.to_s)) end diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index 79a376ff0fd..40695a97d97 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -2,6 +2,7 @@ class LfsObject < ApplicationRecord include AfterCommitQueue + include EachBatch include ObjectStorage::BackgroundMove has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent diff --git a/app/models/list.rb b/app/models/list.rb index ccadd39bda2..ae7085f05a7 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true class List < ApplicationRecord + include Importable + belongs_to :board belongs_to :label - include Importable + has_many :list_user_preferences enum list_type: { backlog: 0, label: 1, closed: 2, assignee: 3, milestone: 4 } @@ -16,9 +18,24 @@ class List < ApplicationRecord scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) } scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) } - scope :preload_associations, -> { preload(:board, :label) } + + scope :preload_associations, -> (user) do + preload(:board, label: :priorities) + .with_preferences_for(user) + end + scope :ordered, -> { order(:list_type, :position) } + # Loads list with preferences for given user + # if preferences exists for user or not + scope :with_preferences_for, -> (user) do + return unless user + + includes(:list_user_preferences).where(list_user_preferences: { user_id: [user.id, nil] }) + end + + alias_method :preferences, :list_user_preferences + class << self def destroyable_types [:label] @@ -29,6 +46,31 @@ class List < ApplicationRecord end end + def preferences_for(user) + return preferences.build unless user + + if preferences.loaded? + preloaded_preferences_for(user) + else + preferences.find_or_initialize_by(user: user) + end + end + + def preloaded_preferences_for(user) + user_preferences = + preferences.find do |preference| + preference.user_id == user.id + end + + user_preferences || preferences.build(user: user) + end + + def update_preferences_for(user, preferences = {}) + return unless user + + preferences_for(user).update(preferences) + end + def destroyable? self.class.destroyable_types.include?(list_type&.to_sym) end @@ -43,6 +85,14 @@ class List < ApplicationRecord def as_json(options = {}) super(options).tap do |json| + json[:collapsed] = false + + if options.key?(:collapsed) + preferences = preferences_for(options[:current_user]) + + json[:collapsed] = preferences.collapsed? + end + if options.key?(:label) json[:label] = label.as_json( project: board.project, diff --git a/app/models/list_user_preference.rb b/app/models/list_user_preference.rb new file mode 100644 index 00000000000..fe1cc7d5425 --- /dev/null +++ b/app/models/list_user_preference.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class ListUserPreference < ApplicationRecord + belongs_to :user + belongs_to :list + + validates :user, presence: true + validates :list, presence: true + validates :user_id, uniqueness: { scope: :list_id, message: "should have only one list preference per user" } +end diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 3d6f397e599..ed5832ff989 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -3,7 +3,7 @@ class GroupMember < Member include FromUnion - SOURCE_TYPE = 'Namespace'.freeze + SOURCE_TYPE = 'Namespace' belongs_to :group, foreign_key: 'source_id' diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index c64e2669b6a..2bb5806cd21 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ProjectMember < Member - SOURCE_TYPE = 'Project'.freeze + SOURCE_TYPE = 'Project' belongs_to :project, foreign_key: 'source_id' diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 2c9dbf2585c..2402fa8e38f 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -4,7 +4,6 @@ class MergeRequestDiff < ApplicationRecord include Sortable include Importable include ManualInverseAssociation - include IgnorableColumn include EachBatch include Gitlab::Utils::StrongMemoize include ObjectStorage::BackgroundMove diff --git a/app/models/namespace/aggregation_schedule.rb b/app/models/namespace/aggregation_schedule.rb index 61a7eb4b576..ed61c807519 100644 --- a/app/models/namespace/aggregation_schedule.rb +++ b/app/models/namespace/aggregation_schedule.rb @@ -7,7 +7,7 @@ class Namespace::AggregationSchedule < ApplicationRecord self.primary_key = :namespace_id DEFAULT_LEASE_TIMEOUT = 1.5.hours.to_i - REDIS_SHARED_KEY = 'gitlab:update_namespace_statistics_delay'.freeze + REDIS_SHARED_KEY = 'gitlab:update_namespace_statistics_delay' belongs_to :namespace diff --git a/app/models/note.rb b/app/models/note.rb index a12d1eb7243..ebd13675dc9 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -14,7 +14,6 @@ class Note < ApplicationRecord include CacheMarkdownField include AfterCommitQueue include ResolvableNote - include IgnorableColumn include Editable include Gitlab::SQL::Pattern include ThrottledTouch @@ -34,7 +33,7 @@ class Note < ApplicationRecord end end - ignore_column :original_discussion_id + self.ignored_columns += %i[original_discussion_id] cache_markdown_field :note, pipeline: :note, issuable_state_filter_enabled: true @@ -89,6 +88,7 @@ class Note < ApplicationRecord delegate :title, to: :noteable, allow_nil: true validates :note, presence: true + validates :note, length: { maximum: Gitlab::Database::MAX_TEXT_SIZE_LIMIT } validates :project, presence: true, if: :for_project_noteable? # Attachments are deprecated and are handled by Markdown uploader @@ -331,6 +331,10 @@ class Note < ApplicationRecord cross_reference? && !all_referenced_mentionables_allowed?(user) end + def visible_for?(user) + !cross_reference_not_visible_for?(user) + end + def award_emoji? can_be_award_emoji? && contains_emoji_only? end diff --git a/app/models/notification_reason.rb b/app/models/notification_reason.rb index 0a13487574f..6856d397413 100644 --- a/app/models/notification_reason.rb +++ b/app/models/notification_reason.rb @@ -3,9 +3,9 @@ # Holds reasons for a notification to have been sent as well as a priority list to select which reason to use # above the rest class NotificationReason - OWN_ACTIVITY = 'own_activity'.freeze - ASSIGNED = 'assigned'.freeze - MENTIONED = 'mentioned'.freeze + OWN_ACTIVITY = 'own_activity' + ASSIGNED = 'assigned' + MENTIONED = 'mentioned' # Priority list for selecting which reason to return in the notification REASON_PRIORITY = [ diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 8306b11a7b6..637c017a342 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true class NotificationSetting < ApplicationRecord - include IgnorableColumn - - ignore_column :events + self.ignored_columns += %i[events] enum level: { global: 3, watch: 2, participating: 1, mention: 4, disabled: 0, custom: 5 } diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 27c122d3559..12ce717efd7 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class PagesDomain < ApplicationRecord - VERIFICATION_KEY = 'gitlab-pages-verification-code'.freeze + VERIFICATION_KEY = 'gitlab-pages-verification-code' VERIFICATION_THRESHOLD = 3.days.freeze SSL_RENEWAL_THRESHOLD = 30.days.freeze diff --git a/app/models/project.rb b/app/models/project.rb index 10679fb1f85..17b52d0578e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -37,8 +37,8 @@ class Project < ApplicationRecord BoardLimitExceeded = Class.new(StandardError) - STATISTICS_ATTRIBUTE = 'repositories_count'.freeze - UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze + STATISTICS_ATTRIBUTE = 'repositories_count' + UNKNOWN_IMPORT_URL = 'http://unknown.git' # Hashed Storage versions handle rolling out new storage to project and dependents models: # nil: legacy # 1: repository @@ -55,12 +55,16 @@ class Project < ApplicationRecord VALID_MIRROR_PORTS = [22, 80, 443].freeze VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze + ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10 + SORTING_PREFERENCE_FIELD = :projects_sort cache_markdown_field :description, pipeline: :description delegate :feature_available?, :builds_enabled?, :wiki_enabled?, :merge_requests_enabled?, :issues_enabled?, :pages_enabled?, :public_pages?, + :merge_requests_access_level, :issues_access_level, :wiki_access_level, + :snippets_access_level, :builds_access_level, :repository_access_level, to: :project_feature, allow_nil: true delegate :base_dir, :disk_path, :ensure_storage_path_exists, to: :storage @@ -497,6 +501,7 @@ class Project < ApplicationRecord # We require an alias to the project_mirror_data_table in order to use import_state in our queries scope :joins_import_state, -> { joins("INNER JOIN project_mirror_data import_state ON import_state.project_id = projects.id") } scope :for_group, -> (group) { where(group: group) } + scope :for_group_and_its_subgroups, ->(group) { where(namespace_id: group.self_and_descendants.select(:id)) } class << self # Searches for a list of projects based on the query given in `query`. @@ -2175,8 +2180,7 @@ class Project < ApplicationRecord hashed_storage?(:repository) && public? && repository_exists? && - Gitlab::CurrentSettings.hashed_storage_enabled && - Feature.enabled?(:object_pools, self, default_enabled: true) + Gitlab::CurrentSettings.hashed_storage_enabled end def leave_pool_repository @@ -2191,6 +2195,10 @@ class Project < ApplicationRecord pool_repository.present? end + def access_request_approvers_to_be_notified + members.maintainers.order_recent_sign_in.limit(ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT) + end + private def merge_requests_allowing_collaboration(source_branch = nil) diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index 43edfde851c..d058904dd9e 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -5,7 +5,7 @@ require "addressable/uri" class BuildkiteService < CiService include ReactiveService - ENDPOINT = "https://buildkite.com".freeze + ENDPOINT = "https://buildkite.com" prop_accessor :project_url, :token boolean_accessor :enable_ssl_verification diff --git a/app/models/project_services/chat_message/base_message.rb b/app/models/project_services/chat_message/base_message.rb index 8c68ddc40f2..6542112ba32 100644 --- a/app/models/project_services/chat_message/base_message.rb +++ b/app/models/project_services/chat_message/base_message.rb @@ -10,6 +10,7 @@ module ChatMessage attr_reader :user_avatar attr_reader :project_name attr_reader :project_url + attr_reader :commit_message_html def initialize(params) @markdown = params[:markdown] || false @@ -18,6 +19,7 @@ module ChatMessage @user_full_name = params.dig(:user, :name) || params[:user_full_name] @user_name = params.dig(:user, :username) || params[:user_name] @user_avatar = params.dig(:user, :avatar_url) || params[:user_avatar] + @commit_message_html = params[:commit_message_html] || false end def user_combined_name diff --git a/app/models/project_services/chat_message/push_message.rb b/app/models/project_services/chat_message/push_message.rb index 5dd0414b7e6..8163fca33a2 100644 --- a/app/models/project_services/chat_message/push_message.rb +++ b/app/models/project_services/chat_message/push_message.rb @@ -52,7 +52,8 @@ module ChatMessage end def commit_messages - commits.map { |commit| compose_commit_message(commit) }.join("\n\n") + linebreak_chars = commit_message_html ? "<br/>\n<br/>\n" : "\n\n" + commits.map { |commit| compose_commit_message(commit) }.join(linebreak_chars) end def commit_message_attachments @@ -63,6 +64,11 @@ module ChatMessage author = commit[:author][:name] id = Commit.truncate_sha(commit[:id]) message = commit[:message] + + if commit_message_html + message = message.gsub(Gitlab::Regex.breakline_regex, "<br/>\n") + end + url = commit[:url] "[#{id}](#{url}): #{message} - #{author}" diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb index 7c9ecc6b821..cb75c89136e 100644 --- a/app/models/project_services/chat_notification_service.rb +++ b/app/models/project_services/chat_notification_service.rb @@ -5,17 +5,25 @@ class ChatNotificationService < Service include ChatMessage + SUPPORTED_EVENTS = %w[ + push issue confidential_issue merge_request note confidential_note + tag_push pipeline wiki_page deployment + ].freeze + + EVENT_CHANNEL = proc { |event| "#{event}_channel" } + default_value_for :category, 'chat' prop_accessor :webhook, :username, :channel + + # Custom serialized properties initialization + prop_accessor(*SUPPORTED_EVENTS.map { |event| EVENT_CHANNEL[event] }) + boolean_accessor :notify_only_broken_pipelines, :notify_only_default_branch validates :webhook, presence: true, public_url: true, if: :activated? def initialize_properties - # Custom serialized properties initialization - self.supported_events.each { |event| self.class.prop_accessor(event_channel_name(event)) } - if properties.nil? self.properties = {} self.notify_only_broken_pipelines = true @@ -32,8 +40,7 @@ class ChatNotificationService < Service end def self.supported_events - %w[push issue confidential_issue merge_request note confidential_note tag_push - pipeline wiki_page deployment] + SUPPORTED_EVENTS end def fields @@ -139,7 +146,7 @@ class ChatNotificationService < Service end def event_channel_name(event) - "#{event}_channel" + EVENT_CHANNEL[event] end def project_name diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index d08fcd8954d..0728c83005e 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -64,7 +64,12 @@ class JiraService < IssueTrackerService end def client - @client ||= JIRA::Client.new(options) + @client ||= begin + JIRA::Client.new(options).tap do |client| + # Replaces JIRA default http client with our implementation + client.request_client = Gitlab::Jira::HttpClient.new(client.options) + end + end end def help diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb index c22a6dc26f6..2334b3f7f66 100644 --- a/app/models/project_services/microsoft_teams_service.rb +++ b/app/models/project_services/microsoft_teams_service.rb @@ -58,6 +58,6 @@ class MicrosoftTeamsService < ChatNotificationService end def custom_data(data) - super(data).merge(markdown: true) + super(data).merge(markdown: true, commit_message_html: true) end end diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb index c15993bdc06..d3fff100964 100644 --- a/app/models/project_services/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class PivotaltrackerService < Service - API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'.freeze + API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits' prop_accessor :token, :restrict_to_branch validates :token, presence: true, if: :activated? diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb index 0d35bab7f80..7324890551c 100644 --- a/app/models/project_services/pushover_service.rb +++ b/app/models/project_services/pushover_service.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class PushoverService < Service - BASE_URI = 'https://api.pushover.net/1'.freeze + BASE_URI = 'https://api.pushover.net/1' prop_accessor :api_key, :user_key, :device, :priority, :sound validates :api_key, :user_key, :priority, presence: true, if: :activated? diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb index cb16ad75d14..5bfd06476f0 100644 --- a/app/models/project_services/slash_commands_service.rb +++ b/app/models/project_services/slash_commands_service.rb @@ -35,7 +35,9 @@ class SlashCommandsService < Service chat_user = find_chat_user(params) if chat_user&.user - return Gitlab::SlashCommands::Presenters::Access.new.access_denied unless chat_user.user.can?(:use_slash_commands) + unless chat_user.user.can?(:use_slash_commands) + return Gitlab::SlashCommands::Presenters::Access.new.access_denied(project) + end Gitlab::SlashCommands::Command.new(project, chat_user, params).execute else diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb index c9ee0653d86..41e63986286 100644 --- a/app/models/remote_mirror.rb +++ b/app/models/remote_mirror.rb @@ -200,6 +200,7 @@ class RemoteMirror < ApplicationRecord result.password = '*****' if result.password result.user = '*****' if result.user && result.user != 'git' # tokens or other data may be saved as user result.to_s + rescue URI::Error end def ensure_remote! diff --git a/app/models/repository.rb b/app/models/repository.rb index b957b9b0bdd..7882b2b3036 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -3,9 +3,9 @@ require 'securerandom' class Repository - REF_MERGE_REQUEST = 'merge-requests'.freeze - REF_KEEP_AROUND = 'keep-around'.freeze - REF_ENVIRONMENTS = 'environments'.freeze + REF_MERGE_REQUEST = 'merge-requests' + REF_KEEP_AROUND = 'keep-around' + REF_ENVIRONMENTS = 'environments' ARCHIVE_CACHE_TIME = 60 # Cache archives referred to by a (mutable) ref for 1 minute ARCHIVE_CACHE_TIME_IMMUTABLE = 3600 # Cache archives referred to by an immutable reference for 1 hour @@ -239,13 +239,13 @@ class Repository def branch_exists?(branch_name) return false unless raw_repository - branch_names_include?(branch_name) + branch_names.include?(branch_name) end def tag_exists?(tag_name) return false unless raw_repository - tag_names_include?(tag_name) + tag_names.include?(tag_name) end def ref_exists?(ref) @@ -565,10 +565,10 @@ class Repository end delegate :branch_names, to: :raw_repository - cache_method_as_redis_set :branch_names, fallback: [] + cache_method :branch_names, fallback: [] delegate :tag_names, to: :raw_repository - cache_method_as_redis_set :tag_names, fallback: [] + cache_method :tag_names, fallback: [] delegate :branch_count, :tag_count, :has_visible_content?, to: :raw_repository cache_method :branch_count, fallback: 0 @@ -1130,10 +1130,6 @@ class Repository @cache ||= Gitlab::RepositoryCache.new(self) end - def redis_set_cache - @redis_set_cache ||= Gitlab::RepositorySetCache.new(self) - end - def request_store_cache @request_store_cache ||= Gitlab::RepositoryCache.new(self, backend: Gitlab::SafeRequestStore) end diff --git a/app/models/service.rb b/app/models/service.rb index f6d8fb1fb46..431c5881460 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -174,7 +174,7 @@ class Service < ApplicationRecord # Also keep track of updated properties in a similar way as ActiveModel::Dirty def self.prop_accessor(*args) args.each do |arg| - class_eval %{ + class_eval <<~RUBY, __FILE__, __LINE__ + 1 unless method_defined?(arg) def #{arg} properties['#{arg}'] @@ -198,7 +198,7 @@ class Service < ApplicationRecord def #{arg}_was updated_properties['#{arg}'] end - } + RUBY end end @@ -209,12 +209,12 @@ class Service < ApplicationRecord self.prop_accessor(*args) args.each do |arg| - class_eval %{ + class_eval <<~RUBY, __FILE__, __LINE__ + 1 def #{arg}? # '!!' is used because nil or empty string is converted to nil !!ActiveRecord::Type::Boolean.new.cast(#{arg}) end - } + RUBY end end diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb index 9a2640db9ca..a19755d286a 100644 --- a/app/models/system_note_metadata.rb +++ b/app/models/system_note_metadata.rb @@ -9,7 +9,7 @@ class SystemNoteMetadata < ApplicationRecord TYPES_WITH_CROSS_REFERENCES = %w[ commit cross_reference close duplicate - moved + moved merge ].freeze ICON_TYPES = %w[ diff --git a/app/models/todo.rb b/app/models/todo.rb index 240c91da5b6..1ec04189482 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -186,9 +186,9 @@ class Todo < ApplicationRecord def target_reference if for_commit? - target.reference_link_text(full: true) + target.reference_link_text else - target.to_reference(full: true) + target.to_reference end end diff --git a/app/models/user.rb b/app/models/user.rb index 6131a8dc710..67d730e2fa3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -13,7 +13,6 @@ class User < ApplicationRecord include Sortable include CaseSensitivity include TokenAuthenticatable - include IgnorableColumn include FeatureGate include CreatedAtFilterable include BulkMemberAccessLoad @@ -24,9 +23,11 @@ class User < ApplicationRecord DEFAULT_NOTIFICATION_LEVEL = :participating - ignore_column :external_email - ignore_column :email_provider - ignore_column :authentication_token + self.ignored_columns += %i[ + authentication_token + email_provider + external_email + ] add_authentication_token_field :incoming_email_token, token_generator: -> { SecureRandom.hex.to_i(16).to_s(36) } add_authentication_token_field :feed_token @@ -58,7 +59,7 @@ class User < ApplicationRecord :validatable, :omniauthable, :confirmable, :registerable BLOCKED_MESSAGE = "Your account has been blocked. Please contact your GitLab " \ - "administrator if you think this is an error.".freeze + "administrator if you think this is an error." # Override Devise::Models::Trackable#update_tracked_fields! # to limit database writes to at most once every hour @@ -161,6 +162,8 @@ class User < ApplicationRecord # # Note: devise :validatable above adds validations for :email and :password validates :name, presence: true, length: { maximum: 128 } + validates :first_name, length: { maximum: 255 } + validates :last_name, length: { maximum: 255 } validates :email, confirmation: true validates :notification_email, presence: true validates :notification_email, devise_email: true, if: ->(user) { user.notification_email != user.email } @@ -491,7 +494,7 @@ class User < ApplicationRecord def by_login(login) return unless login - if login.include?('@'.freeze) + if login.include?('@') unscoped.iwhere(email: login).take else unscoped.iwhere(username: login).take @@ -643,6 +646,13 @@ class User < ApplicationRecord end end + # will_save_change_to_attribute? is used by Devise to check if it is necessary + # to clear any existing reset_password_tokens before updating an authentication_key + # and login in our case is a virtual attribute to allow login by username or email. + def will_save_change_to_login? + will_save_change_to_username? || will_save_change_to_email? + end + def unique_email if !emails.exists?(email: email) && Email.exists?(email: email) errors.add(:email, _('has already been taken')) @@ -881,7 +891,15 @@ class User < ApplicationRecord end def first_name - name.split.first unless name.blank? + read_attribute(:first_name) || begin + name.split(' ').first unless name.blank? + end + end + + def last_name + read_attribute(:last_name) || begin + name.split(' ').drop(1).join(' ') unless name.blank? + end end def projects_limit_left diff --git a/app/models/user_status.rb b/app/models/user_status.rb index 6ced4f56823..016b89bae81 100644 --- a/app/models/user_status.rb +++ b/app/models/user_status.rb @@ -5,7 +5,7 @@ class UserStatus < ApplicationRecord self.primary_key = :user_id - DEFAULT_EMOJI = 'speech_balloon'.freeze + DEFAULT_EMOJI = 'speech_balloon' belongs_to :user |