diff options
Diffstat (limited to 'app/models')
55 files changed, 472 insertions, 322 deletions
diff --git a/app/models/active_session.rb b/app/models/active_session.rb index 0d9c6a4a1f0..1e01f1d17e6 100644 --- a/app/models/active_session.rb +++ b/app/models/active_session.rb @@ -5,7 +5,8 @@ class ActiveSession attr_accessor :created_at, :updated_at, :session_id, :ip_address, - :browser, :os, :device_name, :device_type + :browser, :os, :device_name, :device_type, + :is_impersonated def current?(session) return false if session_id.nil? || session.id.nil? @@ -31,7 +32,8 @@ class ActiveSession device_type: client.device_type, created_at: user.current_sign_in_at || timestamp, updated_at: timestamp, - session_id: session_id + session_id: session_id, + is_impersonated: request.session[:impersonator_id].present? ) redis.pipelined do diff --git a/app/models/appearance.rb b/app/models/appearance.rb index b9ad676ca47..bdee9b2b73c 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -20,6 +20,7 @@ class Appearance < ActiveRecord::Base default_value_for :message_background_color, '#E75E40' default_value_for :message_font_color, '#FFFFFF' + default_value_for :email_header_and_footer_enabled, false mount_uploader :logo, AttachmentUploader mount_uploader :header_logo, AttachmentUploader diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index daadf9427ba..c5035797621 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -7,7 +7,7 @@ class ApplicationSetting < ActiveRecord::Base include IgnorableColumn include ChronicDurationAttribute - add_authentication_token_field :runners_registration_token, encrypted: true, fallback: true + add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption) ? :optional : :required } add_authentication_token_field :health_check_access_token DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace diff --git a/app/models/board_group_recent_visit.rb b/app/models/board_group_recent_visit.rb index 92abbb67222..f5b75270595 100644 --- a/app/models/board_group_recent_visit.rb +++ b/app/models/board_group_recent_visit.rb @@ -10,7 +10,7 @@ class BoardGroupRecentVisit < ActiveRecord::Base validates :group, presence: true validates :board, presence: true - scope :by_user_group, -> (user, group) { where(user: user, group: group).order(:updated_at) } + scope :by_user_group, -> (user, group) { where(user: user, group: group) } def self.visited!(user, board) visit = find_or_create_by(user: user, group: board.group, board: board) @@ -19,7 +19,10 @@ class BoardGroupRecentVisit < ActiveRecord::Base retry end - def self.latest(user, group) - by_user_group(user, group).last + def self.latest(user, group, count: nil) + visits = by_user_group(user, group).order(updated_at: :desc) + visits = visits.preload(:board) if count && count > 1 + + visits.first(count) end end diff --git a/app/models/board_project_recent_visit.rb b/app/models/board_project_recent_visit.rb index 7cffff906d8..2a1b14b3ae0 100644 --- a/app/models/board_project_recent_visit.rb +++ b/app/models/board_project_recent_visit.rb @@ -10,7 +10,7 @@ class BoardProjectRecentVisit < ActiveRecord::Base validates :project, presence: true validates :board, presence: true - scope :by_user_project, -> (user, project) { where(user: user, project: project).order(:updated_at) } + scope :by_user_project, -> (user, project) { where(user: user, project: project) } def self.visited!(user, board) visit = find_or_create_by(user: user, project: board.project, board: board) @@ -19,7 +19,10 @@ class BoardProjectRecentVisit < ActiveRecord::Base retry end - def self.latest(user, project) - by_user_project(user, project).last + def self.latest(user, project, count: nil) + visits = by_user_project(user, project).order(updated_at: :desc) + visits = visits.preload(:board) if count && count > 1 + + visits.first(count) end end diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb index 5450d40ea95..0d8d7d95791 100644 --- a/app/models/ci/bridge.rb +++ b/app/models/ci/bridge.rb @@ -3,14 +3,18 @@ module Ci class Bridge < CommitStatus include Ci::Processable + include Ci::Contextable include Importable include AfterCommitQueue + include HasRef include Gitlab::Utils::StrongMemoize belongs_to :project belongs_to :trigger_request validates :ref, presence: true + delegate :merge_request_event?, to: :pipeline + def self.retry(bridge, current_user) raise NotImplementedError end @@ -37,11 +41,11 @@ module Ci false end - def expanded_environment_name + def runnable? + false end - def predefined_variables - raise NotImplementedError + def expanded_environment_name end def execute_hooks diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index c902e49ee6d..a64c6051f95 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -5,6 +5,7 @@ module Ci prepend ArtifactMigratable include Ci::Processable include Ci::Metadatable + include Ci::Contextable include TokenAuthenticatable include AfterCommitQueue include ObjectStorage::BackgroundMove @@ -46,7 +47,7 @@ module Ci delegate :terminal_specification, to: :runner_session, allow_nil: true delegate :gitlab_deploy_token, to: :project delegate :trigger_short_token, to: :trigger_request, allow_nil: true - delegate :merge_request?, to: :pipeline + delegate :merge_request_event?, to: :pipeline ## # Since Gitlab 11.5, deployments records started being created right after @@ -137,7 +138,7 @@ module Ci acts_as_taggable - add_authentication_token_field :token, encrypted: true, fallback: true + add_authentication_token_field :token, encrypted: :optional before_save :update_artifacts_size, if: :artifacts_file_changed? before_save :ensure_token @@ -289,6 +290,10 @@ module Ci self.name == 'pages' end + def runnable? + true + end + def archived? return true if degenerated? @@ -398,46 +403,6 @@ module Ci options&.dig(:environment, :on_stop) end - # A slugified version of the build ref, suitable for inclusion in URLs and - # domain names. Rules: - # - # * Lowercased - # * Anything not matching [a-z0-9-] is replaced with a - - # * Maximum length is 63 bytes - # * First/Last Character is not a hyphen - def ref_slug - Gitlab::Utils.slugify(ref.to_s) - end - - ## - # Variables in the environment name scope. - # - def scoped_variables(environment: expanded_environment_name) - Gitlab::Ci::Variables::Collection.new.tap do |variables| - variables.concat(predefined_variables) - variables.concat(project.predefined_variables) - variables.concat(pipeline.predefined_variables) - variables.concat(runner.predefined_variables) if runner - variables.concat(project.deployment_variables(environment: environment)) if environment - variables.concat(yaml_variables) - variables.concat(user_variables) - variables.concat(secret_group_variables) - variables.concat(secret_project_variables(environment: environment)) - variables.concat(trigger_request.user_variables) if trigger_request - variables.concat(pipeline.variables) - variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule - end - end - - ## - # Variables that do not depend on the environment name. - # - def simple_variables - strong_memoize(:simple_variables) do - scoped_variables(environment: nil).to_runner_variables - end - end - ## # All variables, including persisted environment variables. # @@ -451,12 +416,46 @@ module Ci end end - ## - # Regular Ruby hash of scoped variables, without duplicates that are - # possible to be present in an array of hashes returned from `variables`. - # - def scoped_variables_hash - scoped_variables.to_hash + CI_REGISTRY_USER = 'gitlab-ci-token'.freeze + + def persisted_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + break variables unless persisted? + + variables + .concat(pipeline.persisted_variables) + .append(key: 'CI_JOB_ID', value: id.to_s) + .append(key: 'CI_JOB_URL', value: Gitlab::Routing.url_helpers.project_job_url(project, self)) + .append(key: 'CI_JOB_TOKEN', value: token.to_s, public: false) + .append(key: 'CI_BUILD_ID', value: id.to_s) + .append(key: 'CI_BUILD_TOKEN', value: token.to_s, public: false) + .append(key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER) + .append(key: 'CI_REGISTRY_PASSWORD', value: token.to_s, public: false) + .append(key: 'CI_REPOSITORY_URL', value: repo_url.to_s, public: false) + .concat(deploy_token_variables) + end + end + + def persisted_environment_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + break variables unless persisted? && persisted_environment.present? + + variables.concat(persisted_environment.predefined_variables) + + # Here we're passing unexpanded environment_url for runner to expand, + # and we need to make sure that CI_ENVIRONMENT_NAME and + # CI_ENVIRONMENT_SLUG so on are available for the URL be expanded. + variables.append(key: 'CI_ENVIRONMENT_URL', value: environment_url) if environment_url + end + end + + def deploy_token_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + break variables unless gitlab_deploy_token + + variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.username) + variables.append(key: 'CI_DEPLOY_PASSWORD', value: gitlab_deploy_token.token, public: false) + end end def features @@ -634,27 +633,6 @@ module Ci super || project.try(:build_coverage_regex) end - def user_variables - Gitlab::Ci::Variables::Collection.new.tap do |variables| - break variables if user.blank? - - variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s) - variables.append(key: 'GITLAB_USER_EMAIL', value: user.email) - variables.append(key: 'GITLAB_USER_LOGIN', value: user.username) - variables.append(key: 'GITLAB_USER_NAME', value: user.name) - end - end - - def secret_group_variables - return [] unless project.group - - project.group.ci_variables_for(git_ref, project) - end - - def secret_project_variables(environment: persisted_environment) - project.ci_variables_for(ref: git_ref, environment: environment) - end - def steps [Gitlab::Ci::Build::Step.from_commands(self), Gitlab::Ci::Build::Step.from_after_script(self)].compact @@ -757,7 +735,7 @@ module Ci # Virtual deployment status depending on the environment status. def deployment_status - return nil unless starts_environment? + return unless starts_environment? if success? return successful_deployment_status @@ -814,89 +792,6 @@ module Ci @unscoped_project ||= Project.unscoped.find_by(id: project_id) end - CI_REGISTRY_USER = 'gitlab-ci-token'.freeze - - def persisted_variables - Gitlab::Ci::Variables::Collection.new.tap do |variables| - break variables unless persisted? - - variables - .concat(pipeline.persisted_variables) - .append(key: 'CI_JOB_ID', value: id.to_s) - .append(key: 'CI_JOB_URL', value: Gitlab::Routing.url_helpers.project_job_url(project, self)) - .append(key: 'CI_JOB_TOKEN', value: token.to_s, public: false) - .append(key: 'CI_BUILD_ID', value: id.to_s) - .append(key: 'CI_BUILD_TOKEN', value: token.to_s, public: false) - .append(key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER) - .append(key: 'CI_REGISTRY_PASSWORD', value: token.to_s, public: false) - .append(key: 'CI_REPOSITORY_URL', value: repo_url.to_s, public: false) - .concat(deploy_token_variables) - end - end - - def predefined_variables # rubocop:disable Metrics/AbcSize - Gitlab::Ci::Variables::Collection.new.tap do |variables| - variables.append(key: 'CI', value: 'true') - variables.append(key: 'GITLAB_CI', value: 'true') - variables.append(key: 'GITLAB_FEATURES', value: project.licensed_features.join(',')) - variables.append(key: 'CI_SERVER_NAME', value: 'GitLab') - variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION) - variables.append(key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s) - variables.append(key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s) - variables.append(key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s) - variables.append(key: 'CI_SERVER_REVISION', value: Gitlab.revision) - variables.append(key: 'CI_JOB_NAME', value: name) - variables.append(key: 'CI_JOB_STAGE', value: stage) - variables.append(key: 'CI_COMMIT_SHA', value: sha) - variables.append(key: 'CI_COMMIT_SHORT_SHA', value: short_sha) - variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha) - variables.append(key: 'CI_COMMIT_REF_NAME', value: ref) - variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug) - variables.append(key: "CI_COMMIT_TAG", value: ref) if tag? - variables.append(key: "CI_PIPELINE_TRIGGERED", value: 'true') if trigger_request - variables.append(key: "CI_JOB_MANUAL", value: 'true') if action? - variables.append(key: "CI_NODE_INDEX", value: self.options[:instance].to_s) if self.options&.include?(:instance) - variables.append(key: "CI_NODE_TOTAL", value: (self.options&.dig(:parallel) || 1).to_s) - variables.concat(legacy_variables) - end - end - - def legacy_variables - Gitlab::Ci::Variables::Collection.new.tap do |variables| - variables.append(key: 'CI_BUILD_REF', value: sha) - variables.append(key: 'CI_BUILD_BEFORE_SHA', value: before_sha) - variables.append(key: 'CI_BUILD_REF_NAME', value: ref) - variables.append(key: 'CI_BUILD_REF_SLUG', value: ref_slug) - variables.append(key: 'CI_BUILD_NAME', value: name) - variables.append(key: 'CI_BUILD_STAGE', value: stage) - variables.append(key: "CI_BUILD_TAG", value: ref) if tag? - variables.append(key: "CI_BUILD_TRIGGERED", value: 'true') if trigger_request - variables.append(key: "CI_BUILD_MANUAL", value: 'true') if action? - end - end - - def persisted_environment_variables - Gitlab::Ci::Variables::Collection.new.tap do |variables| - break variables unless persisted? && persisted_environment.present? - - variables.concat(persisted_environment.predefined_variables) - - # Here we're passing unexpanded environment_url for runner to expand, - # and we need to make sure that CI_ENVIRONMENT_NAME and - # CI_ENVIRONMENT_SLUG so on are available for the URL be expanded. - variables.append(key: 'CI_ENVIRONMENT_URL', value: environment_url) if environment_url - end - end - - def deploy_token_variables - Gitlab::Ci::Variables::Collection.new.tap do |variables| - break variables unless gitlab_deploy_token - - variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.username) - variables.append(key: 'CI_DEPLOY_PASSWORD', value: gitlab_deploy_token.token, public: false) - end - end - def environment_url options&.dig(:environment, :url) || persisted_environment&.external_url end diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb index 33e61cd2111..75017f224a0 100644 --- a/app/models/ci/build_trace_chunk.rb +++ b/app/models/ci/build_trace_chunk.rb @@ -115,7 +115,7 @@ module Ci current_data = get_data unless current_data&.bytesize.to_i == CHUNK_SIZE - raise FailedToPersistDataError, 'Data is not fullfilled in a bucket' + raise FailedToPersistDataError, 'Data is not fulfilled in a bucket' end old_store_class = self.class.get_store_class(data_store) diff --git a/app/models/ci/group_variable.rb b/app/models/ci/group_variable.rb index 492d1d0329e..323ff560564 100644 --- a/app/models/ci/group_variable.rb +++ b/app/models/ci/group_variable.rb @@ -5,6 +5,7 @@ module Ci extend Gitlab::Ci::Model include HasVariable include Presentable + include Maskable belongs_to :group, class_name: "::Group" diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d4586219333..ca9725f7a04 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -39,7 +39,7 @@ module Ci # Merge requests for which the current pipeline is running against # the merge request's latest commit. - has_many :merge_requests, foreign_key: "head_pipeline_id" + has_many :merge_requests_as_head_pipeline, foreign_key: "head_pipeline_id", class_name: 'MergeRequest' has_many :pending_builds, -> { pending }, foreign_key: :commit_id, class_name: 'Ci::Build' has_many :retryable_builds, -> { latest.failed_or_canceled.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build' @@ -60,9 +60,9 @@ module Ci validates :sha, presence: { unless: :importing? } validates :ref, presence: { unless: :importing? } - validates :merge_request, presence: { if: :merge_request? } - validates :merge_request, absence: { unless: :merge_request? } - validates :tag, inclusion: { in: [false], if: :merge_request? } + validates :merge_request, presence: { if: :merge_request_event? } + validates :merge_request, absence: { unless: :merge_request_event? } + validates :tag, inclusion: { in: [false], if: :merge_request_event? } validates :status, presence: { unless: :importing? } validate :valid_commit_sha, unless: :importing? validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create @@ -179,7 +179,7 @@ module Ci scope :sort_by_merge_request_pipelines, -> do sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC' - query = ActiveRecord::Base.send(:sanitize_sql_array, [sql, sources[:merge_request]]) # rubocop:disable GitlabSecurity/PublicSend + query = ActiveRecord::Base.send(:sanitize_sql_array, [sql, sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend order(query) end @@ -196,7 +196,7 @@ module Ci end scope :triggered_by_merge_request, -> (merge_request) do - where(source: :merge_request, merge_request: merge_request) + where(source: :merge_request_event, merge_request: merge_request) end scope :detached_merge_request_pipelines, -> (merge_request) do @@ -417,10 +417,6 @@ module Ci @commit ||= Commit.lazy(project, sha) end - def branch? - super && !merge_request? - end - def stuck? pending_builds.any?(&:stuck?) end @@ -643,7 +639,7 @@ module Ci variables.append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s) variables.append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s) - if merge_request? && merge_request + if merge_request_event? && merge_request variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s) variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s) variables.concat(merge_request.predefined_variables) @@ -673,7 +669,7 @@ module Ci # All the merge requests for which the current pipeline runs/ran against def all_merge_requests @all_merge_requests ||= - if merge_request? + if merge_request_event? MergeRequest.where(id: merge_request_id) else MergeRequest.where(source_project_id: project_id, source_branch: ref) @@ -718,7 +714,7 @@ module Ci # * nil: Modified path can not be evaluated def modified_paths strong_memoize(:modified_paths) do - if merge_request? + if merge_request_event? merge_request.modified_paths elsif branch_updated? push_details.modified_paths @@ -731,7 +727,7 @@ module Ci end def triggered_by_merge_request? - merge_request? && merge_request_id.present? + merge_request_event? && merge_request_id.present? end def detached_merge_request_pipeline? @@ -777,7 +773,7 @@ module Ci end def git_ref - if merge_request? + if merge_request_event? ## # In the future, we're going to change this ref to # merge request's merged reference, such as "refs/merge-requests/:iid/merge". diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb index 4be4fdb1ff2..571c4271475 100644 --- a/app/models/ci/pipeline_enums.rb +++ b/app/models/ci/pipeline_enums.rb @@ -23,7 +23,7 @@ module Ci api: 5, external: 6, chat: 8, - merge_request: 10 + merge_request_event: 10 } end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index d82e11bbb89..ce26ee168ef 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -10,7 +10,7 @@ module Ci include FromUnion include TokenAuthenticatable - add_authentication_token_field :token, encrypted: true, migrating: true + add_authentication_token_field :token, encrypted: -> { Feature.enabled?(:ci_runners_tokens_optional_encryption) ? :optional : :required } enum access_level: { not_protected: 0, diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index 524d79014f8..64836ea4fa4 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -5,6 +5,7 @@ module Ci extend Gitlab::Ci::Model include HasVariable include Presentable + include Maskable belongs_to :project diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb index 421a923d386..80205775b6a 100644 --- a/app/models/clusters/applications/jupyter.rb +++ b/app/models/clusters/applications/jupyter.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Jupyter < ActiveRecord::Base - VERSION = 'v0.6'.freeze + VERSION = '0.9-174bbd5'.freeze self.table_name = 'clusters_applications_jupyter' @@ -75,17 +75,22 @@ module Clusters "gitlab" => { "clientId" => oauth_application.uid, "clientSecret" => oauth_application.secret, - "callbackUrl" => callback_url + "callbackUrl" => callback_url, + "gitlabProjectIdWhitelist" => [project_id] } }, "singleuser" => { "extraEnv" => { - "GITLAB_CLUSTER_ID" => cluster.id + "GITLAB_CLUSTER_ID" => cluster.id.to_s } } } end + def project_id + cluster&.project&.id + end + def gitlab_url Gitlab.config.gitlab.url end diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb index 683b45331f6..ee964fb7c93 100644 --- a/app/models/clusters/concerns/application_core.rb +++ b/app/models/clusters/concerns/application_core.rb @@ -30,6 +30,12 @@ module Clusters # Override if you need extra data synchronized # from K8s after installation end + + def update_command + install_command.tap do |command| + command.version = version + end + end end end end diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 46d0898014e..814fc591408 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -41,7 +41,7 @@ module Clusters validate :no_namespace, unless: :allow_user_defined_namespace? # We expect to be `active?` only when enabled and cluster is created (the api_url is assigned) - validates :api_url, url: true, presence: true + validates :api_url, public_url: true, presence: true validates :token, presence: true validates :ca_cert, certificate: true, allow_blank: true, if: :ca_cert_changed? diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index 094747ee48d..920b1d092dd 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -134,25 +134,25 @@ class CommitRange end def sha_from - return nil unless @commit_from + return unless @commit_from @commit_from.id end def sha_to - return nil unless @commit_to + return unless @commit_to @commit_to.id end def sha_start - return nil unless sha_from + return unless sha_from exclude_start? ? sha_from + '^' : sha_from end def commit_start - return nil unless sha_start + return unless sha_start if exclude_start? @commit_start ||= project.commit(sha_start) diff --git a/app/models/concerns/blob_language_from_git_attributes.rb b/app/models/concerns/blob_language_from_git_attributes.rb index 70213d22147..56e1276a220 100644 --- a/app/models/concerns/blob_language_from_git_attributes.rb +++ b/app/models/concerns/blob_language_from_git_attributes.rb @@ -5,7 +5,7 @@ module BlobLanguageFromGitAttributes extend ActiveSupport::Concern def language_from_gitattributes - return nil unless project + return unless project repository = project.repository repository.gitattribute(path, 'gitlab-language') diff --git a/app/models/concerns/ci/contextable.rb b/app/models/concerns/ci/contextable.rb new file mode 100644 index 00000000000..4986a42dbd2 --- /dev/null +++ b/app/models/concerns/ci/contextable.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +module Ci + ## + # This module implements methods that provide context in form of + # essential CI/CD variables that can be used by a build / bridge job. + # + module Contextable + ## + # Variables in the environment name scope. + # + def scoped_variables(environment: expanded_environment_name) + Gitlab::Ci::Variables::Collection.new.tap do |variables| + variables.concat(predefined_variables) + variables.concat(project.predefined_variables) + variables.concat(pipeline.predefined_variables) + variables.concat(runner.predefined_variables) if runnable? && runner + variables.concat(project.deployment_variables(environment: environment)) if environment + variables.concat(yaml_variables) + variables.concat(user_variables) + variables.concat(secret_group_variables) + variables.concat(secret_project_variables(environment: environment)) + variables.concat(trigger_request.user_variables) if trigger_request + variables.concat(pipeline.variables) + variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule + end + end + + ## + # Regular Ruby hash of scoped variables, without duplicates that are + # possible to be present in an array of hashes returned from `variables`. + # + def scoped_variables_hash + scoped_variables.to_hash + end + + ## + # Variables that do not depend on the environment name. + # + def simple_variables + strong_memoize(:simple_variables) do + scoped_variables(environment: nil).to_runner_variables + end + end + + def user_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + break variables if user.blank? + + variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s) + variables.append(key: 'GITLAB_USER_EMAIL', value: user.email) + variables.append(key: 'GITLAB_USER_LOGIN', value: user.username) + variables.append(key: 'GITLAB_USER_NAME', value: user.name) + end + end + + def predefined_variables # rubocop:disable Metrics/AbcSize + Gitlab::Ci::Variables::Collection.new.tap do |variables| + variables.append(key: 'CI', value: 'true') + variables.append(key: 'GITLAB_CI', value: 'true') + variables.append(key: 'GITLAB_FEATURES', value: project.licensed_features.join(',')) + variables.append(key: 'CI_SERVER_NAME', value: 'GitLab') + variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION) + variables.append(key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s) + variables.append(key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s) + variables.append(key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s) + variables.append(key: 'CI_SERVER_REVISION', value: Gitlab.revision) + variables.append(key: 'CI_JOB_NAME', value: name) + variables.append(key: 'CI_JOB_STAGE', value: stage) + variables.append(key: 'CI_COMMIT_SHA', value: sha) + variables.append(key: 'CI_COMMIT_SHORT_SHA', value: short_sha) + variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha) + variables.append(key: 'CI_COMMIT_REF_NAME', value: ref) + variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug) + variables.append(key: "CI_COMMIT_TAG", value: ref) if tag? + variables.append(key: "CI_PIPELINE_TRIGGERED", value: 'true') if trigger_request + variables.append(key: "CI_JOB_MANUAL", value: 'true') if action? + variables.append(key: "CI_NODE_INDEX", value: self.options[:instance].to_s) if self.options&.include?(:instance) + variables.append(key: "CI_NODE_TOTAL", value: (self.options&.dig(:parallel) || 1).to_s) + variables.concat(legacy_variables) + end + end + + def legacy_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + variables.append(key: 'CI_BUILD_REF', value: sha) + variables.append(key: 'CI_BUILD_BEFORE_SHA', value: before_sha) + variables.append(key: 'CI_BUILD_REF_NAME', value: ref) + variables.append(key: 'CI_BUILD_REF_SLUG', value: ref_slug) + variables.append(key: 'CI_BUILD_NAME', value: name) + variables.append(key: 'CI_BUILD_STAGE', value: stage) + variables.append(key: "CI_BUILD_TAG", value: ref) if tag? + variables.append(key: "CI_BUILD_TRIGGERED", value: 'true') if trigger_request + variables.append(key: "CI_BUILD_MANUAL", value: 'true') if action? + end + end + + def secret_group_variables + return [] unless project.group + + project.group.ci_variables_for(git_ref, project) + end + + def secret_project_variables(environment: persisted_environment) + project.ci_variables_for(ref: git_ref, environment: environment) + end + end +end diff --git a/app/models/concerns/ci/processable.rb b/app/models/concerns/ci/processable.rb index 1c78b1413a8..268fa8ec692 100644 --- a/app/models/concerns/ci/processable.rb +++ b/app/models/concerns/ci/processable.rb @@ -23,5 +23,9 @@ module Ci def expanded_environment_name raise NotImplementedError end + + def scoped_variables_hash + raise NotImplementedError + end end end diff --git a/app/models/concerns/feature_gate.rb b/app/models/concerns/feature_gate.rb index 3f84de54ad5..bb095f113e2 100644 --- a/app/models/concerns/feature_gate.rb +++ b/app/models/concerns/feature_gate.rb @@ -2,7 +2,7 @@ module FeatureGate def flipper_id - return nil if new_record? + return if new_record? "#{self.class.name}:#{id}" end diff --git a/app/models/concerns/has_ref.rb b/app/models/concerns/has_ref.rb index d7089294efc..413cd36dcaa 100644 --- a/app/models/concerns/has_ref.rb +++ b/app/models/concerns/has_ref.rb @@ -4,7 +4,7 @@ module HasRef extend ActiveSupport::Concern def branch? - !tag? + !tag? && !merge_request_event? end def git_ref @@ -14,4 +14,15 @@ module HasRef Gitlab::Git::TAG_REF_PREFIX + ref.to_s end end + + # A slugified version of the build ref, suitable for inclusion in URLs and + # domain names. Rules: + # + # * Lowercased + # * Anything not matching [a-z0-9-] is replaced with a - + # * Maximum length is 63 bytes + # * First/Last Character is not a hyphen + def ref_slug + Gitlab::Utils.slugify(ref.to_s) + end end diff --git a/app/models/concerns/has_variable.rb b/app/models/concerns/has_variable.rb index dfbe413a878..2ec42a1029b 100644 --- a/app/models/concerns/has_variable.rb +++ b/app/models/concerns/has_variable.rb @@ -21,9 +21,9 @@ module HasVariable def key=(new_key) super(new_key.to_s.strip) end + end - def to_runner_variable - { key: key, value: value, public: false } - end + def to_runner_variable + { key: key, value: value, public: false } end end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 670103bc3f3..c7ad182ab82 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -75,6 +75,7 @@ module Issuable validates :author, presence: true validates :title, presence: true, length: { maximum: 255 } + validate :milestone_is_valid scope :authored, ->(user) { where(author_id: user) } scope :recent, -> { reorder(id: :desc) } @@ -118,6 +119,16 @@ module Issuable def has_multiple_assignees? assignees.count > 1 end + + def milestone_available? + project_id == milestone&.project_id || project.ancestors_upto.compact.include?(milestone&.group) + end + + private + + def milestone_is_valid + errors.add(:milestone_id, message: "is invalid") if milestone_id.present? && !milestone_available? + end end class_methods do diff --git a/app/models/concerns/maskable.rb b/app/models/concerns/maskable.rb new file mode 100644 index 00000000000..8793f0ec965 --- /dev/null +++ b/app/models/concerns/maskable.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Maskable + extend ActiveSupport::Concern + + # * Single line + # * No escape characters + # * No variables + # * No spaces + # * Minimal length of 8 characters + # * Absolutely no fun is allowed + REGEX = /\A\w{8,}\z/ + + included do + validates :masked, inclusion: { in: [true, false] } + validates :value, format: { with: REGEX }, if: :masked? + end + + def to_runner_variable + super.merge(masked: masked?) + end +end diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb index 055ffe04646..e65bbb8ca07 100644 --- a/app/models/concerns/milestoneish.rb +++ b/app/models/concerns/milestoneish.rb @@ -46,12 +46,31 @@ module Milestoneish end end + def issue_participants_visible_by_user(user) + User.joins(:issue_assignees) + .where('issue_assignees.issue_id' => issues_visible_to_user(user).select(:id)) + .distinct + end + + def issue_labels_visible_by_user(user) + Label.joins(:label_links) + .where('label_links.target_id' => issues_visible_to_user(user).select(:id), 'label_links.target_type' => 'Issue') + .distinct + end + def sorted_issues(user) issues_visible_to_user(user).preload_associations.sort_by_attribute('label_priority') end - def sorted_merge_requests - merge_requests.sort_by_attribute('label_priority') + def sorted_merge_requests(user) + merge_requests_visible_to_user(user).sort_by_attribute('label_priority') + end + + def merge_requests_visible_to_user(user) + memoize_per_user(user, :merge_requests_visible_to_user) do + MergeRequestsFinder.new(user, issues_finder_params) + .execute.where(milestone_id: milestoneish_id) + end end def upcoming? diff --git a/app/models/concerns/mirror_authentication.rb b/app/models/concerns/mirror_authentication.rb index e3e1a0441f8..948094221e5 100644 --- a/app/models/concerns/mirror_authentication.rb +++ b/app/models/concerns/mirror_authentication.rb @@ -79,7 +79,7 @@ module MirrorAuthentication end def ssh_public_key - return nil if ssh_private_key.blank? + return if ssh_private_key.blank? comment = "git@#{::Gitlab.config.gitlab.host}" ::SSHKey.new(ssh_private_key, comment: comment).ssh_public_key diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb index de77ca3e963..d2ead7130e5 100644 --- a/app/models/concerns/reactive_caching.rb +++ b/app/models/concerns/reactive_caching.rb @@ -69,7 +69,7 @@ module ReactiveCaching def with_reactive_cache(*args, &blk) unless within_reactive_cache_lifetime?(*args) refresh_reactive_cache!(*args) - return nil + return end keep_alive_reactive_cache!(*args) diff --git a/app/models/concerns/token_authenticatable_strategies/base.rb b/app/models/concerns/token_authenticatable_strategies/base.rb index 01fb194281a..df14e6e4754 100644 --- a/app/models/concerns/token_authenticatable_strategies/base.rb +++ b/app/models/concerns/token_authenticatable_strategies/base.rb @@ -39,22 +39,6 @@ module TokenAuthenticatableStrategies instance.save! if Gitlab::Database.read_write? end - def fallback? - unless options[:fallback].in?([true, false, nil]) - raise ArgumentError, 'fallback: needs to be a boolean value!' - end - - options[:fallback] == true - end - - def migrating? - unless options[:migrating].in?([true, false, nil]) - raise ArgumentError, 'migrating: needs to be a boolean value!' - end - - options[:migrating] == true - end - def self.fabricate(model, field, options) if options[:digest] && options[:encrypted] raise ArgumentError, 'Incompatible options set!' diff --git a/app/models/concerns/token_authenticatable_strategies/encrypted.rb b/app/models/concerns/token_authenticatable_strategies/encrypted.rb index 152491aa6e9..2c7fa2c5b3c 100644 --- a/app/models/concerns/token_authenticatable_strategies/encrypted.rb +++ b/app/models/concerns/token_authenticatable_strategies/encrypted.rb @@ -2,28 +2,18 @@ module TokenAuthenticatableStrategies class Encrypted < Base - def initialize(*) - super - - if migrating? && fallback? - raise ArgumentError, '`fallback` and `migrating` options are not compatible!' - end - end - def find_token_authenticatable(token, unscoped = false) return if token.blank? - if fully_encrypted? - return find_by_encrypted_token(token, unscoped) - end - - if fallback? + if required? + find_by_encrypted_token(token, unscoped) + elsif optional? find_by_encrypted_token(token, unscoped) || find_by_plaintext_token(token, unscoped) elsif migrating? find_by_plaintext_token(token, unscoped) else - raise ArgumentError, 'Unknown encryption phase!' + raise ArgumentError, "Unknown encryption strategy: #{encrypted_strategy}!" end end @@ -41,8 +31,8 @@ module TokenAuthenticatableStrategies return super if instance.has_attribute?(encrypted_field) - if fully_encrypted? - raise ArgumentError, 'Using encrypted strategy when encrypted field is missing!' + if required? + raise ArgumentError, 'Using required encryption strategy when encrypted field is missing!' else insecure_strategy.ensure_token(instance) end @@ -53,8 +43,7 @@ module TokenAuthenticatableStrategies encrypted_token = instance.read_attribute(encrypted_field) token = Gitlab::CryptoHelper.aes256_gcm_decrypt(encrypted_token) - - token || (insecure_strategy.get_token(instance) if fallback?) + token || (insecure_strategy.get_token(instance) if optional?) end def set_token(instance, token) @@ -62,16 +51,35 @@ module TokenAuthenticatableStrategies instance[encrypted_field] = Gitlab::CryptoHelper.aes256_gcm_encrypt(token) instance[token_field] = token if migrating? - instance[token_field] = nil if fallback? + instance[token_field] = nil if optional? token end - def fully_encrypted? - !migrating? && !fallback? + def required? + encrypted_strategy == :required + end + + def migrating? + encrypted_strategy == :migrating + end + + def optional? + encrypted_strategy == :optional end protected + def encrypted_strategy + value = options[:encrypted] + value = value.call if value.is_a?(Proc) + + unless value.in?([:required, :optional, :migrating]) + raise ArgumentError, 'encrypted: needs to be a :required, :optional or :migrating!' + end + + value + end + def find_by_plaintext_token(token, unscoped) insecure_strategy.find_token_authenticatable(token, unscoped) end @@ -89,7 +97,7 @@ module TokenAuthenticatableStrategies def token_set?(instance) raw_token = instance.read_attribute(encrypted_field) - unless fully_encrypted? + unless required? raw_token ||= insecure_strategy.get_token(instance) end diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 279603496b0..805092e527a 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -41,6 +41,14 @@ class DiffNote < Note create_note_diff_file(creation_params) end + # Returns the diff file from `position` + def latest_diff_file + strong_memoize(:latest_diff_file) do + position.diff_file(project.repository) + end + end + + # Returns the diff file from `original_position` def diff_file strong_memoize(:diff_file) do enqueue_diff_file_creation_job if should_create_diff_file? diff --git a/app/models/environment.rb b/app/models/environment.rb index 87bdb52b58b..3d909cc8e5c 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -119,7 +119,7 @@ class Environment < ActiveRecord::Base def first_deployment_for(commit_sha) ref = project.repository.ref_name_for_sha(ref_path, commit_sha) - return nil unless ref + return unless ref deployment_iid = ref.split('/').last deployments.find_by(iid: deployment_iid) @@ -130,7 +130,7 @@ class Environment < ActiveRecord::Base end def formatted_external_url - return nil unless external_url + return unless external_url external_url.gsub(%r{\A.*?://}, '') end diff --git a/app/models/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb index 57283a78ea9..1e2bd3bda7f 100644 --- a/app/models/error_tracking/project_error_tracking_setting.rb +++ b/app/models/error_tracking/project_error_tracking_setting.rb @@ -2,19 +2,30 @@ module ErrorTracking class ProjectErrorTrackingSetting < ActiveRecord::Base + include Gitlab::Utils::StrongMemoize include ReactiveCaching + API_URL_PATH_REGEXP = %r{ + \A + (?<prefix>/api/0/projects/+) + (?: + (?<organization>[^/]+)/+ + (?<project>[^/]+)/* + )? + \z + }x + self.reactive_cache_key = ->(setting) { [setting.class.model_name.singular, setting.project_id] } belongs_to :project validates :api_url, length: { maximum: 255 }, public_url: true, url: { enforce_sanitization: true, ascii_only: true }, allow_nil: true - validates :api_url, presence: true, if: :enabled + validates :api_url, presence: { message: 'is a required field' }, if: :enabled validate :validate_api_url_path, if: :enabled - validates :token, presence: true, if: :enabled + validates :token, presence: { message: 'is a required field' }, if: :enabled attr_encrypted :token, mode: :per_attribute_iv, @@ -23,6 +34,11 @@ module ErrorTracking after_save :clear_reactive_cache! + def api_url=(value) + super + clear_memoization(:api_url_slugs) + end + def project_name super || project_name_from_slug end @@ -40,6 +56,8 @@ module ErrorTracking end def self.build_api_url_from(api_host:, project_slug:, organization_slug:) + return if api_host.blank? + uri = Addressable::URI.parse("#{api_host}/api/0/projects/#{organization_slug}/#{project_slug}/") uri.path = uri.path.squeeze('/') @@ -100,34 +118,39 @@ module ErrorTracking end def project_slug_from_api_url - extract_slug(:project) + api_url_slug(:project) end def organization_slug_from_api_url - extract_slug(:organization) + api_url_slug(:organization) + end + + def api_url_slug(capture) + slugs = strong_memoize(:api_url_slugs) { extract_api_url_slugs || {} } + slugs[capture] end - def extract_slug(capture) + def extract_api_url_slugs return if api_url.blank? begin url = Addressable::URI.parse(api_url) rescue Addressable::URI::InvalidURIError - return nil + return end - @slug_match ||= url.path.match(%r{^/api/0/projects/+(?<organization>[^/]+)/+(?<project>[^/|$]+)}) || {} - @slug_match[capture] + url.path.match(API_URL_PATH_REGEXP) end def validate_api_url_path return if api_url.blank? - begin - unless Addressable::URI.parse(api_url).path.starts_with?('/api/0/projects') - errors.add(:api_url, 'path needs to start with /api/0/projects') - end - rescue Addressable::URI::InvalidURIError + unless api_url_slug(:prefix) + return errors.add(:api_url, 'is invalid') + end + + unless api_url_slug(:organization) + errors.add(:project, 'is a required field') end end end diff --git a/app/models/group.rb b/app/models/group.rb index 52f503404af..495bfe04499 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -56,7 +56,7 @@ class Group < Namespace validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 } - add_authentication_token_field :runners_token, encrypted: true, migrating: true + add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption) ? :optional : :required } after_create :post_create_hook after_destroy :post_destroy_hook diff --git a/app/models/individual_note_discussion.rb b/app/models/individual_note_discussion.rb index b4a661ae5b4..3b6b68a9c5f 100644 --- a/app/models/individual_note_discussion.rb +++ b/app/models/individual_note_discussion.rb @@ -14,7 +14,7 @@ class IndividualNoteDiscussion < Discussion end def can_convert_to_discussion? - noteable.supports_replying_to_individual_notes? && Feature.enabled?(:reply_to_individual_notes) + noteable.supports_replying_to_individual_notes? && Feature.enabled?(:reply_to_individual_notes, default_enabled: true) end def convert_to_discussion!(save: false) diff --git a/app/models/issue.rb b/app/models/issue.rb index 071ad50fddc..deab53d25e7 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -64,6 +64,7 @@ class Issue < ActiveRecord::Base scope :order_closest_future_date, -> { reorder('CASE WHEN issues.due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - issues.due_date) ASC') } scope :preload_associations, -> { preload(:labels, project: :namespace) } + scope :with_api_entity_associations, -> { preload(:timelogs, :assignees, :author, :notes, :labels, project: [:route, { namespace: :route }] ) } scope :public_only, -> { where(confidential: false) } scope :confidential_only, -> { where(confidential: true) } diff --git a/app/models/label_note.rb b/app/models/label_note.rb index 680952cf421..d6814f4a948 100644 --- a/app/models/label_note.rb +++ b/app/models/label_note.rb @@ -81,7 +81,7 @@ class LabelNote < Note deleted = label_refs.count - existing_refs.count deleted_str = deleted == 0 ? nil : "#{deleted} deleted" - return nil unless refs_str || deleted_str + return unless refs_str || deleted_str label_list_str = [refs_str, deleted_str].compact.join(' + ') suffix = 'label'.pluralize(deleted > 0 ? deleted : existing_refs.count) diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 00dec6bb92b..e2c75bc7ee9 100644 --- a/app/models/legacy_diff_note.rb +++ b/app/models/legacy_diff_note.rb @@ -73,7 +73,7 @@ class LegacyDiffNote < Note private def find_diff - return nil unless noteable + return unless noteable return @diff if defined?(@diff) @diff = noteable.raw_diffs(Commit.max_diff_options).find do |d| diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 1468ae1c34a..acf80addc6a 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -71,7 +71,7 @@ class MergeRequest < ActiveRecord::Base serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize - after_create :ensure_merge_request_diff, unless: :importing? + after_create :ensure_merge_request_diff after_update :clear_memoized_shas after_update :reload_diff_if_branch_changed after_save :ensure_metrics @@ -184,11 +184,21 @@ class MergeRequest < ActiveRecord::Base scope :assigned, -> { where("assignee_id IS NOT NULL") } scope :unassigned, -> { where("assignee_id IS NULL") } scope :assigned_to, ->(u) { where(assignee_id: u.id)} + scope :with_api_entity_associations, -> { + preload(:author, :assignee, :notes, :labels, :milestone, :timelogs, + latest_merge_request_diff: [:merge_request_diff_commits], + metrics: [:latest_closed_by, :merged_by], + target_project: [:route, { namespace: :route }], + source_project: [:route, { namespace: :route }]) + } participant :assignee after_save :keep_around_commit + alias_attribute :project, :target_project + alias_attribute :project_id, :target_project_id + def self.reference_prefix '!' end @@ -847,10 +857,6 @@ class MergeRequest < ActiveRecord::Base target_project != source_project end - def project - target_project - end - # If the merge request closes any issues, save this information in the # `MergeRequestsClosingIssues` model. This is a performance optimization. # Calculating this information for a number of merge requests requires @@ -1154,35 +1160,16 @@ class MergeRequest < ActiveRecord::Base Gitlab::Ci::Variables::Collection.new.tap do |variables| variables.append(key: 'CI_MERGE_REQUEST_ID', value: id.to_s) variables.append(key: 'CI_MERGE_REQUEST_IID', value: iid.to_s) - - variables.append(key: 'CI_MERGE_REQUEST_REF_PATH', - value: ref_path.to_s) - - variables.append(key: 'CI_MERGE_REQUEST_PROJECT_ID', - value: project.id.to_s) - - variables.append(key: 'CI_MERGE_REQUEST_PROJECT_PATH', - value: project.full_path) - - variables.append(key: 'CI_MERGE_REQUEST_PROJECT_URL', - value: project.web_url) - - variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME', - value: target_branch.to_s) - - if source_project - variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID', - value: source_project.id.to_s) - - variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH', - value: source_project.full_path) - - variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL', - value: source_project.web_url) - - variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME', - value: source_branch.to_s) - end + variables.append(key: 'CI_MERGE_REQUEST_REF_PATH', value: ref_path.to_s) + variables.append(key: 'CI_MERGE_REQUEST_PROJECT_ID', value: project.id.to_s) + variables.append(key: 'CI_MERGE_REQUEST_PROJECT_PATH', value: project.full_path) + variables.append(key: 'CI_MERGE_REQUEST_PROJECT_URL', value: project.web_url) + variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME', value: target_branch.to_s) + variables.append(key: 'CI_MERGE_REQUEST_TITLE', value: title) + variables.append(key: 'CI_MERGE_REQUEST_ASSIGNEES', value: assignee.username) if assignee + variables.append(key: 'CI_MERGE_REQUEST_MILESTONE', value: milestone.title) if milestone + variables.append(key: 'CI_MERGE_REQUEST_LABELS', value: label_names.join(',')) if labels.present? + variables.concat(source_project_variables) end end @@ -1389,4 +1376,15 @@ class MergeRequest < ActiveRecord::Base source_project&.ci_pipelines &.latest_for_merge_request(self, source_branch, diff_head_sha) end + + def source_project_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + break variables unless source_project + + variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID', value: source_project.id.to_s) + variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH', value: source_project.full_path) + variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL', value: source_project.web_url) + variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME', value: source_branch.to_s) + end + end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index e286a4e57f2..351a662ae83 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -22,6 +22,8 @@ class MergeRequestDiff < ActiveRecord::Base has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) } + validates :base_commit_sha, :head_commit_sha, :start_commit_sha, sha: true + state_machine :state, initial: :empty do event :clean do transition any => :without_files diff --git a/app/models/namespace.rb b/app/models/namespace.rb index f7592532c5b..a5c479bdc0c 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -149,7 +149,7 @@ class Namespace < ApplicationRecord end def find_fork_of(project) - return nil unless project.fork_network + return unless project.fork_network if Gitlab::SafeRequestStore.active? forks_in_namespace = Gitlab::SafeRequestStore.fetch("namespaces:#{id}:forked_projects") do diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb index 481c1d963c6..793cce191fa 100644 --- a/app/models/notification_recipient.rb +++ b/app/models/notification_recipient.rb @@ -119,7 +119,7 @@ class NotificationRecipient private def read_ability - return nil if @skip_read_ability + return if @skip_read_ability return @read_ability if instance_variable_defined?(:@read_ability) @read_ability = @@ -136,7 +136,7 @@ class NotificationRecipient end def default_project - return nil if @target.nil? + return if @target.nil? return @target if @target.is_a?(Project) return @target.project if @target.respond_to?(:project) end diff --git a/app/models/project.rb b/app/models/project.rb index 47baf899222..4cc13f372c1 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -85,7 +85,7 @@ class Project < ActiveRecord::Base default_value_for :snippets_enabled, gitlab_config_features.snippets default_value_for :only_allow_merge_if_all_discussions_are_resolved, false - add_authentication_token_field :runners_token, encrypted: true, migrating: true + add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption) ? :optional : :required } before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } @@ -1230,7 +1230,7 @@ class Project < ActiveRecord::Base end def fork_source - return nil unless forked? + return unless forked? forked_from_project || fork_network&.root_project end @@ -1679,7 +1679,7 @@ class Project < ActiveRecord::Base end def export_path - return nil unless namespace.present? || hashed_storage?(:repository) + return unless namespace.present? || hashed_storage?(:repository) import_export_shared.archive_path end @@ -1970,9 +1970,19 @@ class Project < ActiveRecord::Base return unless storage_upgradable? if git_transfer_in_progress? - ProjectMigrateHashedStorageWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id) + HashedStorage::ProjectMigrateWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id) else - ProjectMigrateHashedStorageWorker.perform_async(id) + HashedStorage::ProjectMigrateWorker.perform_async(id) + end + end + + def rollback_to_legacy_storage! + return if legacy_storage? + + if git_transfer_in_progress? + HashedStorage::ProjectRollbackWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id) + else + HashedStorage::ProjectRollbackWorker.perform_async(id) end end diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index f700090a493..e6787236c4e 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -76,7 +76,7 @@ class ProjectFeature < ActiveRecord::Base # This feature might not be behind a feature flag at all, so default to true return false unless ::Feature.enabled?(feature, user, default_enabled: true) - get_permission(user, access_level(feature)) + get_permission(user, feature) end def access_level(feature) @@ -134,12 +134,12 @@ class ProjectFeature < ActiveRecord::Base (FEATURES - %i(pages)).each {|f| validator.call("#{f}_access_level")} end - def get_permission(user, level) - case level + def get_permission(user, feature) + case access_level(feature) when DISABLED false when PRIVATE - user && (project.team.member?(user) || user.full_private_access?) + team_access?(user, feature) when ENABLED true when PUBLIC @@ -148,4 +148,11 @@ class ProjectFeature < ActiveRecord::Base true end end + + def team_access?(user, feature) + return unless user + return true if user.full_private_access? + + project.team.member?(user, ProjectFeature.required_minimum_access_level(feature)) + end end diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index 1d7877a1fb5..ad26e42a21b 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -57,7 +57,7 @@ class CampfireService < Service # https://github.com/basecamp/campfire-api/blob/master/sections/messages.md#create-message def speak(room_name, message, auth) room = rooms(auth).find { |r| r["name"] == room_name } - return nil unless room + return unless room path = "/room/#{room["id"]}/speak.json" body = { diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 83fd9a34438..fb76bc89c98 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -112,7 +112,7 @@ class IrkerService < Service end def consider_uri(uri) - return nil if uri.scheme.nil? + return if uri.scheme.nil? # Authorize both irc://domain.com/#chan and irc://domain.com/chan if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil? diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 9066a0b7f1d..f7064d5aaea 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -215,7 +215,7 @@ class JiraService < IssueTrackerService end def add_issue_solved_comment(issue, commit_id, commit_url) - link_title = "GitLab: Solved by commit #{commit_id}." + link_title = "Solved by commit #{commit_id}." comment = "Issue solved with [#{commit_id}|#{commit_url}]." link_props = build_remote_link_props(url: commit_url, title: link_title, resolved: true) send_message(issue, comment, link_props) @@ -230,7 +230,7 @@ class JiraService < IssueTrackerService project_name = data[:project][:name] message = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title.chomp}'" - link_title = "GitLab: Mentioned on #{entity_name} - #{entity_title}" + link_title = "#{entity_name.capitalize} - #{entity_title}" link_props = build_remote_link_props(url: entity_url, title: link_title) unless comment_exists?(issue, message) @@ -278,6 +278,7 @@ class JiraService < IssueTrackerService { GlobalID: 'GitLab', + relationship: 'mentioned on', object: { url: url, title: title, diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index 60cb2d380d5..c68a9d923c8 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -71,7 +71,7 @@ class PrometheusService < MonitoringService end def prometheus_client - RestClient::Resource.new(api_url, max_redirects: 0) if api_url && manual_configuration? && active? + RestClient::Resource.new(api_url, max_redirects: 0) if should_return_client? end def prometheus_available? @@ -83,6 +83,10 @@ class PrometheusService < MonitoringService private + def should_return_client? + api_url && manual_configuration? && active? && valid? + end + def synchronize_service_state self.active = prometheus_available? || manual_configuration? diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index d075440b147..597431be65a 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -18,13 +18,23 @@ class ProtectedBranch < ActiveRecord::Base def self.protected?(project, ref_name) return true if project.empty_repo? && default_branch_protected? - refs = project.protected_branches.select(:name) + self.matching(ref_name, protected_refs: protected_refs(project)).present? + end - self.matching(ref_name, protected_refs: refs).present? + def self.any_protected?(project, ref_names) + protected_refs(project).any? do |protected_ref| + ref_names.any? do |ref_name| + protected_ref.matches?(ref_name) + end + end end def self.default_branch_protected? Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_FULL || Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE end + + def self.protected_refs(project) + project.protected_branches.select(:name) + end end diff --git a/app/models/repository.rb b/app/models/repository.rb index cd761a29618..851175a61b7 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -79,7 +79,7 @@ class Repository end def raw_repository - return nil unless full_path + return unless full_path @raw_repository ||= initialize_raw_repository end @@ -103,7 +103,7 @@ class Repository end def commit(ref = nil) - return nil unless exists? + return unless exists? return ref if ref.is_a?(::Commit) find_commit(ref || root_ref) diff --git a/app/models/ssh_host_key.rb b/app/models/ssh_host_key.rb index fd23cc9ac87..b6fb39ee81f 100644 --- a/app/models/ssh_host_key.rb +++ b/app/models/ssh_host_key.rb @@ -27,7 +27,7 @@ class SshHostKey def self.find_by(opts = {}) opts = HashWithIndifferentAccess.new(opts) - return nil unless opts.key?(:id) + return unless opts.key?(:id) project_id, url = opts[:id].split(':', 2) project = Project.find_by(id: project_id) diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb index 7eee4fbbe5f..09034646bff 100644 --- a/app/models/suggestion.rb +++ b/app/models/suggestion.rb @@ -19,11 +19,6 @@ class Suggestion < ApplicationRecord position.file_path end - def diff_file - repository = project.repository - position.diff_file(repository) - end - # For now, suggestions only serve as a way to send patches that # will change a single line (being able to apply multiple in the same place), # which explains `from_line` and `to_line` being the same line. diff --git a/app/models/todo.rb b/app/models/todo.rb index d9b86d941b6..2b0dee875a3 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -31,7 +31,13 @@ class Todo < ActiveRecord::Base belongs_to :note belongs_to :project belongs_to :group - belongs_to :target, polymorphic: true, touch: true # rubocop:disable Cop/PolymorphicAssociations + belongs_to :target, -> { + if self.klass.respond_to?(:with_api_entity_associations) + self.with_api_entity_associations + else + self + end + }, polymorphic: true, touch: true # rubocop:disable Cop/PolymorphicAssociations belongs_to :user delegate :name, :email, to: :author, prefix: true, allow_nil: true @@ -52,6 +58,7 @@ class Todo < ActiveRecord::Base scope :for_type, -> (type) { where(target_type: type) } scope :for_target, -> (id) { where(target_id: id) } scope :for_commit, -> (id) { where(commit_id: id) } + scope :with_api_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: :route }]) } state_machine :state, initial: :pending do event :done do diff --git a/app/models/user.rb b/app/models/user.rb index ee51c35d576..778c9e631bd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -470,7 +470,7 @@ class User < ApplicationRecord end def by_login(login) - return nil unless login + return unless login if login.include?('@'.freeze) unscoped.iwhere(email: login).take diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index b1d6d461928..64daba81dcf 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -132,7 +132,7 @@ class WikiPage # The GitLab Commit instance for this page. def version - return nil unless persisted? + return unless persisted? @version ||= @page.version end |