summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-09-06 13:18:53 +0100
committerFilipa Lacerda <filipa@gitlab.com>2017-09-06 13:18:53 +0100
commit0e1404d441b0db532046d50dcbaf754ff4d3a77e (patch)
tree87a5b482c31e846d5e799291aa9852a008732c2a /lib
parentfa0f9d60e2be51261f4f58c25d75229ba996659d (diff)
parentba302454e1191f28453929df80e13c4dd418f8c7 (diff)
downloadgitlab-ce-collapsable-pipeline-settings.tar.gz
Merge branch 'master' into collapsable-pipeline-settingscollapsable-pipeline-settings
* master: (260 commits) Enable auto-retry in GitLab CI/CD pipeline Clean up new navigation templates Wait for gitaly to boot during tests Update 'Visibility of pipelines' refactored code Fix note resolution specs Add author and MR to changelog Tidy up projects API specs Resolve outdated diff discussions on push Fix migration change collapse to resolve and comments to discussions add unit tests for new collapse_outdated_diff_comments toggle Add functionality to collapse outdated diff comments regardless of discussion resolution refactor code based on feedback fix spec failures Use flexbox for prometheus graph row grouping instead of bootstrap classes Fix wrong API status codes small refactor Hide admin link from default search results for non-admins Make search dropdowns consistent ...
Diffstat (limited to 'lib')
-rw-r--r--lib/api/branches.rb25
-rw-r--r--lib/api/commit_statuses.rb2
-rw-r--r--lib/api/entities.rb8
-rw-r--r--lib/api/issues.rb1
-rw-r--r--lib/api/merge_requests.rb1
-rw-r--r--lib/api/pipeline_schedules.rb85
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/runner.rb4
-rw-r--r--lib/api/users.rb150
-rw-r--r--lib/api/v3/entities.rb1
-rw-r--r--lib/api/v3/projects.rb7
-rw-r--r--lib/api/v3/triggers.rb32
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb49
-rw-r--r--lib/gitlab/ci/config/entry/policy.rb22
-rw-r--r--lib/gitlab/ci/config/entry/validators.rb8
-rw-r--r--lib/gitlab/conflict/file_collection.rb2
-rw-r--r--lib/gitlab/git.rb1
-rw-r--r--lib/gitlab/git/operation_service.rb168
-rw-r--r--lib/gitlab/git/repository.rb165
-rw-r--r--lib/gitlab/gitaly_client/ref_service.rb14
-rw-r--r--lib/gitlab/gpg.rb2
-rw-r--r--lib/gitlab/gpg/commit.rb34
-rw-r--r--lib/gitlab/gpg/invalid_gpg_signature_updater.rb2
-rw-r--r--lib/gitlab/i18n/po_linter.rb2
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb79
-rw-r--r--lib/gitlab/import_export/shared.rb2
-rw-r--r--lib/gitlab/issuables_count_for_state.rb50
-rw-r--r--lib/gitlab/sql/pattern.rb23
-rw-r--r--lib/system_check/app/git_user_default_ssh_config_check.rb69
-rw-r--r--lib/system_check/app/init_script_up_to_date_check.rb28
-rw-r--r--lib/system_check/base_check.rb19
-rw-r--r--lib/system_check/incoming_email/foreman_configured_check.rb23
-rw-r--r--lib/system_check/incoming_email/imap_authentication_check.rb45
-rw-r--r--lib/system_check/incoming_email/initd_configured_check.rb32
-rw-r--r--lib/system_check/incoming_email/mail_room_running_check.rb43
-rw-r--r--lib/system_check/simple_executor.rb4
-rw-r--r--lib/tasks/gettext.rake2
-rw-r--r--lib/tasks/gitlab/check.rake126
39 files changed, 1083 insertions, 250 deletions
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index a989394ad91..642c1140fcc 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -24,17 +24,22 @@ module API
present paginate(branches), with: Entities::RepoBranch, project: user_project
end
- desc 'Get a single branch' do
- success Entities::RepoBranch
- end
- params do
- requires :branch, type: String, desc: 'The name of the branch'
- end
- get ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
- branch = user_project.repository.find_branch(params[:branch])
- not_found!("Branch") unless branch
+ resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
+ desc 'Get a single branch' do
+ success Entities::RepoBranch
+ end
+ params do
+ requires :branch, type: String, desc: 'The name of the branch'
+ end
+ head do
+ user_project.repository.branch_exists?(params[:branch]) ? status(204) : status(404)
+ end
+ get do
+ branch = user_project.repository.find_branch(params[:branch])
+ not_found!('Branch') unless branch
- present branch, with: Entities::RepoBranch, project: user_project
+ present branch, with: Entities::RepoBranch, project: user_project
+ end
end
# Note: This API will be deprecated in favor of the protected branches API.
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 6314ea63197..829eef18795 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -103,7 +103,7 @@ module API
when 'success'
status.success!
when 'failed'
- status.drop!
+ status.drop!(:api_failure)
when 'canceled'
status.cancel!
else
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index f13f2d723bb..9114b69606b 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -119,6 +119,7 @@ module API
expose :archived?, as: :archived
expose :visibility
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
+ expose :resolve_outdated_diff_discussions
expose :container_registry_enabled
# Expose old field names with the new permissions methods to keep API compatible
@@ -491,6 +492,10 @@ module API
expose :user, using: Entities::UserPublic
end
+ class GPGKey < Grape::Entity
+ expose :id, :key, :created_at
+ end
+
class Note < Grape::Entity
# Only Issue and MergeRequest have iid
NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze
@@ -819,7 +824,7 @@ module API
class Variable < Grape::Entity
expose :key, :value
- expose :protected?, as: :protected
+ expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) }
end
class Pipeline < PipelineBasic
@@ -840,6 +845,7 @@ module API
class PipelineScheduleDetails < PipelineSchedule
expose :last_pipeline, using: Entities::PipelineBasic
+ expose :variables, using: Entities::Variable
end
class EnvironmentBasic < Grape::Entity
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index e4c2c390853..1729df2aad0 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -36,6 +36,7 @@ module API
optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID'
optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`'
+ optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
use :pagination
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 7bcbf9f20ff..56d72d511da 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -40,6 +40,7 @@ module API
optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID'
optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`'
+ optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
use :pagination
end
end
diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb
index ef01cbc7875..37f32411296 100644
--- a/lib/api/pipeline_schedules.rb
+++ b/lib/api/pipeline_schedules.rb
@@ -31,10 +31,6 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
get ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :read_pipeline_schedule, user_project
-
- not_found!('PipelineSchedule') unless pipeline_schedule
-
present pipeline_schedule, with: Entities::PipelineScheduleDetails
end
@@ -74,9 +70,6 @@ module API
optional :active, type: Boolean, desc: 'The activation of pipeline schedule'
end
put ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :read_pipeline_schedule, user_project
-
- not_found!('PipelineSchedule') unless pipeline_schedule
authorize! :update_pipeline_schedule, pipeline_schedule
if pipeline_schedule.update(declared_params(include_missing: false))
@@ -93,9 +86,6 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
- authorize! :read_pipeline_schedule, user_project
-
- not_found!('PipelineSchedule') unless pipeline_schedule
authorize! :update_pipeline_schedule, pipeline_schedule
if pipeline_schedule.own!(current_user)
@@ -112,21 +102,84 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
delete ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :read_pipeline_schedule, user_project
-
- not_found!('PipelineSchedule') unless pipeline_schedule
authorize! :admin_pipeline_schedule, pipeline_schedule
destroy_conditionally!(pipeline_schedule)
end
+
+ desc 'Create a new pipeline schedule variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :key, type: String, desc: 'The key of the variable'
+ requires :value, type: String, desc: 'The value of the variable'
+ end
+ post ':id/pipeline_schedules/:pipeline_schedule_id/variables' do
+ authorize! :update_pipeline_schedule, pipeline_schedule
+
+ variable_params = declared_params(include_missing: false)
+ variable = pipeline_schedule.variables.create(variable_params)
+ if variable.persisted?
+ present variable, with: Entities::Variable
+ else
+ render_validation_error!(variable)
+ end
+ end
+
+ desc 'Edit a pipeline schedule variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :key, type: String, desc: 'The key of the variable'
+ optional :value, type: String, desc: 'The value of the variable'
+ end
+ put ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
+ authorize! :update_pipeline_schedule, pipeline_schedule
+
+ if pipeline_schedule_variable.update(declared_params(include_missing: false))
+ present pipeline_schedule_variable, with: Entities::Variable
+ else
+ render_validation_error!(pipeline_schedule_variable)
+ end
+ end
+
+ desc 'Delete a pipeline schedule variable' do
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :key, type: String, desc: 'The key of the variable'
+ end
+ delete ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do
+ authorize! :admin_pipeline_schedule, pipeline_schedule
+
+ status :accepted
+ present pipeline_schedule_variable.destroy, with: Entities::Variable
+ end
end
helpers do
def pipeline_schedule
@pipeline_schedule ||=
- user_project.pipeline_schedules
- .preload(:owner, :last_pipeline)
- .find_by(id: params.delete(:pipeline_schedule_id))
+ user_project
+ .pipeline_schedules
+ .preload(:owner, :last_pipeline)
+ .find_by(id: params.delete(:pipeline_schedule_id)).tap do |pipeline_schedule|
+ unless can?(current_user, :read_pipeline_schedule, pipeline_schedule)
+ not_found!('Pipeline Schedule')
+ end
+ end
+ end
+
+ def pipeline_schedule_variable
+ @pipeline_schedule_variable ||=
+ pipeline_schedule.variables.find_by(key: params[:key]).tap do |pipeline_schedule_variable|
+ unless pipeline_schedule_variable
+ not_found!('Pipeline Schedule Variable')
+ end
+ end
end
end
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 4845242a173..7dc19788462 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -16,6 +16,7 @@ module API
optional :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs are enabled'
optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
+ optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
@@ -236,6 +237,7 @@ module API
at_least_one_of_ce =
[
:jobs_enabled,
+ :resolve_outdated_diff_discussions,
:container_registry_enabled,
:default_branch,
:description,
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index 11999354594..a3987c560dd 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -114,6 +114,8 @@ module API
requires :id, type: Integer, desc: %q(Job's ID)
optional :trace, type: String, desc: %q(Job's full trace)
optional :state, type: String, desc: %q(Job's status: success, failed)
+ optional :failure_reason, type: String, values: CommitStatus.failure_reasons.keys,
+ desc: %q(Job's failure_reason)
end
put '/:id' do
job = authenticate_job!
@@ -127,7 +129,7 @@ module API
when 'success'
job.success
when 'failed'
- job.drop
+ job.drop(params[:failure_reason] || :unknown_failure)
end
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 96f47bb618a..1825c90a23b 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -233,6 +233,86 @@ module API
destroy_conditionally!(key)
end
+ desc 'Add a GPG key to a specified user. Available only for admins.' do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :key, type: String, desc: 'The new GPG key'
+ end
+ post ':id/gpg_keys' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params.delete(:id))
+ not_found!('User') unless user
+
+ key = user.gpg_keys.new(declared_params(include_missing: false))
+
+ if key.save
+ present key, with: Entities::GPGKey
+ else
+ render_validation_error!(key)
+ end
+ end
+
+ desc 'Get the GPG keys of a specified user. Available only for admins.' do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ use :pagination
+ end
+ get ':id/gpg_keys' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ present paginate(user.gpg_keys), with: Entities::GPGKey
+ end
+
+ desc 'Delete an existing GPG key from a specified user. Available only for admins.' do
+ detail 'This feature was added in GitLab 10.0'
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :key_id, type: Integer, desc: 'The ID of the GPG key'
+ end
+ delete ':id/gpg_keys/:key_id' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ key = user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ status 204
+ key.destroy
+ end
+
+ desc 'Revokes an existing GPG key from a specified user. Available only for admins.' do
+ detail 'This feature was added in GitLab 10.0'
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the user'
+ requires :key_id, type: Integer, desc: 'The ID of the GPG key'
+ end
+ post ':id/gpg_keys/:key_id/revoke' do
+ authenticated_as_admin!
+
+ user = User.find_by(id: params[:id])
+ not_found!('User') unless user
+
+ key = user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ key.revoke
+ status :accepted
+ end
+
desc 'Add an email address to a specified user. Available only for admins.' do
success Entities::Email
end
@@ -492,6 +572,76 @@ module API
destroy_conditionally!(key)
end
+ desc "Get the currently authenticated user's GPG keys" do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ use :pagination
+ end
+ get 'gpg_keys' do
+ present paginate(current_user.gpg_keys), with: Entities::GPGKey
+ end
+
+ desc 'Get a single GPG key owned by currently authenticated user' do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the GPG key'
+ end
+ get 'gpg_keys/:key_id' do
+ key = current_user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ present key, with: Entities::GPGKey
+ end
+
+ desc 'Add a new GPG key to the currently authenticated user' do
+ detail 'This feature was added in GitLab 10.0'
+ success Entities::GPGKey
+ end
+ params do
+ requires :key, type: String, desc: 'The new GPG key'
+ end
+ post 'gpg_keys' do
+ key = current_user.gpg_keys.new(declared_params)
+
+ if key.save
+ present key, with: Entities::GPGKey
+ else
+ render_validation_error!(key)
+ end
+ end
+
+ desc 'Revoke a GPG key owned by currently authenticated user' do
+ detail 'This feature was added in GitLab 10.0'
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the GPG key'
+ end
+ post 'gpg_keys/:key_id/revoke' do
+ key = current_user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ key.revoke
+ status :accepted
+ end
+
+ desc 'Delete a GPG key from the currently authenticated user' do
+ detail 'This feature was added in GitLab 10.0'
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the SSH key'
+ end
+ delete 'gpg_keys/:key_id' do
+ key = current_user.gpg_keys.find_by(id: params[:key_id])
+ not_found!('GPG Key') unless key
+
+ status 204
+ key.destroy
+ end
+
desc "Get the currently authenticated user's email addresses" do
success Entities::Email
end
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index a9a35f2a4bd..ac47a713966 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -64,6 +64,7 @@ module API
expose :owner, using: ::API::Entities::UserBasic, unless: ->(project, options) { project.group }
expose :name, :name_with_namespace
expose :path, :path_with_namespace
+ expose :resolve_outdated_diff_discussions
expose :container_registry_enabled
# Expose old field names with the new permissions methods to keep API compatible
diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb
index 449876c10d9..74df246bdfe 100644
--- a/lib/api/v3/projects.rb
+++ b/lib/api/v3/projects.rb
@@ -18,6 +18,7 @@ module API
optional :builds_enabled, type: Boolean, desc: 'Flag indication if builds are enabled'
optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
+ optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.'
@@ -296,9 +297,9 @@ module API
use :optional_params
at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled,
:wiki_enabled, :builds_enabled, :snippets_enabled,
- :shared_runners_enabled, :container_registry_enabled,
- :lfs_enabled, :public, :visibility_level, :public_builds,
- :request_access_enabled, :only_allow_merge_if_build_succeeds,
+ :shared_runners_enabled, :resolve_outdated_diff_discussions,
+ :container_registry_enabled, :lfs_enabled, :public, :visibility_level,
+ :public_builds, :request_access_enabled, :only_allow_merge_if_build_succeeds,
:only_allow_merge_if_all_discussions_are_resolved, :path,
:default_branch
end
diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb
index e9d4c35307b..534911fde5c 100644
--- a/lib/api/v3/triggers.rb
+++ b/lib/api/v3/triggers.rb
@@ -16,25 +16,31 @@ module API
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/builds", requirements: { ref: /.+/ } do
- project = find_project(params[:id])
- trigger = Ci::Trigger.find_by_token(params[:token].to_s)
- not_found! unless project && trigger
- unauthorized! unless trigger.project == project
-
# validate variables
- variables = params[:variables].to_h
- unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
+ params[:variables] = params[:variables].to_h
+ unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) }
render_api_error!('variables needs to be a map of key-valued strings', 400)
end
- # create request and trigger builds
- result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref].to_s, variables)
- pipeline = result.pipeline
+ project = find_project(params[:id])
+ not_found! unless project
+
+ result = Ci::PipelineTriggerService.new(project, nil, params).execute
+ not_found! unless result
- if pipeline.persisted?
- present result.trigger_request, with: ::API::V3::Entities::TriggerRequest
+ if result[:http_status]
+ render_api_error!(result[:message], result[:http_status])
else
- render_validation_error!(pipeline)
+ pipeline = result[:pipeline]
+
+ # We switched to Ci::PipelineVariable from Ci::TriggerRequest.variables.
+ # Ci::TriggerRequest doesn't save variables anymore.
+ # Here is copying Ci::PipelineVariable to Ci::TriggerRequest.variables for presenting the variables.
+ # The same endpoint in v4 API pressents Pipeline instead of TriggerRequest, so it doesn't need such a process.
+ trigger_request = pipeline.trigger_requests.last
+ trigger_request.variables = params[:variables]
+
+ present trigger_request, with: ::API::V3::Entities::TriggerRequest
end
end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 3a4911b23b0..62b44389b15 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -20,24 +20,6 @@ module Ci
raise ValidationError, e.message
end
- def jobs_for_ref(ref, tag = false, source = nil)
- @jobs.select do |_, job|
- process?(job[:only], job[:except], ref, tag, source)
- end
- end
-
- def jobs_for_stage_and_ref(stage, ref, tag = false, source = nil)
- jobs_for_ref(ref, tag, source).select do |_, job|
- job[:stage] == stage
- end
- end
-
- def builds_for_ref(ref, tag = false, source = nil)
- jobs_for_ref(ref, tag, source).map do |name, _|
- build_attributes(name)
- end
- end
-
def builds_for_stage_and_ref(stage, ref, tag = false, source = nil)
jobs_for_stage_and_ref(stage, ref, tag, source).map do |name, _|
build_attributes(name)
@@ -52,8 +34,7 @@ module Ci
def stage_seeds(pipeline)
seeds = @stages.uniq.map do |stage|
- builds = builds_for_stage_and_ref(
- stage, pipeline.ref, pipeline.tag?, pipeline.source)
+ builds = pipeline_stage_builds(stage, pipeline)
Gitlab::Ci::Stage::Seed.new(pipeline, stage, builds) if builds.any?
end
@@ -101,6 +82,34 @@ module Ci
private
+ def pipeline_stage_builds(stage, pipeline)
+ builds = builds_for_stage_and_ref(
+ stage, pipeline.ref, pipeline.tag?, pipeline.source)
+
+ builds.select do |build|
+ job = @jobs[build.fetch(:name).to_sym]
+ has_kubernetes = pipeline.has_kubernetes_active?
+ only_kubernetes = job.dig(:only, :kubernetes)
+ except_kubernetes = job.dig(:except, :kubernetes)
+
+ [!only_kubernetes && !except_kubernetes,
+ only_kubernetes && has_kubernetes,
+ except_kubernetes && !has_kubernetes].any?
+ end
+ end
+
+ def jobs_for_ref(ref, tag = false, source = nil)
+ @jobs.select do |_, job|
+ process?(job.dig(:only, :refs), job.dig(:except, :refs), ref, tag, source)
+ end
+ end
+
+ def jobs_for_stage_and_ref(stage, ref, tag = false, source = nil)
+ jobs_for_ref(ref, tag, source).select do |_, job|
+ job[:stage] == stage
+ end
+ end
+
def initial_parsing
##
# Global config
diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb
index 3cdae1cee4f..0027e9ec8c5 100644
--- a/lib/gitlab/ci/config/entry/policy.rb
+++ b/lib/gitlab/ci/config/entry/policy.rb
@@ -7,6 +7,7 @@ module Gitlab
#
class Policy < Simplifiable
strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) }
+ strategy :ComplexPolicy, if: -> (config) { config.is_a?(Hash) }
class RefsPolicy < Entry::Node
include Entry::Validatable
@@ -14,6 +15,27 @@ module Gitlab
validations do
validates :config, array_of_strings_or_regexps: true
end
+
+ def value
+ { refs: @config }
+ end
+ end
+
+ class ComplexPolicy < Entry::Node
+ include Entry::Validatable
+ include Entry::Attributable
+
+ attributes :refs, :kubernetes
+
+ validations do
+ validates :config, presence: true
+ validates :config, allowed_keys: %i[refs kubernetes]
+
+ with_options allow_nil: true do
+ validates :refs, array_of_strings_or_regexps: true
+ validates :kubernetes, allowed_values: %w[active]
+ end
+ end
end
class UnknownStrategy < Entry::Node
diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb
index b2ca3c881e4..0159179f0a9 100644
--- a/lib/gitlab/ci/config/entry/validators.rb
+++ b/lib/gitlab/ci/config/entry/validators.rb
@@ -14,6 +14,14 @@ module Gitlab
end
end
+ class AllowedValuesValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ unless options[:in].include?(value.to_s)
+ record.errors.add(attribute, "unknown value: #{value}")
+ end
+ end
+ end
+
class ArrayOfStringsValidator < ActiveModel::EachValidator
include LegacyValidationHelpers
diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb
index d671867e7c7..90f83e0f810 100644
--- a/lib/gitlab/conflict/file_collection.rb
+++ b/lib/gitlab/conflict/file_collection.rb
@@ -18,7 +18,7 @@ module Gitlab
new(merge_request, project).tap do |file_collection|
project
.repository
- .with_repo_branch_commit(merge_request.target_project.repository, merge_request.target_branch) do
+ .with_repo_branch_commit(merge_request.target_project.repository.raw_repository, merge_request.target_branch) do
yield file_collection
end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index b6449f27034..8c9acbc9fbe 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -5,6 +5,7 @@ module Gitlab
BRANCH_REF_PREFIX = "refs/heads/".freeze
CommandError = Class.new(StandardError)
+ CommitError = Class.new(StandardError)
class << self
include Gitlab::EncodingHelper
diff --git a/lib/gitlab/git/operation_service.rb b/lib/gitlab/git/operation_service.rb
new file mode 100644
index 00000000000..9e6fca8c80c
--- /dev/null
+++ b/lib/gitlab/git/operation_service.rb
@@ -0,0 +1,168 @@
+module Gitlab
+ module Git
+ class OperationService
+ attr_reader :committer, :repository
+
+ def initialize(committer, new_repository)
+ committer = Gitlab::Git::Committer.from_user(committer) if committer.is_a?(User)
+ @committer = committer
+
+ # Refactoring aid
+ unless new_repository.is_a?(Gitlab::Git::Repository)
+ raise "expected a Gitlab::Git::Repository, got #{new_repository}"
+ end
+
+ @repository = new_repository
+ end
+
+ def add_branch(branch_name, newrev)
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+ oldrev = Gitlab::Git::BLANK_SHA
+
+ update_ref_in_hooks(ref, newrev, oldrev)
+ end
+
+ def rm_branch(branch)
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name
+ oldrev = branch.target
+ newrev = Gitlab::Git::BLANK_SHA
+
+ update_ref_in_hooks(ref, newrev, oldrev)
+ end
+
+ def add_tag(tag_name, newrev, options = {})
+ ref = Gitlab::Git::TAG_REF_PREFIX + tag_name
+ oldrev = Gitlab::Git::BLANK_SHA
+
+ with_hooks(ref, newrev, oldrev) do |service|
+ # We want to pass the OID of the tag object to the hooks. For an
+ # annotated tag we don't know that OID until after the tag object
+ # (raw_tag) is created in the repository. That is why we have to
+ # update the value after creating the tag object. Only the
+ # "post-receive" hook will receive the correct value in this case.
+ raw_tag = repository.rugged.tags.create(tag_name, newrev, options)
+ service.newrev = raw_tag.target_id
+ end
+ end
+
+ def rm_tag(tag)
+ ref = Gitlab::Git::TAG_REF_PREFIX + tag.name
+ oldrev = tag.target
+ newrev = Gitlab::Git::BLANK_SHA
+
+ update_ref_in_hooks(ref, newrev, oldrev) do
+ repository.rugged.tags.delete(tag_name)
+ end
+ end
+
+ # Whenever `start_branch_name` is passed, if `branch_name` doesn't exist,
+ # it would be created from `start_branch_name`.
+ # If `start_project` is passed, and the branch doesn't exist,
+ # it would try to find the commits from it instead of current repository.
+ def with_branch(
+ branch_name,
+ start_branch_name: nil,
+ start_repository: repository,
+ &block)
+
+ # Refactoring aid
+ unless start_repository.is_a?(Gitlab::Git::Repository)
+ raise "expected a Gitlab::Git::Repository, got #{start_repository}"
+ end
+
+ start_branch_name = nil if start_repository.empty_repo?
+
+ if start_branch_name && !start_repository.branch_exists?(start_branch_name)
+ raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.full_path}"
+ end
+
+ update_branch_with_hooks(branch_name) do
+ repository.with_repo_branch_commit(
+ start_repository,
+ start_branch_name || branch_name,
+ &block)
+ end
+ end
+
+ private
+
+ # Returns [newrev, should_run_after_create, should_run_after_create_branch]
+ def update_branch_with_hooks(branch_name)
+ update_autocrlf_option
+
+ was_empty = repository.empty?
+
+ # Make commit
+ newrev = yield
+
+ unless newrev
+ raise Gitlab::Git::CommitError.new('Failed to create commit')
+ end
+
+ branch = repository.find_branch(branch_name)
+ oldrev = find_oldrev_from_branch(newrev, branch)
+
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+ update_ref_in_hooks(ref, newrev, oldrev)
+
+ [newrev, was_empty, was_empty || Gitlab::Git.blank_ref?(oldrev)]
+ end
+
+ def find_oldrev_from_branch(newrev, branch)
+ return Gitlab::Git::BLANK_SHA unless branch
+
+ oldrev = branch.target
+
+ if oldrev == repository.rugged.merge_base(newrev, branch.target)
+ oldrev
+ else
+ raise Gitlab::Git::CommitError.new('Branch diverged')
+ end
+ end
+
+ def update_ref_in_hooks(ref, newrev, oldrev)
+ with_hooks(ref, newrev, oldrev) do
+ update_ref(ref, newrev, oldrev)
+ end
+ end
+
+ def with_hooks(ref, newrev, oldrev)
+ Gitlab::Git::HooksService.new.execute(
+ committer,
+ repository,
+ oldrev,
+ newrev,
+ ref) do |service|
+
+ yield(service)
+ end
+ end
+
+ # Gitaly note: JV: wait with migrating #update_ref until we know how to migrate its call sites.
+ def update_ref(ref, newrev, oldrev)
+ # We use 'git update-ref' because libgit2/rugged currently does not
+ # offer 'compare and swap' ref updates. Without compare-and-swap we can
+ # (and have!) accidentally reset the ref to an earlier state, clobbering
+ # commits. See also https://github.com/libgit2/libgit2/issues/1534.
+ command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]
+ _, status = Gitlab::Popen.popen(
+ command,
+ repository.path) do |stdin|
+ stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00")
+ end
+
+ unless status.zero?
+ raise Gitlab::Git::CommitError.new(
+ "Could not update branch #{Gitlab::Git.branch_name(ref)}." \
+ " Please refresh and try again.")
+ end
+ end
+
+ def update_autocrlf_option
+ if repository.autocrlf != :input
+ repository.autocrlf = :input
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 8709f82bcc4..75d4efc0bc5 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -73,6 +73,10 @@ module Gitlab
delegate :exists?, to: :gitaly_repository_client
+ def ==(other)
+ path == other.path
+ end
+
# Default branch in the repository
def root_ref
@root_ref ||= gitaly_migrate(:root_ref) do |is_enabled|
@@ -130,15 +134,19 @@ module Gitlab
# This is to work around a bug in libgit2 that causes in-memory refs to
# be stale/invalid when packed-refs is changed.
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/15392#note_14538333
- #
- # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/474
def find_branch(name, force_reload = false)
- reload_rugged if force_reload
+ gitaly_migrate(:find_branch) do |is_enabled|
+ if is_enabled
+ gitaly_ref_client.find_branch(name)
+ else
+ reload_rugged if force_reload
- rugged_ref = rugged.branches[name]
- if rugged_ref
- target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
- Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
+ rugged_ref = rugged.branches[name]
+ if rugged_ref
+ target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
+ Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
+ end
+ end
end
end
@@ -601,6 +609,49 @@ module Gitlab
# TODO: implement this method
end
+ def add_branch(branch_name, committer:, target:)
+ target_object = Ref.dereference_object(lookup(target))
+ raise InvalidRef.new("target not found: #{target}") unless target_object
+
+ OperationService.new(committer, self).add_branch(branch_name, target_object.oid)
+ find_branch(branch_name)
+ rescue Rugged::ReferenceError => ex
+ raise InvalidRef, ex
+ end
+
+ def add_tag(tag_name, committer:, target:, message: nil)
+ target_object = Ref.dereference_object(lookup(target))
+ raise InvalidRef.new("target not found: #{target}") unless target_object
+
+ committer = Committer.from_user(committer) if committer.is_a?(User)
+
+ options = nil # Use nil, not the empty hash. Rugged cares about this.
+ if message
+ options = {
+ message: message,
+ tagger: Gitlab::Git.committer_hash(email: committer.email, name: committer.name)
+ }
+ end
+
+ OperationService.new(committer, self).add_tag(tag_name, target_object.oid, options)
+
+ find_tag(tag_name)
+ rescue Rugged::ReferenceError => ex
+ raise InvalidRef, ex
+ end
+
+ def rm_branch(branch_name, committer:)
+ OperationService.new(committer, self).rm_branch(find_branch(branch_name))
+ end
+
+ def rm_tag(tag_name, committer:)
+ OperationService.new(committer, self).rm_tag(find_tag(tag_name))
+ end
+
+ def find_tag(name)
+ tags.find { |tag| tag.name == name }
+ end
+
# Delete the specified branch from the repository
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/476
@@ -740,6 +791,106 @@ module Gitlab
end
end
+ def with_repo_branch_commit(start_repository, start_branch_name)
+ raise "expected Gitlab::Git::Repository, got #{start_repository}" unless start_repository.is_a?(Gitlab::Git::Repository)
+
+ return yield nil if start_repository.empty_repo?
+
+ if start_repository == self
+ yield commit(start_branch_name)
+ else
+ sha = start_repository.commit(start_branch_name).sha
+
+ if branch_commit = commit(sha)
+ yield branch_commit
+ else
+ with_repo_tmp_commit(
+ start_repository, start_branch_name, sha) do |tmp_commit|
+ yield tmp_commit
+ end
+ end
+ end
+ end
+
+ def with_repo_tmp_commit(start_repository, start_branch_name, sha)
+ tmp_ref = fetch_ref(
+ start_repository.path,
+ "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}",
+ "refs/tmp/#{SecureRandom.hex}/head"
+ )
+
+ yield commit(sha)
+ ensure
+ delete_refs(tmp_ref) if tmp_ref
+ end
+
+ def fetch_source_branch(source_repository, source_branch, local_ref)
+ with_repo_branch_commit(source_repository, source_branch) do |commit|
+ if commit
+ write_ref(local_ref, commit.sha)
+ else
+ raise Rugged::ReferenceError, 'source repository is empty'
+ end
+ end
+ end
+
+ def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:)
+ with_repo_branch_commit(source_repository, source_branch_name) do |commit|
+ break unless commit
+
+ Gitlab::Git::Compare.new(
+ self,
+ target_branch_name,
+ commit.sha,
+ straight: straight
+ )
+ end
+ end
+
+ def write_ref(ref_path, sha)
+ rugged.references.create(ref_path, sha, force: true)
+ end
+
+ def fetch_ref(source_path, source_ref, target_ref)
+ args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
+ message, status = run_git(args)
+
+ # Make sure ref was created, and raise Rugged::ReferenceError when not
+ raise Rugged::ReferenceError, message if status != 0
+
+ target_ref
+ end
+
+ # Refactoring aid; allows us to copy code from app/models/repository.rb
+ def run_git(args)
+ circuit_breaker.perform do
+ popen([Gitlab.config.git.bin_path, *args], path)
+ end
+ end
+
+ # Refactoring aid; allows us to copy code from app/models/repository.rb
+ def commit(ref = 'HEAD')
+ Gitlab::Git::Commit.find(self, ref)
+ end
+
+ # Refactoring aid; allows us to copy code from app/models/repository.rb
+ def empty_repo?
+ !exists? || !has_visible_content?
+ end
+
+ #
+ # Git repository can contains some hidden refs like:
+ # /refs/notes/*
+ # /refs/git-as-svn/*
+ # /refs/pulls/*
+ # This refs by default not visible in project page and not cloned to client side.
+ #
+ # This method return true if repository contains some content visible in project page.
+ #
+ def has_visible_content?
+ branch_count > 0
+ end
+
def gitaly_repository
Gitlab::GitalyClient::Util.repository(@storage, @relative_path)
end
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index 8c0008c6971..a1a25cf2079 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -78,6 +78,20 @@ module Gitlab
raise ArgumentError, e.message
end
+ def find_branch(branch_name)
+ request = Gitaly::DeleteBranchRequest.new(
+ repository: @gitaly_repo,
+ name: GitalyClient.encode(branch_name)
+ )
+
+ response = GitalyClient.call(@repository.storage, :ref_service, :find_branch, request)
+ branch = response.branch
+ return unless branch
+
+ target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target_commit)
+ Gitlab::Git::Branch.new(@repository, encode!(branch.name.dup), branch.target_commit.id, target_commit)
+ end
+
private
def consume_refs_response(response)
diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb
index 45e9f9d65ae..025f826e65f 100644
--- a/lib/gitlab/gpg.rb
+++ b/lib/gitlab/gpg.rb
@@ -39,7 +39,7 @@ module Gitlab
fingerprints = CurrentKeyChain.fingerprints_from_key(key)
GPGME::Key.find(:public, fingerprints).flat_map do |raw_key|
- raw_key.uids.map { |uid| { name: uid.name, email: uid.email } }
+ raw_key.uids.map { |uid| { name: uid.name, email: uid.email.downcase } }
end
end
end
diff --git a/lib/gitlab/gpg/commit.rb b/lib/gitlab/gpg/commit.rb
index 606c7576f70..86bd9f5b125 100644
--- a/lib/gitlab/gpg/commit.rb
+++ b/lib/gitlab/gpg/commit.rb
@@ -1,17 +1,12 @@
module Gitlab
module Gpg
class Commit
- def self.for_commit(commit)
- new(commit.project, commit.sha)
- end
-
- def initialize(project, sha)
- @project = project
- @sha = sha
+ def initialize(commit)
+ @commit = commit
@signature_text, @signed_text =
begin
- Rugged::Commit.extract_signature(project.repository.rugged, sha)
+ Rugged::Commit.extract_signature(@commit.project.repository.rugged, @commit.sha)
rescue Rugged::OdbError
nil
end
@@ -26,7 +21,7 @@ module Gitlab
return @signature if @signature
- cached_signature = GpgSignature.find_by(commit_sha: @sha)
+ cached_signature = GpgSignature.find_by(commit_sha: @commit.sha)
return @signature = cached_signature if cached_signature.present?
@signature = create_cached_signature!
@@ -73,20 +68,31 @@ module Gitlab
def attributes(gpg_key)
user_infos = user_infos(gpg_key)
+ verification_status = verification_status(gpg_key)
{
- commit_sha: @sha,
- project: @project,
+ commit_sha: @commit.sha,
+ project: @commit.project,
gpg_key: gpg_key,
gpg_key_primary_keyid: gpg_key&.primary_keyid || verified_signature.fingerprint,
gpg_key_user_name: user_infos[:name],
gpg_key_user_email: user_infos[:email],
- valid_signature: gpg_signature_valid_signature_value(gpg_key)
+ verification_status: verification_status
}
end
- def gpg_signature_valid_signature_value(gpg_key)
- !!(gpg_key && gpg_key.verified? && verified_signature.valid?)
+ def verification_status(gpg_key)
+ return :unknown_key unless gpg_key
+ return :unverified_key unless gpg_key.verified?
+ return :unverified unless verified_signature.valid?
+
+ if gpg_key.verified_and_belongs_to_email?(@commit.committer_email)
+ :verified
+ elsif gpg_key.user.all_emails.include?(@commit.committer_email)
+ :same_user_different_email
+ else
+ :other_user
+ end
end
def user_infos(gpg_key)
diff --git a/lib/gitlab/gpg/invalid_gpg_signature_updater.rb b/lib/gitlab/gpg/invalid_gpg_signature_updater.rb
index a525ee7a9ee..e085eab26c9 100644
--- a/lib/gitlab/gpg/invalid_gpg_signature_updater.rb
+++ b/lib/gitlab/gpg/invalid_gpg_signature_updater.rb
@@ -8,7 +8,7 @@ module Gitlab
def run
GpgSignature
.select(:id, :commit_sha, :project_id)
- .where('gpg_key_id IS NULL OR valid_signature = ?', false)
+ .where('gpg_key_id IS NULL OR verification_status <> ?', GpgSignature.verification_statuses[:verified])
.where(gpg_key_primary_keyid: @gpg_key.primary_keyid)
.find_each { |sig| sig.gpg_commit.update_signature!(sig) }
end
diff --git a/lib/gitlab/i18n/po_linter.rb b/lib/gitlab/i18n/po_linter.rb
index 2e02787a4f4..7d3ff8c7f58 100644
--- a/lib/gitlab/i18n/po_linter.rb
+++ b/lib/gitlab/i18n/po_linter.rb
@@ -1,5 +1,3 @@
-require 'simple_po_parser'
-
module Gitlab
module I18n
class PoLinter
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 78795dd3d92..ec73846d844 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -116,6 +116,7 @@ excluded_attributes:
statuses:
- :trace
- :token
+ - :when
push_event_payload:
- :event_id
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index cbc8d170936..3bc095a99a9 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -9,6 +9,8 @@ module Gitlab
@user = user
@shared = shared
@project = project
+ @project_id = project.id
+ @saved = true
end
def restore
@@ -22,8 +24,10 @@ module Gitlab
@project_members = @tree_hash.delete('project_members')
- ActiveRecord::Base.no_touching do
- create_relations
+ ActiveRecord::Base.uncached do
+ ActiveRecord::Base.no_touching do
+ create_relations
+ end
end
rescue => e
@shared.error(e)
@@ -48,21 +52,24 @@ module Gitlab
# the configuration yaml file too.
# Finally, it updates each attribute in the newly imported project.
def create_relations
- saved = []
default_relation_list.each do |relation|
- next unless relation.is_a?(Hash) || @tree_hash[relation.to_s].present?
+ if relation.is_a?(Hash)
+ create_sub_relations(relation, @tree_hash)
+ elsif @tree_hash[relation.to_s].present?
+ save_relation_hash(@tree_hash[relation.to_s], relation)
+ end
+ end
- create_sub_relations(relation, @tree_hash) if relation.is_a?(Hash)
+ @saved
+ end
- relation_key = relation.is_a?(Hash) ? relation.keys.first : relation
- relation_hash_list = @tree_hash[relation_key.to_s]
+ def save_relation_hash(relation_hash_batch, relation_key)
+ relation_hash = create_relation(relation_key, relation_hash_batch)
- next unless relation_hash_list
+ @saved = false unless restored_project.append_or_update_attribute(relation_key, relation_hash)
- relation_hash = create_relation(relation_key, relation_hash_list)
- saved << restored_project.append_or_update_attribute(relation_key, relation_hash)
- end
- saved.all?
+ # Restore the project again, extra query that skips holding the AR objects in memory
+ @restored_project = Project.find(@project_id)
end
def default_relation_list
@@ -93,20 +100,42 @@ module Gitlab
# issue, finds any subrelations such as notes, creates them and assign them back to the hash
#
# Recursively calls this method if the sub-relation is a hash containing more sub-relations
- def create_sub_relations(relation, tree_hash)
+ def create_sub_relations(relation, tree_hash, save: true)
relation_key = relation.keys.first.to_s
return if tree_hash[relation_key].blank?
- [tree_hash[relation_key]].flatten.each do |relation_item|
- relation.values.flatten.each do |sub_relation|
- # We just use author to get the user ID, do not attempt to create an instance.
- next if sub_relation == :author
+ tree_array = [tree_hash[relation_key]].flatten
+
+ # Avoid keeping a possible heavy object in memory once we are done with it
+ while relation_item = tree_array.shift
+ # The transaction at this level is less speedy than one single transaction
+ # But we can't have it in the upper level or GC won't get rid of the AR objects
+ # after we save the batch.
+ Project.transaction do
+ process_sub_relation(relation, relation_item)
+
+ # For every subrelation that hangs from Project, save the associated records alltogether
+ # This effectively batches all records per subrelation item, only keeping those in memory
+ # We have to keep in mind that more batch granularity << Memory, but >> Slowness
+ if save
+ save_relation_hash([relation_item], relation_key)
+ tree_hash[relation_key].delete(relation_item)
+ end
+ end
+ end
+
+ tree_hash.delete(relation_key) if save
+ end
+
+ def process_sub_relation(relation, relation_item)
+ relation.values.flatten.each do |sub_relation|
+ # We just use author to get the user ID, do not attempt to create an instance.
+ next if sub_relation == :author
- create_sub_relations(sub_relation, relation_item) if sub_relation.is_a?(Hash)
+ create_sub_relations(sub_relation, relation_item, save: false) if sub_relation.is_a?(Hash)
- relation_hash, sub_relation = assign_relation_hash(relation_item, sub_relation)
- relation_item[sub_relation.to_s] = create_relation(sub_relation, relation_hash) unless relation_hash.blank?
- end
+ relation_hash, sub_relation = assign_relation_hash(relation_item, sub_relation)
+ relation_item[sub_relation.to_s] = create_relation(sub_relation, relation_hash) unless relation_hash.blank?
end
end
@@ -121,14 +150,12 @@ module Gitlab
end
def create_relation(relation, relation_hash_list)
- relation_type = relation.to_sym
-
relation_array = [relation_hash_list].flatten.map do |relation_hash|
- Gitlab::ImportExport::RelationFactory.create(relation_sym: relation_type,
- relation_hash: parsed_relation_hash(relation_hash, relation_type),
+ Gitlab::ImportExport::RelationFactory.create(relation_sym: relation.to_sym,
+ relation_hash: parsed_relation_hash(relation_hash, relation.to_sym),
members_mapper: members_mapper,
user: @user,
- project: restored_project)
+ project: @restored_project)
end.compact
relation_hash_list.is_a?(Array) ? relation_array : relation_array.first
diff --git a/lib/gitlab/import_export/shared.rb b/lib/gitlab/import_export/shared.rb
index 5d6de8bc475..9fd0b709ef2 100644
--- a/lib/gitlab/import_export/shared.rb
+++ b/lib/gitlab/import_export/shared.rb
@@ -16,7 +16,7 @@ module Gitlab
error_out(error.message, caller[0].dup)
@errors << error.message
# Debug:
- Rails.logger.error(error.backtrace)
+ Rails.logger.error(error.backtrace.join("\n"))
end
private
diff --git a/lib/gitlab/issuables_count_for_state.rb b/lib/gitlab/issuables_count_for_state.rb
new file mode 100644
index 00000000000..505810964bc
--- /dev/null
+++ b/lib/gitlab/issuables_count_for_state.rb
@@ -0,0 +1,50 @@
+module Gitlab
+ # Class for counting and caching the number of issuables per state.
+ class IssuablesCountForState
+ # The name of the RequestStore cache key.
+ CACHE_KEY = :issuables_count_for_state
+
+ # The state values that can be safely casted to a Symbol.
+ STATES = %w[opened closed merged all].freeze
+
+ # finder - The finder class to use for retrieving the issuables.
+ def initialize(finder)
+ @finder = finder
+ @cache =
+ if RequestStore.active?
+ RequestStore[CACHE_KEY] ||= initialize_cache
+ else
+ initialize_cache
+ end
+ end
+
+ def for_state_or_opened(state = nil)
+ self[state || :opened]
+ end
+
+ # Returns the count for the given state.
+ #
+ # state - The name of the state as either a String or a Symbol.
+ #
+ # Returns an Integer.
+ def [](state)
+ state = state.to_sym if cast_state_to_symbol?(state)
+
+ cache_for_finder[state] || 0
+ end
+
+ private
+
+ def cache_for_finder
+ @cache[@finder]
+ end
+
+ def cast_state_to_symbol?(state)
+ state.is_a?(String) && STATES.include?(state)
+ end
+
+ def initialize_cache
+ Hash.new { |hash, finder| hash[finder] = finder.count_by_state }
+ end
+ end
+end
diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb
index b42bc67ccfc..7c2d1d8f887 100644
--- a/lib/gitlab/sql/pattern.rb
+++ b/lib/gitlab/sql/pattern.rb
@@ -4,6 +4,7 @@ module Gitlab
extend ActiveSupport::Concern
MIN_CHARS_FOR_PARTIAL_MATCHING = 3
+ REGEX_QUOTED_WORD = /(?<=^| )"[^"]+"(?= |$)/
class_methods do
def to_pattern(query)
@@ -17,6 +18,28 @@ module Gitlab
def partial_matching?(query)
query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING
end
+
+ def to_fuzzy_arel(column, query)
+ words = select_fuzzy_words(query)
+
+ matches = words.map { |word| arel_table[column].matches(to_pattern(word)) }
+
+ matches.reduce { |result, match| result.and(match) }
+ end
+
+ def select_fuzzy_words(query)
+ quoted_words = query.scan(REGEX_QUOTED_WORD)
+
+ query = quoted_words.reduce(query) { |q, quoted_word| q.sub(quoted_word, '') }
+
+ words = query.split(/\s+/)
+
+ quoted_words.map! { |quoted_word| quoted_word[1..-2] }
+
+ words.concat(quoted_words)
+
+ words.select { |word| partial_matching?(word) }
+ end
end
end
end
diff --git a/lib/system_check/app/git_user_default_ssh_config_check.rb b/lib/system_check/app/git_user_default_ssh_config_check.rb
new file mode 100644
index 00000000000..7b486d78cf0
--- /dev/null
+++ b/lib/system_check/app/git_user_default_ssh_config_check.rb
@@ -0,0 +1,69 @@
+module SystemCheck
+ module App
+ class GitUserDefaultSSHConfigCheck < SystemCheck::BaseCheck
+ # These files are allowed in the .ssh directory. The `config` file is not
+ # whitelisted as it may change the SSH client's behaviour dramatically.
+ WHITELIST = %w[
+ authorized_keys
+ authorized_keys2
+ known_hosts
+ ].freeze
+
+ set_name 'Git user has default SSH configuration?'
+ set_skip_reason 'skipped (git user is not present or configured)'
+
+ def skip?
+ !home_dir || !File.directory?(home_dir)
+ end
+
+ def check?
+ forbidden_files.empty?
+ end
+
+ def show_error
+ backup_dir = "~/gitlab-check-backup-#{Time.now.to_i}"
+
+ instructions = forbidden_files.map do |filename|
+ "sudo mv #{Shellwords.escape(filename)} #{backup_dir}"
+ end
+
+ try_fixing_it("mkdir #{backup_dir}", *instructions)
+ for_more_information('doc/ssh/README.md in section "SSH on the GitLab server"')
+ fix_and_rerun
+ end
+
+ private
+
+ def git_user
+ Gitlab.config.gitlab.user
+ end
+
+ def home_dir
+ return @home_dir if defined?(@home_dir)
+
+ @home_dir =
+ begin
+ File.expand_path("~#{git_user}")
+ rescue ArgumentError
+ nil
+ end
+ end
+
+ def ssh_dir
+ return nil unless home_dir
+
+ File.join(home_dir, '.ssh')
+ end
+
+ def forbidden_files
+ @forbidden_files ||=
+ begin
+ present = Dir[File.join(ssh_dir, '*')]
+ whitelisted = WHITELIST.map { |basename| File.join(ssh_dir, basename) }
+
+ present - whitelisted
+ end
+ end
+ end
+ end
+end
diff --git a/lib/system_check/app/init_script_up_to_date_check.rb b/lib/system_check/app/init_script_up_to_date_check.rb
index 015c7ed1731..53a47eb0f42 100644
--- a/lib/system_check/app/init_script_up_to_date_check.rb
+++ b/lib/system_check/app/init_script_up_to_date_check.rb
@@ -7,26 +7,22 @@ module SystemCheck
set_skip_reason 'skipped (omnibus-gitlab has no init script)'
def skip?
- omnibus_gitlab?
- end
+ return true if omnibus_gitlab?
- def multi_check
- recipe_path = Rails.root.join('lib/support/init.d/', 'gitlab')
+ unless init_file_exists?
+ self.skip_reason = "can't check because of previous errors"
- unless File.exist?(SCRIPT_PATH)
- $stdout.puts "can't check because of previous errors".color(:magenta)
- return
+ true
end
+ end
+
+ def check?
+ recipe_path = Rails.root.join('lib/support/init.d/', 'gitlab')
recipe_content = File.read(recipe_path)
script_content = File.read(SCRIPT_PATH)
- if recipe_content == script_content
- $stdout.puts 'yes'.color(:green)
- else
- $stdout.puts 'no'.color(:red)
- show_error
- end
+ recipe_content == script_content
end
def show_error
@@ -38,6 +34,12 @@ module SystemCheck
)
fix_and_rerun
end
+
+ private
+
+ def init_file_exists?
+ File.exist?(SCRIPT_PATH)
+ end
end
end
end
diff --git a/lib/system_check/base_check.rb b/lib/system_check/base_check.rb
index 7f9e2ffffc2..0f5742dd67f 100644
--- a/lib/system_check/base_check.rb
+++ b/lib/system_check/base_check.rb
@@ -62,6 +62,25 @@ module SystemCheck
call_or_return(@skip_reason) || 'skipped'
end
+ # Define a reason why we skipped the SystemCheck (during runtime)
+ #
+ # This is used when you need dynamic evaluation like when you have
+ # multiple reasons why a check can fail
+ #
+ # @param [String] reason to be displayed
+ def skip_reason=(reason)
+ @skip_reason = reason
+ end
+
+ # Skip reason defined during runtime
+ #
+ # This value have precedence over the one defined in the subclass
+ #
+ # @return [String] the reason
+ def skip_reason
+ @skip_reason
+ end
+
# Does the check support automatically repair routine?
#
# @return [Boolean] whether check implemented `#repair!` method or not
diff --git a/lib/system_check/incoming_email/foreman_configured_check.rb b/lib/system_check/incoming_email/foreman_configured_check.rb
new file mode 100644
index 00000000000..1db7bf2b782
--- /dev/null
+++ b/lib/system_check/incoming_email/foreman_configured_check.rb
@@ -0,0 +1,23 @@
+module SystemCheck
+ module IncomingEmail
+ class ForemanConfiguredCheck < SystemCheck::BaseCheck
+ set_name 'Foreman configured correctly?'
+
+ def check?
+ path = Rails.root.join('Procfile')
+
+ File.exist?(path) && File.read(path) =~ /^mail_room:/
+ end
+
+ def show_error
+ try_fixing_it(
+ 'Enable mail_room in your Procfile.'
+ )
+ for_more_information(
+ 'doc/administration/reply_by_email.md'
+ )
+ fix_and_rerun
+ end
+ end
+ end
+end
diff --git a/lib/system_check/incoming_email/imap_authentication_check.rb b/lib/system_check/incoming_email/imap_authentication_check.rb
new file mode 100644
index 00000000000..dee108d987b
--- /dev/null
+++ b/lib/system_check/incoming_email/imap_authentication_check.rb
@@ -0,0 +1,45 @@
+module SystemCheck
+ module IncomingEmail
+ class ImapAuthenticationCheck < SystemCheck::BaseCheck
+ set_name 'IMAP server credentials are correct?'
+
+ def check?
+ if mailbox_config
+ begin
+ imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl])
+ imap.starttls if config[:start_tls]
+ imap.login(config[:email], config[:password])
+ connected = true
+ rescue
+ connected = false
+ end
+ end
+
+ connected
+ end
+
+ def show_error
+ try_fixing_it(
+ 'Check that the information in config/gitlab.yml is correct'
+ )
+ for_more_information(
+ 'doc/administration/reply_by_email.md'
+ )
+ fix_and_rerun
+ end
+
+ private
+
+ def mailbox_config
+ return @config if @config
+
+ config_path = Rails.root.join('config', 'mail_room.yml').to_s
+ erb = ERB.new(File.read(config_path))
+ erb.filename = config_path
+ config_file = YAML.load(erb.result)
+
+ @config = config_file[:mailboxes]&.first
+ end
+ end
+ end
+end
diff --git a/lib/system_check/incoming_email/initd_configured_check.rb b/lib/system_check/incoming_email/initd_configured_check.rb
new file mode 100644
index 00000000000..ea23b8ef49c
--- /dev/null
+++ b/lib/system_check/incoming_email/initd_configured_check.rb
@@ -0,0 +1,32 @@
+module SystemCheck
+ module IncomingEmail
+ class InitdConfiguredCheck < SystemCheck::BaseCheck
+ set_name 'Init.d configured correctly?'
+
+ def skip?
+ omnibus_gitlab?
+ end
+
+ def check?
+ mail_room_configured?
+ end
+
+ def show_error
+ try_fixing_it(
+ 'Enable mail_room in the init.d configuration.'
+ )
+ for_more_information(
+ 'doc/administration/reply_by_email.md'
+ )
+ fix_and_rerun
+ end
+
+ private
+
+ def mail_room_configured?
+ path = '/etc/default/gitlab'
+ File.exist?(path) && File.read(path).include?('mail_room_enabled=true')
+ end
+ end
+ end
+end
diff --git a/lib/system_check/incoming_email/mail_room_running_check.rb b/lib/system_check/incoming_email/mail_room_running_check.rb
new file mode 100644
index 00000000000..c1807501829
--- /dev/null
+++ b/lib/system_check/incoming_email/mail_room_running_check.rb
@@ -0,0 +1,43 @@
+module SystemCheck
+ module IncomingEmail
+ class MailRoomRunningCheck < SystemCheck::BaseCheck
+ set_name 'MailRoom running?'
+
+ def skip?
+ return true if omnibus_gitlab?
+
+ unless mail_room_configured?
+ self.skip_reason = "can't check because of previous errors"
+ true
+ end
+ end
+
+ def check?
+ mail_room_running?
+ end
+
+ def show_error
+ try_fixing_it(
+ sudo_gitlab('RAILS_ENV=production bin/mail_room start')
+ )
+ for_more_information(
+ see_installation_guide_section('Install Init Script'),
+ 'see log/mail_room.log for possible errors'
+ )
+ fix_and_rerun
+ end
+
+ private
+
+ def mail_room_configured?
+ path = '/etc/default/gitlab'
+ File.exist?(path) && File.read(path).include?('mail_room_enabled=true')
+ end
+
+ def mail_room_running?
+ ps_ux, _ = Gitlab::Popen.popen(%w(ps uxww))
+ ps_ux.include?("mail_room")
+ end
+ end
+ end
+end
diff --git a/lib/system_check/simple_executor.rb b/lib/system_check/simple_executor.rb
index 6604b1078cf..00221f77cf4 100644
--- a/lib/system_check/simple_executor.rb
+++ b/lib/system_check/simple_executor.rb
@@ -23,7 +23,7 @@ module SystemCheck
#
# @param [BaseCheck] check class
def <<(check)
- raise ArgumentError unless check < BaseCheck
+ raise ArgumentError unless check.is_a?(Class) && check < BaseCheck
@checks << check
end
@@ -48,7 +48,7 @@ module SystemCheck
# When implements skip method, we run it first, and if true, skip the check
if check.can_skip? && check.skip?
- $stdout.puts check_klass.skip_reason.color(:magenta)
+ $stdout.puts check.skip_reason.try(:color, :magenta) || check_klass.skip_reason.color(:magenta)
return
end
diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake
index e1491f29b5e..35ba729c156 100644
--- a/lib/tasks/gettext.rake
+++ b/lib/tasks/gettext.rake
@@ -22,6 +22,8 @@ namespace :gettext do
desc 'Lint all po files in `locale/'
task lint: :environment do
+ require 'simple_po_parser'
+
FastGettext.silence_errors
files = Dir.glob(Rails.root.join('locale/*/gitlab.po'))
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 1bd36bbe20a..654f638c454 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -33,6 +33,7 @@ namespace :gitlab do
SystemCheck::App::RedisVersionCheck,
SystemCheck::App::RubyVersionCheck,
SystemCheck::App::GitVersionCheck,
+ SystemCheck::App::GitUserDefaultSSHConfigCheck,
SystemCheck::App::ActiveUsersCheck
]
@@ -308,133 +309,24 @@ namespace :gitlab do
desc "GitLab | Check the configuration of Reply by email"
task check: :environment do
warn_user_is_not_gitlab
- start_checking "Reply by email"
if Gitlab.config.incoming_email.enabled
- check_imap_authentication
+ checks = [
+ SystemCheck::IncomingEmail::ImapAuthenticationCheck
+ ]
if Rails.env.production?
- check_initd_configured_correctly
- check_mail_room_running
+ checks << SystemCheck::IncomingEmail::InitdConfiguredCheck
+ checks << SystemCheck::IncomingEmail::MailRoomRunningCheck
else
- check_foreman_configured_correctly
+ checks << SystemCheck::IncomingEmail::ForemanConfiguredCheck
end
- else
- puts 'Reply by email is disabled in config/gitlab.yml'
- end
-
- finished_checking "Reply by email"
- end
-
- # Checks
- ########################
-
- def check_initd_configured_correctly
- return if omnibus_gitlab?
-
- print "Init.d configured correctly? ... "
-
- path = "/etc/default/gitlab"
-
- if File.exist?(path) && File.read(path).include?("mail_room_enabled=true")
- puts "yes".color(:green)
- else
- puts "no".color(:red)
- try_fixing_it(
- "Enable mail_room in the init.d configuration."
- )
- for_more_information(
- "doc/administration/reply_by_email.md"
- )
- fix_and_rerun
- end
- end
-
- def check_foreman_configured_correctly
- print "Foreman configured correctly? ... "
- path = Rails.root.join("Procfile")
-
- if File.exist?(path) && File.read(path) =~ /^mail_room:/
- puts "yes".color(:green)
+ SystemCheck.run('Reply by email', checks)
else
- puts "no".color(:red)
- try_fixing_it(
- "Enable mail_room in your Procfile."
- )
- for_more_information(
- "doc/administration/reply_by_email.md"
- )
- fix_and_rerun
- end
- end
-
- def check_mail_room_running
- return if omnibus_gitlab?
-
- print "MailRoom running? ... "
-
- path = "/etc/default/gitlab"
-
- unless File.exist?(path) && File.read(path).include?("mail_room_enabled=true")
- puts "can't check because of previous errors".color(:magenta)
- return
- end
-
- if mail_room_running?
- puts "yes".color(:green)
- else
- puts "no".color(:red)
- try_fixing_it(
- sudo_gitlab("RAILS_ENV=production bin/mail_room start")
- )
- for_more_information(
- see_installation_guide_section("Install Init Script"),
- "see log/mail_room.log for possible errors"
- )
- fix_and_rerun
- end
- end
-
- def check_imap_authentication
- print "IMAP server credentials are correct? ... "
-
- config_path = Rails.root.join('config', 'mail_room.yml').to_s
- erb = ERB.new(File.read(config_path))
- erb.filename = config_path
- config_file = YAML.load(erb.result)
-
- config = config_file[:mailboxes].first
-
- if config
- begin
- imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl])
- imap.starttls if config[:start_tls]
- imap.login(config[:email], config[:password])
- connected = true
- rescue
- connected = false
- end
- end
-
- if connected
- puts "yes".color(:green)
- else
- puts "no".color(:red)
- try_fixing_it(
- "Check that the information in config/gitlab.yml is correct"
- )
- for_more_information(
- "doc/administration/reply_by_email.md"
- )
- fix_and_rerun
+ puts 'Reply by email is disabled in config/gitlab.yml'
end
end
-
- def mail_room_running?
- ps_ux, _ = Gitlab::Popen.popen(%w(ps uxww))
- ps_ux.include?("mail_room")
- end
end
namespace :ldap do