summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/after_commit_queue.rb26
-rw-r--r--lib/api/api_guard.rb107
-rw-r--r--lib/api/branches.rb4
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/entities.rb97
-rw-r--r--lib/api/groups.rb14
-rw-r--r--lib/api/helpers.rb24
-rw-r--r--lib/api/helpers/custom_validators.rb1
-rw-r--r--lib/api/helpers/internal_helpers.rb8
-rw-r--r--lib/api/helpers/pagination.rb10
-rw-r--r--lib/api/helpers/runner.rb1
-rw-r--r--lib/api/issues.rb4
-rw-r--r--lib/api/merge_requests.rb6
-rw-r--r--lib/api/namespaces.rb10
-rw-r--r--lib/api/notes.rb9
-rw-r--r--lib/api/projects.rb8
-rw-r--r--lib/api/projects_relation_builder.rb34
-rw-r--r--lib/api/protected_branches.rb4
-rw-r--r--lib/api/runner.rb10
-rw-r--r--lib/api/runners.rb27
-rw-r--r--lib/api/settings.rb16
-rw-r--r--lib/api/snippets.rb1
-rw-r--r--lib/api/users.rb5
-rw-r--r--lib/api/v3/commits.rb2
-rw-r--r--lib/api/v3/entities.rb4
-rw-r--r--lib/api/v3/runners.rb1
-rw-r--r--lib/api/v3/settings.rb8
-rw-r--r--lib/api/v3/snippets.rb2
-rw-r--r--lib/backup/artifacts.rb2
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb3
-rw-r--r--lib/banzai/filter/mermaid_filter.rb20
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb35
-rw-r--r--lib/banzai/object_renderer.rb1
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/banzai/querying.rb2
-rw-r--r--lib/banzai/reference_parser/user_parser.rb1
-rw-r--r--lib/banzai/renderer.rb2
-rw-r--r--lib/declarative_policy.rb2
-rw-r--r--lib/declarative_policy/base.rb2
-rw-r--r--lib/declarative_policy/cache.rb2
-rw-r--r--lib/declarative_policy/rule.rb5
-rw-r--r--lib/declarative_policy/runner.rb1
-rw-r--r--lib/file_size_validator.rb1
-rw-r--r--lib/gitlab/access.rb6
-rw-r--r--lib/gitlab/auth.rb39
-rw-r--r--lib/gitlab/auth/request_authenticator.rb25
-rw-r--r--lib/gitlab/auth/user_auth_finders.rb109
-rw-r--r--lib/gitlab/background_migration/.rubocop.yml52
-rw-r--r--lib/gitlab/background_migration/create_fork_network_memberships_range.rb4
-rw-r--r--lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys.rb4
-rw-r--r--lib/gitlab/background_migration/delete_conflicting_redirect_routes_range.rb4
-rw-r--r--lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb6
-rw-r--r--lib/gitlab/background_migration/migrate_build_stage_id_reference.rb3
-rw-r--r--lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb4
-rw-r--r--lib/gitlab/background_migration/migrate_stage_status.rb4
-rw-r--r--lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb4
-rw-r--r--lib/gitlab/background_migration/move_personal_snippet_files.rb4
-rw-r--r--lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb7
-rw-r--r--lib/gitlab/background_migration/populate_fork_networks_range.rb77
-rw-r--r--lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id.rb33
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb4
-rw-r--r--lib/gitlab/changes_list.rb1
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata.rb1
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata/entry.rb3
-rw-r--r--lib/gitlab/ci/build/image.rb1
-rw-r--r--lib/gitlab/ci/config/entry/image.rb1
-rw-r--r--lib/gitlab/ci/config/entry/validators.rb1
-rw-r--r--lib/gitlab/ci/pipeline/chain/build.rb58
-rw-r--r--lib/gitlab/ci/pipeline/chain/sequence.rb13
-rw-r--r--lib/gitlab/cycle_analytics/plan_event_fetcher.rb8
-rw-r--r--lib/gitlab/daemon.rb1
-rw-r--r--lib/gitlab/database.rb4
-rw-r--r--lib/gitlab/database/migration_helpers.rb13
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb12
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb8
-rw-r--r--lib/gitlab/diff/file.rb18
-rw-r--r--lib/gitlab/diff/file_collection/base.rb5
-rw-r--r--lib/gitlab/diff/inline_diff.rb1
-rw-r--r--lib/gitlab/diff/parser.rb1
-rw-r--r--lib/gitlab/diff/position.rb1
-rw-r--r--lib/gitlab/ee_compat_check.rb63
-rw-r--r--lib/gitlab/email/handler.rb2
-rw-r--r--lib/gitlab/email/handler/create_merge_request_handler.rb67
-rw-r--r--lib/gitlab/email/handler/unsubscribe_handler.rb1
-rw-r--r--lib/gitlab/email/receiver.rb6
-rw-r--r--lib/gitlab/encoding_helper.rb4
-rw-r--r--lib/gitlab/fogbugz_import/client.rb1
-rw-r--r--lib/gitlab/fogbugz_import/importer.rb2
-rw-r--r--lib/gitlab/git/blob.rb4
-rw-r--r--lib/gitlab/git/commit.rb38
-rw-r--r--lib/gitlab/git/conflict/file.rb2
-rw-r--r--lib/gitlab/git/conflict/resolver.rb2
-rw-r--r--lib/gitlab/git/repository.rb323
-rw-r--r--lib/gitlab/git/repository_mirroring.rb57
-rw-r--r--lib/gitlab/git/storage.rb1
-rw-r--r--lib/gitlab/git/storage/circuit_breaker.rb14
-rw-r--r--lib/gitlab/git/storage/health.rb25
-rw-r--r--lib/gitlab/git/user.rb9
-rw-r--r--lib/gitlab/git/wiki.rb56
-rw-r--r--lib/gitlab/gitaly_client.rb98
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb50
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb30
-rw-r--r--lib/gitlab/gitaly_client/ref_service.rb13
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb28
-rw-r--r--lib/gitlab/gitaly_client/wiki_service.rb1
-rw-r--r--lib/gitlab/github_import.rb4
-rw-r--r--lib/gitlab/github_import/importer/repository_importer.rb17
-rw-r--r--lib/gitlab/gitlab_import/client.rb1
-rw-r--r--lib/gitlab/import_export.rb2
-rw-r--r--lib/gitlab/import_export/import_export.yml2
-rw-r--r--lib/gitlab/import_export/merge_request_parser.rb2
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb2
-rw-r--r--lib/gitlab/import_export/relation_factory.rb8
-rw-r--r--lib/gitlab/import_export/uploads_saver.rb3
-rw-r--r--lib/gitlab/kubernetes/helm.rb2
-rw-r--r--lib/gitlab/kubernetes/namespace.rb1
-rw-r--r--lib/gitlab/ldap/authentication.rb1
-rw-r--r--lib/gitlab/ldap/user.rb5
-rw-r--r--lib/gitlab/legacy_github_import/importer.rb5
-rw-r--r--lib/gitlab/metrics/method_call.rb33
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb1
-rw-r--r--lib/gitlab/metrics/subscribers/active_record.rb1
-rw-r--r--lib/gitlab/middleware/go.rb6
-rw-r--r--lib/gitlab/middleware/read_only.rb8
-rw-r--r--lib/gitlab/multi_collection_paginator.rb4
-rw-r--r--lib/gitlab/o_auth/user.rb2
-rw-r--r--lib/gitlab/optimistic_locking.rb1
-rw-r--r--lib/gitlab/path_regex.rb1
-rw-r--r--lib/gitlab/project_search_results.rb7
-rw-r--r--lib/gitlab/prometheus/queries/query_additional_metrics.rb2
-rw-r--r--lib/gitlab/routing.rb6
-rw-r--r--lib/gitlab/saml/user.rb1
-rw-r--r--lib/gitlab/search_results.rb2
-rw-r--r--lib/gitlab/seeder.rb3
-rw-r--r--lib/gitlab/shell.rb27
-rw-r--r--lib/gitlab/shell_adapter.rb2
-rw-r--r--lib/gitlab/sidekiq_config.rb50
-rw-r--r--lib/gitlab/sql/pattern.rb25
-rw-r--r--lib/gitlab/string_range_marker.rb1
-rw-r--r--lib/gitlab/template/finders/repo_template_finder.rb1
-rw-r--r--lib/gitlab/url_sanitizer.rb1
-rw-r--r--lib/gitlab/usage_data.rb2
-rw-r--r--lib/gitlab/visibility_level.rb1
-rw-r--r--lib/gitlab/workhorse.rb3
-rw-r--r--lib/google_api/cloud_platform/client.rb12
-rw-r--r--lib/haml_lint/inline_javascript.rb1
-rw-r--r--lib/milestone_array.rb40
-rw-r--r--lib/rouge/lexers/math.rb9
-rw-r--r--lib/rouge/lexers/plantuml.rb9
-rw-r--r--lib/system_check/simple_executor.rb1
-rw-r--r--lib/tasks/brakeman.rake2
-rw-r--r--lib/tasks/gitlab/cleanup.rake12
-rw-r--r--lib/tasks/gitlab/gitaly.rake17
-rw-r--r--lib/tasks/gitlab/sidekiq.rake47
-rw-r--r--lib/tasks/gitlab/storage.rake85
155 files changed, 1848 insertions, 595 deletions
diff --git a/lib/after_commit_queue.rb b/lib/after_commit_queue.rb
index 4750a2c373a..db63c5038ae 100644
--- a/lib/after_commit_queue.rb
+++ b/lib/after_commit_queue.rb
@@ -6,12 +6,34 @@ module AfterCommitQueue
after_rollback :_clear_after_commit_queue
end
- def run_after_commit(method = nil, &block)
- _after_commit_queue << proc { self.send(method) } if method # rubocop:disable GitlabSecurity/PublicSend
+ def run_after_commit(&block)
_after_commit_queue << block if block
+
+ true
+ end
+
+ def run_after_commit_or_now(&block)
+ if AfterCommitQueue.inside_transaction?
+ run_after_commit(&block)
+ else
+ instance_eval(&block)
+ end
+
true
end
+ def self.open_transactions_baseline
+ if ::Rails.env.test?
+ return DatabaseCleaner.connections.count { |conn| conn.strategy.is_a?(DatabaseCleaner::ActiveRecord::Transaction) }
+ end
+
+ 0
+ end
+
+ def self.inside_transaction?
+ ActiveRecord::Base.connection.open_transactions > open_transactions_baseline
+ end
+
protected
def _run_after_commit_queue
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index c1c0d344917..9aeebc34525 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -6,9 +6,6 @@ module API
module APIGuard
extend ActiveSupport::Concern
- PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN".freeze
- PRIVATE_TOKEN_PARAM = :private_token
-
included do |base|
# OAuth2 Resource Server Authentication
use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request|
@@ -42,7 +39,7 @@ module API
# Helper Methods for Grape Endpoint
module HelperMethods
- include Gitlab::Utils::StrongMemoize
+ include Gitlab::Auth::UserAuthFinders
def find_current_user!
user = find_user_from_access_token || find_user_from_warden
@@ -53,76 +50,8 @@ module API
user
end
- def access_token
- strong_memoize(:access_token) do
- find_oauth_access_token || find_personal_access_token
- end
- end
-
- def validate_access_token!(scopes: [])
- return unless access_token
-
- case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes)
- when AccessTokenValidationService::INSUFFICIENT_SCOPE
- raise InsufficientScopeError.new(scopes)
- when AccessTokenValidationService::EXPIRED
- raise ExpiredError
- when AccessTokenValidationService::REVOKED
- raise RevokedError
- end
- end
-
private
- def find_user_from_access_token
- return unless access_token
-
- validate_access_token!
-
- access_token.user || raise(UnauthorizedError)
- end
-
- # Check the Rails session for valid authentication details
- def find_user_from_warden
- warden.try(:authenticate) if verified_request?
- end
-
- def warden
- env['warden']
- end
-
- # Check if the request is GET/HEAD, or if CSRF token is valid.
- def verified_request?
- Gitlab::RequestForgeryProtection.verified?(env)
- end
-
- def find_oauth_access_token
- token = Doorkeeper::OAuth::Token.from_request(doorkeeper_request, *Doorkeeper.configuration.access_token_methods)
- return unless token
-
- # Expiration, revocation and scopes are verified in `find_user_by_access_token`
- access_token = OauthAccessToken.by_token(token)
- raise UnauthorizedError unless access_token
-
- access_token.revoke_previous_refresh_token!
- access_token
- end
-
- def find_personal_access_token
- token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
- return unless token.present?
-
- # Expiration, revocation and scopes are verified in `find_user_by_access_token`
- access_token = PersonalAccessToken.find_by(token: token)
- raise UnauthorizedError unless access_token
-
- access_token
- end
-
- def doorkeeper_request
- @doorkeeper_request ||= ActionDispatch::Request.new(env)
- end
-
# An array of scopes that were registered (using `allow_access_with_scope`)
# for the current endpoint class. It also returns scopes registered on
# `API::API`, since these are meant to apply to all API routes.
@@ -145,8 +74,11 @@ module API
private
def install_error_responders(base)
- error_classes = [MissingTokenError, TokenNotFoundError,
- ExpiredError, RevokedError, InsufficientScopeError]
+ error_classes = [Gitlab::Auth::MissingTokenError,
+ Gitlab::Auth::TokenNotFoundError,
+ Gitlab::Auth::ExpiredError,
+ Gitlab::Auth::RevokedError,
+ Gitlab::Auth::InsufficientScopeError]
base.__send__(:rescue_from, *error_classes, oauth2_bearer_token_error_handler) # rubocop:disable GitlabSecurity/PublicSend
end
@@ -155,25 +87,25 @@ module API
proc do |e|
response =
case e
- when MissingTokenError
+ when Gitlab::Auth::MissingTokenError
Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new
- when TokenNotFoundError
+ when Gitlab::Auth::TokenNotFoundError
Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
:invalid_token,
"Bad Access Token.")
- when ExpiredError
+ when Gitlab::Auth::ExpiredError
Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
:invalid_token,
"Token is expired. You can either do re-authorization or token refresh.")
- when RevokedError
+ when Gitlab::Auth::RevokedError
Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
:invalid_token,
"Token was revoked. You have to re-authorize from the user.")
- when InsufficientScopeError
+ when Gitlab::Auth::InsufficientScopeError
# FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2)
# does not include WWW-Authenticate header, which breaks the standard.
Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(
@@ -186,22 +118,5 @@ module API
end
end
end
-
- #
- # Exceptions
- #
-
- MissingTokenError = Class.new(StandardError)
- TokenNotFoundError = Class.new(StandardError)
- ExpiredError = Class.new(StandardError)
- RevokedError = Class.new(StandardError)
- UnauthorizedError = Class.new(StandardError)
-
- class InsufficientScopeError < StandardError
- attr_reader :scopes
- def initialize(scopes)
- @scopes = scopes.map { |s| s.try(:name) || s }
- end
- end
end
end
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index cdef1b546a9..0791a110c39 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -81,9 +81,9 @@ module API
service_args = [user_project, current_user, protected_branch_params]
protected_branch = if protected_branch
- ::ProtectedBranches::ApiUpdateService.new(*service_args).execute(protected_branch)
+ ::ProtectedBranches::LegacyApiUpdateService.new(*service_args).execute(protected_branch)
else
- ::ProtectedBranches::ApiCreateService.new(*service_args).execute
+ ::ProtectedBranches::LegacyApiCreateService.new(*service_args).execute
end
if protected_branch.valid?
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 2bc4039b019..38e05074353 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -180,10 +180,12 @@ module API
if params[:path]
commit.raw_diffs(limits: false).each do |diff|
next unless diff.new_path == params[:path]
+
lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
lines.each do |line|
next unless line.new_pos == params[:line] && line.type == params[:line_type]
+
break opts[:line_code] = Gitlab::Git.diff_line_code(diff.new_path, line.new_pos, line.old_pos)
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 16ae99b5c6c..62ee20bf7de 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -80,16 +80,37 @@ module API
expose :group_access, as: :group_access_level
end
- class BasicProjectDetails < Grape::Entity
- expose :id, :description, :default_branch, :tag_list
- expose :ssh_url_to_repo, :http_url_to_repo, :web_url
+ class ProjectIdentity < Grape::Entity
+ expose :id, :description
expose :name, :name_with_namespace
expose :path, :path_with_namespace
+ expose :created_at
+ end
+
+ class BasicProjectDetails < ProjectIdentity
+ include ::API::ProjectsRelationBuilder
+
+ expose :default_branch
+ # Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770
+ expose :tag_list do |project|
+ # project.tags.order(:name).pluck(:name) is the most suitable option
+ # to avoid loading all the ActiveRecord objects but, if we use it here
+ # it override the preloaded associations and makes a query
+ # (fixed in https://github.com/rails/rails/pull/25976).
+ project.tags.map(&:name).sort
+ end
+ expose :ssh_url_to_repo, :http_url_to_repo, :web_url
expose :avatar_url do |project, options|
project.avatar_url(only_path: false)
end
expose :star_count, :forks_count
- expose :created_at, :last_activity_at
+ expose :last_activity_at
+
+ def self.preload_relation(projects_relation, options = {})
+ projects_relation.preload(:project_feature, :route)
+ .preload(namespace: [:route, :owner],
+ tags: :taggings)
+ end
end
class Project < BasicProjectDetails
@@ -141,7 +162,7 @@ module API
expose :shared_runners_enabled
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
- expose :namespace, using: 'API::Entities::Namespace'
+ expose :namespace, using: 'API::Entities::NamespaceBasic'
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
expose :import_status
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] }
@@ -151,7 +172,7 @@ module API
expose :public_builds, as: :public_jobs
expose :ci_config_path
expose :shared_with_groups do |project, options|
- SharedGroup.represent(project.project_group_links.all, options)
+ SharedGroup.represent(project.project_group_links, options)
end
expose :only_allow_merge_if_pipeline_succeeds
expose :request_access_enabled
@@ -159,6 +180,18 @@ module API
expose :printing_merge_request_link_enabled
expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
+
+ def self.preload_relation(projects_relation, options = {})
+ super(projects_relation).preload(:group)
+ .preload(project_group_links: :group,
+ fork_network: :root_project,
+ forked_project_link: :forked_from_project,
+ forked_from_project: [:route, :forks, namespace: :route, tags: :taggings])
+ end
+
+ def self.forks_counting_projects(projects_relation)
+ projects_relation + projects_relation.map(&:forked_from_project).compact
+ end
end
class ProjectStatistics < Grape::Entity
@@ -242,7 +275,11 @@ module API
end
expose :merged do |repo_branch, options|
- options[:project].repository.merged_to_root_ref?(repo_branch, options[:merged_branch_names])
+ if options[:merged_branch_names]
+ options[:merged_branch_names].include?(repo_branch.name)
+ else
+ options[:project].repository.merged_to_root_ref?(repo_branch)
+ end
end
expose :protected do |repo_branch, options|
@@ -609,9 +646,11 @@ module API
expose :created_at
end
- class Namespace < Grape::Entity
+ class NamespaceBasic < Grape::Entity
expose :id, :name, :path, :kind, :full_path, :parent_id
+ end
+ class Namespace < NamespaceBasic
expose :members_count_with_descendants, if: -> (namespace, opts) { expose_members_count_with_descendants?(namespace, opts) } do |namespace, _|
namespace.users_with_descendants.count
end
@@ -671,7 +710,7 @@ module API
if options.key?(:project_members)
(options[:project_members] || []).find { |member| member.source_id == project.id }
else
- project.project_members.find_by(user_id: options[:current_user].id)
+ project.project_member(options[:current_user])
end
end
@@ -680,11 +719,25 @@ module API
if options.key?(:group_members)
(options[:group_members] || []).find { |member| member.source_id == project.namespace_id }
else
- project.group.group_members.find_by(user_id: options[:current_user].id)
+ project.group.group_member(options[:current_user])
end
end
end
end
+
+ def self.preload_relation(projects_relation, options = {})
+ relation = super(projects_relation, options)
+
+ unless options.key?(:group_members)
+ relation = relation.preload(group: [group_members: [:source, user: [notification_settings: :source]]])
+ end
+
+ unless options.key?(:project_members)
+ relation = relation.preload(project_members: [:source, user: [notification_settings: :source]])
+ end
+
+ relation
+ end
end
class LabelBasic < Grape::Entity
@@ -763,7 +816,10 @@ module API
expose(:default_project_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_project_visibility) }
expose(:default_snippet_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_snippet_visibility) }
expose(:default_group_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_group_visibility) }
- expose :password_authentication_enabled, as: :signin_enabled
+
+ # support legacy names, can be removed in v5
+ expose :password_authentication_enabled_for_web, as: :password_authentication_enabled
+ expose :password_authentication_enabled_for_web, as: :signin_enabled
end
class Release < Grape::Entity
@@ -820,17 +876,24 @@ module API
expose :id, :sha, :ref, :status
end
- class Job < Grape::Entity
+ class JobBasic < Grape::Entity
expose :id, :status, :stage, :name, :ref, :tag, :coverage
expose :created_at, :started_at, :finished_at
expose :duration
expose :user, with: User
- expose :artifacts_file, using: JobArtifactFile, if: -> (job, opts) { job.artifacts? }
expose :commit, with: Commit
- expose :runner, with: Runner
expose :pipeline, with: PipelineBasic
end
+ class Job < JobBasic
+ expose :artifacts_file, using: JobArtifactFile, if: -> (job, opts) { job.artifacts? }
+ expose :runner, with: Runner
+ end
+
+ class JobBasicWithProject < JobBasic
+ expose :project, with: ProjectIdentity
+ end
+
class Trigger < Grape::Entity
expose :id
expose :token, :description
@@ -987,13 +1050,9 @@ module API
expose :type, :url, :username, :password
end
- class ArtifactFile < Grape::Entity
- expose :filename, :size
- end
-
class Dependency < Grape::Entity
expose :id, :name, :token
- expose :artifacts_file, using: ArtifactFile, if: ->(job, _) { job.artifacts? }
+ expose :artifacts_file, using: JobArtifactFile, if: ->(job, _) { job.artifacts? }
end
class Response < Grape::Entity
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index bcf2e6dae1d..b81f07a1770 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -52,6 +52,13 @@ module API
groups
end
+ def find_group_projects(params)
+ group = find_group!(params[:id])
+ projects = GroupProjectsFinder.new(group: group, current_user: current_user, params: project_finder_params).execute
+ projects = reorder_projects(projects)
+ paginate(projects)
+ end
+
def present_groups(params, groups)
options = {
with: Entities::Group,
@@ -170,11 +177,10 @@ module API
use :pagination
end
get ":id/projects" do
- group = find_group!(params[:id])
- projects = GroupProjectsFinder.new(group: group, current_user: current_user, params: project_finder_params).execute
- projects = reorder_projects(projects)
+ projects = find_group_projects(params)
entity = params[:simple] ? Entities::BasicProjectDetails : Entities::Project
- present paginate(projects), with: entity, current_user: current_user
+
+ present entity.prepare_relation(projects), with: entity, current_user: current_user
end
desc 'Get a list of subgroups in this group.' do
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 3c8960cb1ab..686bf7a3c2b 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -50,6 +50,10 @@ module API
initial_current_user != current_user
end
+ def user_namespace
+ @user_namespace ||= find_namespace!(params[:id])
+ end
+
def user_group
@group ||= find_group!(params[:id])
end
@@ -112,6 +116,24 @@ module API
end
end
+ def find_namespace(id)
+ if id.to_s =~ /^\d+$/
+ Namespace.find_by(id: id)
+ else
+ Namespace.find_by_full_path(id)
+ end
+ end
+
+ def find_namespace!(id)
+ namespace = find_namespace(id)
+
+ if can?(current_user, :read_namespace, namespace)
+ namespace
+ else
+ not_found!('Namespace')
+ end
+ end
+
def find_project_label(id)
label = available_labels.find_by_id(id) || available_labels.find_by_title(id)
label || not_found!('Label')
@@ -398,7 +420,7 @@ module API
begin
@initial_current_user = Gitlab::Auth::UniqueIpsLimiter.limit_user! { find_current_user! }
- rescue APIGuard::UnauthorizedError
+ rescue Gitlab::Auth::UnauthorizedError
unauthorized!
end
end
diff --git a/lib/api/helpers/custom_validators.rb b/lib/api/helpers/custom_validators.rb
index 0a8f3073a50..dd4f6c41131 100644
--- a/lib/api/helpers/custom_validators.rb
+++ b/lib/api/helpers/custom_validators.rb
@@ -4,6 +4,7 @@ module API
class Absence < Grape::Validations::Base
def validate_param!(attr_name, params)
return if params.respond_to?(:key?) && !params.key?(attr_name)
+
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:absence)
end
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 4b3c473b0bb..d6dea4c30e3 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -2,8 +2,8 @@ module API
module Helpers
module InternalHelpers
SSH_GITALY_FEATURES = {
- 'git-receive-pack' => :ssh_receive_pack,
- 'git-upload-pack' => :ssh_upload_pack
+ 'git-receive-pack' => [:ssh_receive_pack, Gitlab::GitalyClient::MigrationStatus::OPT_IN],
+ 'git-upload-pack' => [:ssh_upload_pack, Gitlab::GitalyClient::MigrationStatus::OPT_OUT]
}.freeze
def wiki?
@@ -102,8 +102,8 @@ module API
# Return the Gitaly Address if it is enabled
def gitaly_payload(action)
- feature = SSH_GITALY_FEATURES[action]
- return unless feature && Gitlab::GitalyClient.feature_enabled?(feature)
+ feature, status = SSH_GITALY_FEATURES[action]
+ return unless feature && Gitlab::GitalyClient.feature_enabled?(feature, status: status)
{
repository: repository.gitaly_repository,
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index 95108292aac..bb70370ba77 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -2,6 +2,8 @@ module API
module Helpers
module Pagination
def paginate(relation)
+ relation = add_default_order(relation)
+
relation.page(params[:page]).per(params[:per_page]).tap do |data|
add_pagination_headers(data)
end
@@ -45,6 +47,14 @@ module API
# Ensure there is in total at least 1 page
[paginated_data.total_pages, 1].max
end
+
+ def add_default_order(relation)
+ if relation.is_a?(ActiveRecord::Relation) && relation.order_values.empty?
+ relation = relation.order(:id)
+ end
+
+ relation
+ end
end
end
end
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 282af32ca94..2cae53dba53 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -14,6 +14,7 @@ module API
def get_runner_version_from_params
return unless params['info'].present?
+
attributes_for_keys(%w(name version revision platform architecture), params['info'])
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 74dfd9f96de..e60e00d7956 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -255,7 +255,9 @@ module API
authorize!(:destroy_issue, issue)
- destroy_conditionally!(issue)
+ destroy_conditionally!(issue) do |issue|
+ Issuable::DestroyService.new(user_project, current_user).execute(issue)
+ end
end
desc 'List merge requests closing issue' do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 726f09e3669..d34886fca2e 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -21,7 +21,7 @@ module API
return merge_requests if args[:view] == 'simple'
merge_requests
- .preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels, :timelogs)
+ .preload(:notes, :author, :assignee, :milestone, :latest_merge_request_diff, :labels, :timelogs)
end
params :merge_requests_params do
@@ -167,7 +167,9 @@ module API
authorize!(:destroy_merge_request, merge_request)
- destroy_conditionally!(merge_request)
+ destroy_conditionally!(merge_request) do |merge_request|
+ Issuable::DestroyService.new(user_project, current_user).execute(merge_request)
+ end
end
params do
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index f1eaff6b0eb..32b77aedba8 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -19,6 +19,16 @@ module API
present paginate(namespaces), with: Entities::Namespace, current_user: current_user
end
+
+ desc 'Get a namespace by ID' do
+ success Entities::Namespace
+ end
+ params do
+ requires :id, type: String, desc: "Namespace's ID or path"
+ end
+ get ':id' do
+ present user_namespace, with: Entities::Namespace, current_user: current_user
+ end
end
end
end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 0b9ab4eeb05..3588dc85c9e 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -18,6 +18,10 @@ module API
end
params do
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+ optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
+ desc: 'Return notes ordered by `created_at` or `updated_at` fields.'
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return notes sorted in `asc` or `desc` order.'
use :pagination
end
get ":id/#{noteables_str}/:noteable_id/notes" do
@@ -29,11 +33,12 @@ module API
# at the DB query level (which we cannot in that case), the current
# page can have less elements than :per_page even if
# there's more than one page.
+ raw_notes = noteable.notes.with_metadata.reorder(params[:order_by] => params[:sort])
notes =
# paginate() only works with a relation. This could lead to a
# mismatch between the pagination headers info and the actual notes
# array returned, but this is really a edge-case.
- paginate(noteable.notes)
+ paginate(raw_notes)
.reject { |n| n.cross_reference_not_visible_for?(current_user) }
present notes, with: Entities::Note
else
@@ -50,7 +55,7 @@ module API
end
get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
noteable = find_project_noteable(noteables_str, params[:noteable_id])
- note = noteable.notes.find(params[:note_id])
+ note = noteable.notes.with_metadata.find(params[:note_id])
can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user)
if can_read_note
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 4cd7e714aa2..14a4fc6f025 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -79,11 +79,11 @@ module API
projects = projects.with_statistics if params[:statistics]
projects = projects.with_issues_enabled if params[:with_issues_enabled]
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
+ projects = paginate(projects)
if current_user
- projects = projects.includes(:route, :taggings, namespace: :route)
- project_members = current_user.project_members
- group_members = current_user.group_members
+ project_members = current_user.project_members.preload(:source, user: [notification_settings: :source])
+ group_members = current_user.group_members.preload(:source, user: [notification_settings: :source])
end
options = options.reverse_merge(
@@ -95,7 +95,7 @@ module API
)
options[:with] = Entities::BasicProjectDetails if params[:simple]
- present paginate(projects), options
+ present options[:with].prepare_relation(projects, options), options
end
end
diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb
new file mode 100644
index 00000000000..6482fd94ab8
--- /dev/null
+++ b/lib/api/projects_relation_builder.rb
@@ -0,0 +1,34 @@
+module API
+ module ProjectsRelationBuilder
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def prepare_relation(projects_relation, options = {})
+ projects_relation = preload_relation(projects_relation, options)
+ execute_batch_counting(projects_relation)
+ projects_relation
+ end
+
+ def preload_relation(projects_relation, options = {})
+ projects_relation
+ end
+
+ def forks_counting_projects(projects_relation)
+ projects_relation
+ end
+
+ def batch_forks_counting(projects_relation)
+ ::Projects::BatchForksCountService.new(forks_counting_projects(projects_relation)).refresh_cache
+ end
+
+ def batch_open_issues_counting(projects_relation)
+ ::Projects::BatchOpenIssuesCountService.new(projects_relation).refresh_cache
+ end
+
+ def execute_batch_counting(projects_relation)
+ batch_forks_counting(projects_relation)
+ batch_open_issues_counting(projects_relation)
+ end
+ end
+ end
+end
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index 15fcb9e8e27..b5021e8a712 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -40,10 +40,10 @@ module API
params do
requires :name, type: String, desc: 'The name of the protected branch'
optional :push_access_level, type: Integer, default: Gitlab::Access::MASTER,
- values: ProtectedBranchAccess::ALLOWED_ACCESS_LEVELS,
+ values: ProtectedRefAccess::ALLOWED_ACCESS_LEVELS,
desc: 'Access levels allowed to push (defaults: `40`, master access level)'
optional :merge_access_level, type: Integer, default: Gitlab::Access::MASTER,
- values: ProtectedBranchAccess::ALLOWED_ACCESS_LEVELS,
+ values: ProtectedRefAccess::ALLOWED_ACCESS_LEVELS,
desc: 'Access levels allowed to merge (defaults: `40`, master access level)'
end
post ':id/protected_branches' do
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index a3987c560dd..80feb629d54 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -215,18 +215,20 @@ module API
job = authenticate_job!
forbidden!('Job is not running!') unless job.running?
- artifacts_upload_path = ArtifactUploader.artifacts_upload_path
+ artifacts_upload_path = JobArtifactUploader.artifacts_upload_path
artifacts = uploaded_file(:file, artifacts_upload_path)
metadata = uploaded_file(:metadata, artifacts_upload_path)
bad_request!('Missing artifacts file!') unless artifacts
file_to_large! unless artifacts.size < max_artifacts_size
- job.artifacts_file = artifacts
- job.artifacts_metadata = metadata
- job.artifacts_expire_in = params['expire_in'] ||
+ expire_in = params['expire_in'] ||
Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in
+ job.build_job_artifacts_archive(project: job.project, file_type: :archive, file: artifacts, expire_in: expire_in)
+ job.build_job_artifacts_metadata(project: job.project, file_type: :metadata, file: metadata, expire_in: expire_in) if metadata
+ job.artifacts_expire_in = expire_in
+
if job.save
present job, with: Entities::JobRequest::Response
else
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index d3559ef71be..996457c5dfe 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -84,6 +84,23 @@ module API
destroy_conditionally!(runner)
end
+
+ desc 'List jobs running on a runner' do
+ success Entities::JobBasicWithProject
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the runner'
+ optional :status, type: String, desc: 'Status of the job', values: Ci::Build::AVAILABLE_STATUSES
+ use :pagination
+ end
+ get ':id/jobs' do
+ runner = get_runner(params[:id])
+ authenticate_list_runners_jobs!(runner)
+
+ jobs = RunnerJobsFinder.new(runner, params).execute
+
+ present paginate(jobs), with: Entities::JobBasicWithProject
+ end
end
params do
@@ -165,17 +182,20 @@ module API
def authenticate_show_runner!(runner)
return if runner.is_shared || current_user.admin?
+
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
def authenticate_update_runner!(runner)
return if current_user.admin?
+
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
def authenticate_delete_runner!(runner)
return if current_user.admin?
+
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner associated with more than one project") if runner.projects.count > 1
forbidden!("No access granted") unless user_can_access_runner?(runner)
@@ -185,6 +205,13 @@ module API
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner is locked") if runner.locked?
return if current_user.admin?
+
+ forbidden!("No access granted") unless user_can_access_runner?(runner)
+ end
+
+ def authenticate_list_runners_jobs!(runner)
+ return if current_user.admin?
+
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 851b226e9e5..cee4d309816 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -44,9 +44,11 @@ module API
requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
end
optional :after_sign_up_text, type: String, desc: 'Text shown after sign up'
- optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled'
- optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled'
- mutually_exclusive :password_authentication_enabled, :signin_enabled
+ optional :password_authentication_enabled_for_web, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface'
+ optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface' # support legacy names, can be removed in v5
+ optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface' # support legacy names, can be removed in v5
+ mutually_exclusive :password_authentication_enabled_for_web, :password_authentication_enabled, :signin_enabled
+ optional :password_authentication_enabled_for_git, type: Boolean, desc: 'Flag indicating if password authentication is enabled for Git over HTTP(S)'
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication'
given require_two_factor_authentication: ->(val) { val } do
requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication'
@@ -121,6 +123,9 @@ module API
end
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.'
+ optional :gitaly_timeout_default, type: Integer, desc: 'Default Gitaly timeout, in seconds. Set to 0 to disable timeouts.'
+ optional :gitaly_timeout_medium, type: Integer, desc: 'Medium Gitaly timeout, in seconds. Set to 0 to disable timeouts.'
+ optional :gitaly_timeout_fast, type: Integer, desc: 'Gitaly fast operation timeout, in seconds. Set to 0 to disable timeouts.'
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
@@ -135,8 +140,11 @@ module API
put "application/settings" do
attrs = declared_params(include_missing: false)
+ # support legacy names, can be removed in v5
if attrs.has_key?(:signin_enabled)
- attrs[:password_authentication_enabled] = attrs.delete(:signin_enabled)
+ attrs[:password_authentication_enabled_for_web] = attrs.delete(:signin_enabled)
+ elsif attrs.has_key?(:password_authentication_enabled)
+ attrs[:password_authentication_enabled_for_web] = attrs.delete(:password_authentication_enabled)
end
if current_settings.update_attributes(attrs)
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index 00eb7c60f16..c736cc32021 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -95,6 +95,7 @@ module API
put ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id))
return not_found!('Snippet') unless snippet
+
authorize! :update_personal_snippet, snippet
attrs = declared_params(include_missing: false).merge(request: request, api: true)
diff --git a/lib/api/users.rb b/lib/api/users.rb
index d80b364bd09..e5de31ad51b 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -31,7 +31,6 @@ module API
optional :location, type: String, desc: 'The location of the user'
optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator'
optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups'
- optional :skip_confirmation, type: Boolean, default: false, desc: 'Flag indicating the account is confirmed'
optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
optional :avatar, type: File, desc: 'Avatar image for user'
all_or_none_of :extern_uid, :provider
@@ -77,6 +76,8 @@ module API
forbidden!("Not authorized to access /api/v4/users") unless authorized
entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
+ users = users.preload(:identities, :u2f_registrations) if entity == Entities::UserWithAdmin
+
present paginate(users), with: entity
end
@@ -101,6 +102,7 @@ module API
requires :email, type: String, desc: 'The email of the user'
optional :password, type: String, desc: 'The password of the new user'
optional :reset_password, type: Boolean, desc: 'Flag indicating the user will be sent a password reset token'
+ optional :skip_confirmation, type: Boolean, desc: 'Flag indicating the account is confirmed'
at_least_one_of :password, :reset_password
requires :name, type: String, desc: 'The name of the user'
requires :username, type: String, desc: 'The username of the user'
@@ -134,6 +136,7 @@ module API
requires :id, type: Integer, desc: 'The ID of the user'
optional :email, type: String, desc: 'The email of the user'
optional :password, type: String, desc: 'The password of the new user'
+ optional :skip_reconfirmation, type: Boolean, desc: 'Flag indicating the account skips the confirmation by email'
optional :name, type: String, desc: 'The name of the user'
optional :username, type: String, desc: 'The username of the user'
use :optional_attributes
diff --git a/lib/api/v3/commits.rb b/lib/api/v3/commits.rb
index be360fbfc0c..0ef26aa696a 100644
--- a/lib/api/v3/commits.rb
+++ b/lib/api/v3/commits.rb
@@ -169,10 +169,12 @@ module API
if params[:path]
commit.raw_diffs(limits: false).each do |diff|
next unless diff.new_path == params[:path]
+
lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
lines.each do |line|
next unless line.new_pos == params[:line] && line.type == params[:line_type]
+
break opts[:line_code] = Gitlab::Git.diff_line_code(diff.new_path, line.new_pos, line.old_pos)
end
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index afdd7b83998..c17b6f45ed8 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -172,8 +172,8 @@ module API
expose :id
expose :default_projects_limit
expose :signup_enabled
- expose :password_authentication_enabled
- expose :password_authentication_enabled, as: :signin_enabled
+ expose :password_authentication_enabled_for_web, as: :password_authentication_enabled
+ expose :password_authentication_enabled_for_web, as: :signin_enabled
expose :gravatar_enabled
expose :sign_in_text
expose :after_sign_up_text
diff --git a/lib/api/v3/runners.rb b/lib/api/v3/runners.rb
index faa265f3314..c6d9957d452 100644
--- a/lib/api/v3/runners.rb
+++ b/lib/api/v3/runners.rb
@@ -51,6 +51,7 @@ module API
helpers do
def authenticate_delete_runner!(runner)
return if current_user.admin?
+
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner associated with more than one project") if runner.projects.count > 1
forbidden!("No access granted") unless user_can_access_runner?(runner)
diff --git a/lib/api/v3/settings.rb b/lib/api/v3/settings.rb
index 202011cfcbe..9b4ab7630fb 100644
--- a/lib/api/v3/settings.rb
+++ b/lib/api/v3/settings.rb
@@ -44,8 +44,8 @@ module API
requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
end
optional :after_sign_up_text, type: String, desc: 'Text shown after sign up'
- optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled'
- optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled'
+ optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface'
+ optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface'
mutually_exclusive :password_authentication_enabled, :signin_enabled
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication'
given require_two_factor_authentication: ->(val) { val } do
@@ -131,7 +131,9 @@ module API
attrs = declared_params(include_missing: false)
if attrs.has_key?(:signin_enabled)
- attrs[:password_authentication_enabled] = attrs.delete(:signin_enabled)
+ attrs[:password_authentication_enabled_for_web] = attrs.delete(:signin_enabled)
+ elsif attrs.has_key?(:password_authentication_enabled)
+ attrs[:password_authentication_enabled_for_web] = attrs.delete(:password_authentication_enabled)
end
if current_settings.update_attributes(attrs)
diff --git a/lib/api/v3/snippets.rb b/lib/api/v3/snippets.rb
index 0762fc02d70..126ec72248e 100644
--- a/lib/api/v3/snippets.rb
+++ b/lib/api/v3/snippets.rb
@@ -91,6 +91,7 @@ module API
put ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id))
return not_found!('Snippet') unless snippet
+
authorize! :update_personal_snippet, snippet
attrs = declared_params(include_missing: false)
@@ -113,6 +114,7 @@ module API
delete ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id))
return not_found!('Snippet') unless snippet
+
authorize! :destroy_personal_snippet, snippet
snippet.destroy
no_content!
diff --git a/lib/backup/artifacts.rb b/lib/backup/artifacts.rb
index 1f4bda6f588..7a582a20056 100644
--- a/lib/backup/artifacts.rb
+++ b/lib/backup/artifacts.rb
@@ -3,7 +3,7 @@ require 'backup/files'
module Backup
class Artifacts < Files
def initialize
- super('artifacts', ArtifactUploader.local_artifacts_store)
+ super('artifacts', LegacyArtifactUploader.local_store_path)
end
def create_files_dir
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 9fef386de16..8975395aff1 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -213,7 +213,8 @@ module Banzai
end
def object_link_text(object, matches)
- text = object.reference_link_text(context[:project])
+ parent = context[:project] || context[:group]
+ text = object.reference_link_text(parent)
extras = object_link_text_extras(object, matches)
text += " (#{extras.join(", ")})" if extras.any?
diff --git a/lib/banzai/filter/mermaid_filter.rb b/lib/banzai/filter/mermaid_filter.rb
new file mode 100644
index 00000000000..b545b947a2c
--- /dev/null
+++ b/lib/banzai/filter/mermaid_filter.rb
@@ -0,0 +1,20 @@
+module Banzai
+ module Filter
+ class MermaidFilter < HTML::Pipeline::Filter
+ def call
+ doc.css('pre[lang="mermaid"]').add_class('mermaid')
+ doc.css('pre[lang="mermaid"]').add_class('js-render-mermaid')
+
+ # The `<code></code>` blocks are added in the lib/banzai/filter/syntax_highlight_filter.rb
+ # We want to keep context and consistency, so we the blocks are added for all filters.
+ # Details: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15107/diffs?diff_id=7962900#note_45495859
+ doc.css('pre[lang="mermaid"]').each do |pre|
+ document = pre.at('code')
+ document.replace(document.content)
+ end
+
+ doc
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 7da565043d1..a79a0154846 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -14,23 +14,26 @@ module Banzai
end
def highlight_node(node)
- language = node.attr('lang')
code = node.text
- css_classes = "code highlight"
- lexer = lexer_for(language)
- lang = lexer.tag
-
- begin
- code = Rouge::Formatters::HTMLGitlab.format(lex(lexer, code), tag: lang)
-
- css_classes << " js-syntax-highlight #{lang}"
- rescue
- lang = nil
- # Gracefully handle syntax highlighter bugs/errors to ensure
- # users can still access an issue/comment/etc.
+ css_classes = 'code highlight js-syntax-highlight'
+ language = node.attr('lang')
+
+ if use_rouge?(language)
+ lexer = lexer_for(language)
+ language = lexer.tag
+
+ begin
+ code = Rouge::Formatters::HTMLGitlab.format(lex(lexer, code), tag: language)
+ css_classes << " #{language}"
+ rescue
+ # Gracefully handle syntax highlighter bugs/errors to ensure
+ # users can still access an issue/comment/etc.
+
+ language = nil
+ end
end
- highlighted = %(<pre class="#{css_classes}" lang="#{lang}" v-pre="true"><code>#{code}</code></pre>)
+ highlighted = %(<pre class="#{css_classes}" lang="#{language}" v-pre="true"><code>#{code}</code></pre>)
# Extracted to a method to measure it
replace_parent_pre_element(node, highlighted)
@@ -51,6 +54,10 @@ module Banzai
# Replace the parent `pre` element with the entire highlighted block
node.parent.replace(highlighted)
end
+
+ def use_rouge?(language)
+ %w(math mermaid plantuml).exclude?(language)
+ end
end
end
end
diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb
index 9bb8ed913d8..ecb3affbba5 100644
--- a/lib/banzai/object_renderer.rb
+++ b/lib/banzai/object_renderer.rb
@@ -86,6 +86,7 @@ module Banzai
def save_options
return {} unless base_context[:xhtml]
+
{ save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML }
end
end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 3208abfc538..55874ad50a3 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -14,6 +14,7 @@ module Banzai
Filter::SyntaxHighlightFilter,
Filter::MathFilter,
+ Filter::MermaidFilter,
Filter::UploadLinkFilter,
Filter::VideoLinkFilter,
Filter::ImageLazyLoadFilter,
diff --git a/lib/banzai/querying.rb b/lib/banzai/querying.rb
index fb2faae02bc..a19a05e8c0d 100644
--- a/lib/banzai/querying.rb
+++ b/lib/banzai/querying.rb
@@ -52,8 +52,10 @@ module Banzai
children.each do |child|
next if child.text.blank?
+
node = nodes.shift
break unless node == child
+
filtered_nodes << node
end
end
diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb
index 4d336068861..8932d4f2905 100644
--- a/lib/banzai/reference_parser/user_parser.rb
+++ b/lib/banzai/reference_parser/user_parser.rb
@@ -31,6 +31,7 @@ module Banzai
nodes.each do |node|
if node.has_attribute?(group_attr)
next unless can_read_group_reference?(node, user, groups)
+
visible << node
elsif can_read_project_reference?(node)
visible << node
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index 5cb9adf52b0..0050295eeda 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -149,6 +149,7 @@ module Banzai
def self.full_cache_key(cache_key, pipeline_name)
return unless cache_key
+
["banzai", *cache_key, pipeline_name || :full]
end
@@ -157,6 +158,7 @@ module Banzai
# method.
def self.full_cache_multi_key(cache_key, pipeline_name)
return unless cache_key
+
Rails.cache.__send__(:expanded_key, full_cache_key(cache_key, pipeline_name)) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb
index ae65653645b..b1949d693ad 100644
--- a/lib/declarative_policy.rb
+++ b/lib/declarative_policy.rb
@@ -30,6 +30,7 @@ module DeclarativePolicy
policy_class = class_for_class(subject.class)
raise "no policy for #{subject.class.name}" if policy_class.nil?
+
policy_class
end
@@ -84,6 +85,7 @@ module DeclarativePolicy
while subject.respond_to?(:declarative_policy_delegate)
raise ArgumentError, "circular delegations" if seen.include?(subject.object_id)
+
seen << subject.object_id
subject = subject.declarative_policy_delegate
end
diff --git a/lib/declarative_policy/base.rb b/lib/declarative_policy/base.rb
index b028169f500..47542194497 100644
--- a/lib/declarative_policy/base.rb
+++ b/lib/declarative_policy/base.rb
@@ -276,6 +276,7 @@ module DeclarativePolicy
# boolean `false`
def cache(key, &b)
return @cache[key] if cached?(key)
+
@cache[key] = yield
end
@@ -291,6 +292,7 @@ module DeclarativePolicy
@_conditions[name] ||=
begin
raise "invalid condition #{name}" unless self.class.conditions.key?(name)
+
ManifestCondition.new(self.class.conditions[name], self)
end
end
diff --git a/lib/declarative_policy/cache.rb b/lib/declarative_policy/cache.rb
index 0804edba016..780d8f707bd 100644
--- a/lib/declarative_policy/cache.rb
+++ b/lib/declarative_policy/cache.rb
@@ -3,6 +3,7 @@ module DeclarativePolicy
class << self
def user_key(user)
return '<anonymous>' if user.nil?
+
id_for(user)
end
@@ -15,6 +16,7 @@ module DeclarativePolicy
def subject_key(subject)
return '<nil>' if subject.nil?
return subject.inspect if subject.is_a?(Symbol)
+
"#{subject.class.name}:#{id_for(subject)}"
end
diff --git a/lib/declarative_policy/rule.rb b/lib/declarative_policy/rule.rb
index 7cfa82a9a9f..e309244a3b3 100644
--- a/lib/declarative_policy/rule.rb
+++ b/lib/declarative_policy/rule.rb
@@ -83,6 +83,7 @@ module DeclarativePolicy
def cached_pass?(context)
condition = context.condition(@name)
return nil unless condition.cached?
+
condition.pass?
end
@@ -109,6 +110,7 @@ module DeclarativePolicy
def delegated_context(context)
policy = context.delegated_policies[@delegate_name]
raise MissingDelegate if policy.nil?
+
policy
end
@@ -121,6 +123,7 @@ module DeclarativePolicy
def cached_pass?(context)
condition = delegated_context(context).condition(@name)
return nil unless condition.cached?
+
condition.pass?
rescue MissingDelegate
false
@@ -157,6 +160,7 @@ module DeclarativePolicy
def cached_pass?(context)
runner = context.runner(@ability)
return nil unless runner.cached?
+
runner.pass?
end
@@ -258,6 +262,7 @@ module DeclarativePolicy
def score(context)
return 0 unless cached_pass?(context).nil?
+
@rules.map { |r| r.score(context) }.inject(0, :+)
end
diff --git a/lib/declarative_policy/runner.rb b/lib/declarative_policy/runner.rb
index 45ff2ef9ced..77c91817382 100644
--- a/lib/declarative_policy/runner.rb
+++ b/lib/declarative_policy/runner.rb
@@ -43,6 +43,7 @@ module DeclarativePolicy
# used by Rule::Ability. See #steps_by_score
def score
return 0 if cached?
+
steps.map(&:score).inject(0, :+)
end
diff --git a/lib/file_size_validator.rb b/lib/file_size_validator.rb
index de391de9059..69d981e8be9 100644
--- a/lib/file_size_validator.rb
+++ b/lib/file_size_validator.rb
@@ -8,6 +8,7 @@ class FileSizeValidator < ActiveModel::EachValidator
def initialize(options)
if range = (options.delete(:in) || options.delete(:within))
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
+
options[:minimum], options[:maximum] = range.begin, range.end
options[:maximum] -= 1 if range.exclude_end?
end
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index b4012ebbb99..7127948cf00 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -58,9 +58,9 @@ module Gitlab
def protection_options
{
"Not protected: Both developers and masters can push new commits, force push, or delete the branch." => PROTECTION_NONE,
- "Protected against pushes: Developers cannot push new commits, but are allowed to accept merge requests to the branch." => PROTECTION_DEV_CAN_MERGE,
- "Partially protected: Developers can push new commits, but cannot force push or delete the branch. Masters can do all of those." => PROTECTION_DEV_CAN_PUSH,
- "Fully protected: Developers cannot push new commits, force push, or delete the branch. Only masters can do any of those." => PROTECTION_FULL
+ "Protected against pushes: Developers cannot push new commits, but are allowed to accept merge requests to the branch. Masters can push to the branch." => PROTECTION_DEV_CAN_MERGE,
+ "Partially protected: Both developers and masters can push new commits, but cannot force push or delete the branch." => PROTECTION_DEV_CAN_PUSH,
+ "Fully protected: Developers cannot push new commits, but masters can. No-one can force push or delete the branch." => PROTECTION_FULL
}
end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index cbbc51db99e..65d7fd3ec70 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -34,7 +34,7 @@ module Gitlab
rate_limit!(ip, success: result.success?, login: login)
Gitlab::Auth::UniqueIpsLimiter.limit_user!(result.actor)
- return result if result.success? || current_application_settings.password_authentication_enabled? || Gitlab::LDAP::Config.enabled?
+ return result if result.success? || authenticate_using_internal_or_ldap_password?
# If sign-in is disabled and LDAP is not configured, recommend a
# personal access token on failed auth attempts
@@ -45,6 +45,10 @@ module Gitlab
# Avoid resource intensive login checks if password is not provided
return unless password.present?
+ # Nothing to do here if internal auth is disabled and LDAP is
+ # not configured
+ return unless authenticate_using_internal_or_ldap_password?
+
Gitlab::Auth::UniqueIpsLimiter.limit_user! do
user = User.by_login(login)
@@ -52,10 +56,8 @@ module Gitlab
# LDAP users are only authenticated via LDAP
if user.nil? || user.ldap_user?
# Second chance - try LDAP authentication
- return unless Gitlab::LDAP::Config.enabled?
-
Gitlab::LDAP::Authentication.login(login, password)
- else
+ elsif current_application_settings.password_authentication_enabled_for_git?
user if user.active? && user.valid_password?(password)
end
end
@@ -84,6 +86,10 @@ module Gitlab
private
+ def authenticate_using_internal_or_ldap_password?
+ current_application_settings.password_authentication_enabled_for_git? || Gitlab::LDAP::Config.enabled?
+ end
+
def service_request_check(login, password, project)
matched_login = /(?<service>^[a-zA-Z]*-ci)-token$/.match(login)
@@ -128,7 +134,7 @@ module Gitlab
token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password)
if token && valid_scoped_token?(token, available_scopes)
- Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scope(token.scopes))
+ Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scopes(token.scopes))
end
end
@@ -140,10 +146,15 @@ module Gitlab
AccessTokenValidationService.new(token).include_any_scope?(scopes)
end
- def abilities_for_scope(scopes)
- scopes.map do |scope|
- self.public_send(:"#{scope}_scope_authentication_abilities") # rubocop:disable GitlabSecurity/PublicSend
- end.flatten.uniq
+ def abilities_for_scopes(scopes)
+ abilities_by_scope = {
+ api: full_authentication_abilities,
+ read_registry: [:read_container_image]
+ }
+
+ scopes.flat_map do |scope|
+ abilities_by_scope.fetch(scope.to_sym, [])
+ end.uniq
end
def lfs_token_check(login, password, project)
@@ -222,16 +233,6 @@ module Gitlab
:admin_container_image
]
end
- alias_method :api_scope_authentication_abilities, :full_authentication_abilities
-
- def read_registry_scope_authentication_abilities
- [:read_container_image]
- end
-
- # The currently used auth method doesn't allow any actions for this scope
- def read_user_scope_authentication_abilities
- []
- end
def available_scopes(current_user = nil)
scopes = API_SCOPES + registry_scopes
diff --git a/lib/gitlab/auth/request_authenticator.rb b/lib/gitlab/auth/request_authenticator.rb
new file mode 100644
index 00000000000..46ec040ce92
--- /dev/null
+++ b/lib/gitlab/auth/request_authenticator.rb
@@ -0,0 +1,25 @@
+# Use for authentication only, in particular for Rack::Attack.
+# Does not perform authorization of scopes, etc.
+module Gitlab
+ module Auth
+ class RequestAuthenticator
+ include UserAuthFinders
+
+ attr_reader :request
+
+ def initialize(request)
+ @request = request
+ end
+
+ def user
+ find_sessionless_user || find_user_from_warden
+ end
+
+ def find_sessionless_user
+ find_user_from_access_token || find_user_from_rss_token
+ rescue Gitlab::Auth::AuthenticationError
+ nil
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/auth/user_auth_finders.rb b/lib/gitlab/auth/user_auth_finders.rb
new file mode 100644
index 00000000000..b4114a3ac96
--- /dev/null
+++ b/lib/gitlab/auth/user_auth_finders.rb
@@ -0,0 +1,109 @@
+module Gitlab
+ module Auth
+ #
+ # Exceptions
+ #
+
+ AuthenticationError = Class.new(StandardError)
+ MissingTokenError = Class.new(AuthenticationError)
+ TokenNotFoundError = Class.new(AuthenticationError)
+ ExpiredError = Class.new(AuthenticationError)
+ RevokedError = Class.new(AuthenticationError)
+ UnauthorizedError = Class.new(AuthenticationError)
+
+ class InsufficientScopeError < AuthenticationError
+ attr_reader :scopes
+ def initialize(scopes)
+ @scopes = scopes.map { |s| s.try(:name) || s }
+ end
+ end
+
+ module UserAuthFinders
+ include Gitlab::Utils::StrongMemoize
+
+ PRIVATE_TOKEN_HEADER = 'HTTP_PRIVATE_TOKEN'.freeze
+ PRIVATE_TOKEN_PARAM = :private_token
+
+ # Check the Rails session for valid authentication details
+ def find_user_from_warden
+ current_request.env['warden']&.authenticate if verified_request?
+ end
+
+ def find_user_from_rss_token
+ return unless current_request.path.ends_with?('.atom') || current_request.format.atom?
+
+ token = current_request.params[:rss_token].presence
+ return unless token
+
+ User.find_by_rss_token(token) || raise(UnauthorizedError)
+ end
+
+ def find_user_from_access_token
+ return unless access_token
+
+ validate_access_token!
+
+ access_token.user || raise(UnauthorizedError)
+ end
+
+ def validate_access_token!(scopes: [])
+ return unless access_token
+
+ case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes)
+ when AccessTokenValidationService::INSUFFICIENT_SCOPE
+ raise InsufficientScopeError.new(scopes)
+ when AccessTokenValidationService::EXPIRED
+ raise ExpiredError
+ when AccessTokenValidationService::REVOKED
+ raise RevokedError
+ end
+ end
+
+ private
+
+ def access_token
+ strong_memoize(:access_token) do
+ find_oauth_access_token || find_personal_access_token
+ end
+ end
+
+ def find_personal_access_token
+ token =
+ current_request.params[PRIVATE_TOKEN_PARAM].presence ||
+ current_request.env[PRIVATE_TOKEN_HEADER].presence
+
+ return unless token
+
+ # Expiration, revocation and scopes are verified in `validate_access_token!`
+ PersonalAccessToken.find_by(token: token) || raise(UnauthorizedError)
+ end
+
+ def find_oauth_access_token
+ token = Doorkeeper::OAuth::Token.from_request(current_request, *Doorkeeper.configuration.access_token_methods)
+ return unless token
+
+ # Expiration, revocation and scopes are verified in `validate_access_token!`
+ oauth_token = OauthAccessToken.by_token(token)
+ raise UnauthorizedError unless oauth_token
+
+ oauth_token.revoke_previous_refresh_token!
+ oauth_token
+ end
+
+ # Check if the request is GET/HEAD, or if CSRF token is valid.
+ def verified_request?
+ Gitlab::RequestForgeryProtection.verified?(current_request.env)
+ end
+
+ def ensure_action_dispatch_request(request)
+ return request if request.is_a?(ActionDispatch::Request)
+
+ ActionDispatch::Request.new(request.env)
+ end
+
+ def current_request
+ @current_request ||= ensure_action_dispatch_request(request)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/.rubocop.yml b/lib/gitlab/background_migration/.rubocop.yml
new file mode 100644
index 00000000000..8242821cedc
--- /dev/null
+++ b/lib/gitlab/background_migration/.rubocop.yml
@@ -0,0 +1,52 @@
+# For background migrations we define a custom set of rules to make it less
+# difficult to review these migrations. To reduce the complexity of these
+# migrations some rules may be stricter than the defaults set in the root
+# .rubocop.yml file.
+---
+inherit_from: ../../../.rubocop.yml
+
+Metrics/AbcSize:
+ Enabled: true
+ Max: 30
+ Details: >
+ Code that involves a lot of branches can be very hard to wrap your head
+ around.
+
+Metrics/PerceivedComplexity:
+ Enabled: true
+
+Metrics/LineLength:
+ Enabled: true
+ Details: >
+ Long lines are very hard to read and make it more difficult to review
+ changes.
+
+Metrics/MethodLength:
+ Enabled: true
+ Max: 30
+ Details: >
+ Long methods can be very hard to review. Consider splitting this method up
+ into separate methods.
+
+Metrics/ClassLength:
+ Enabled: true
+ Details: >
+ Long classes can be very hard to review. Consider splitting this class up
+ into multiple classes.
+
+Metrics/BlockLength:
+ Enabled: true
+ Details: >
+ Long blocks can be hard to read. Consider splitting the code into separate
+ methods.
+
+Style/Documentation:
+ Enabled: true
+ Details: >
+ Adding documentation makes it easier to figure out what a migration is
+ supposed to do.
+
+Style/FrozenStringLiteralComment:
+ Enabled: true
+ Details: >-
+ This removes the need for calling "freeze", reducing noise in the code.
diff --git a/lib/gitlab/background_migration/create_fork_network_memberships_range.rb b/lib/gitlab/background_migration/create_fork_network_memberships_range.rb
index 67a39d28944..03b17b319fa 100644
--- a/lib/gitlab/background_migration/create_fork_network_memberships_range.rb
+++ b/lib/gitlab/background_migration/create_fork_network_memberships_range.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+# rubocop:disable Metrics/LineLength
+# rubocop:disable Style/Documentation
+
module Gitlab
module BackgroundMigration
class CreateForkNetworkMembershipsRange
diff --git a/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys.rb b/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys.rb
index e94719db72e..c2bf42f846d 100644
--- a/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys.rb
+++ b/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+# rubocop:disable Metrics/LineLength
+# rubocop:disable Style/Documentation
+
class Gitlab::BackgroundMigration::CreateGpgKeySubkeysFromGpgKeys
class GpgKey < ActiveRecord::Base
self.table_name = 'gpg_keys'
diff --git a/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range.rb b/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range.rb
index b1411be3016..a1af045a71f 100644
--- a/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range.rb
+++ b/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+# rubocop:disable Metrics/LineLength
+# rubocop:disable Style/Documentation
+
module Gitlab
module BackgroundMigration
class DeleteConflictingRedirectRoutesRange
diff --git a/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb b/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb
index 380802258f5..fd5cbf76e47 100644
--- a/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb
+++ b/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb
@@ -1,3 +1,9 @@
+# frozen_string_literal: true
+# rubocop:disable Metrics/MethodLength
+# rubocop:disable Metrics/LineLength
+# rubocop:disable Metrics/AbcSize
+# rubocop:disable Style/Documentation
+
module Gitlab
module BackgroundMigration
class DeserializeMergeRequestDiffsAndCommits
diff --git a/lib/gitlab/background_migration/migrate_build_stage_id_reference.rb b/lib/gitlab/background_migration/migrate_build_stage_id_reference.rb
index 91540127ea9..0a8a4313cd5 100644
--- a/lib/gitlab/background_migration/migrate_build_stage_id_reference.rb
+++ b/lib/gitlab/background_migration/migrate_build_stage_id_reference.rb
@@ -1,3 +1,6 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
module Gitlab
module BackgroundMigration
class MigrateBuildStageIdReference
diff --git a/lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb b/lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb
index 432f7c3e706..84ac00f1a5c 100644
--- a/lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb
+++ b/lib/gitlab/background_migration/migrate_events_to_push_event_payloads.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+# rubocop:disable Metrics/LineLength
+# rubocop:disable Style/Documentation
+
module Gitlab
module BackgroundMigration
# Class that migrates events for the new push event payloads setup. All
diff --git a/lib/gitlab/background_migration/migrate_stage_status.rb b/lib/gitlab/background_migration/migrate_stage_status.rb
index b1ff0900709..0e5c7f092f2 100644
--- a/lib/gitlab/background_migration/migrate_stage_status.rb
+++ b/lib/gitlab/background_migration/migrate_stage_status.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+# rubocop:disable Metrics/AbcSize
+# rubocop:disable Style/Documentation
+
module Gitlab
module BackgroundMigration
class MigrateStageStatus
diff --git a/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb b/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb
index 0881244ed49..7f243073fd0 100644
--- a/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb
+++ b/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+# rubocop:disable Metrics/LineLength
+# rubocop:disable Style/Documentation
+
module Gitlab
module BackgroundMigration
class MigrateSystemUploadsToNewFolder
diff --git a/lib/gitlab/background_migration/move_personal_snippet_files.rb b/lib/gitlab/background_migration/move_personal_snippet_files.rb
index 07cec96bcc3..a4ef51fd0e8 100644
--- a/lib/gitlab/background_migration/move_personal_snippet_files.rb
+++ b/lib/gitlab/background_migration/move_personal_snippet_files.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+# rubocop:disable Metrics/LineLength
+# rubocop:disable Style/Documentation
+
module Gitlab
module BackgroundMigration
class MovePersonalSnippetFiles
diff --git a/lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb b/lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
index bc53e6d7f94..85749366bfd 100644
--- a/lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
+++ b/lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
@@ -1,3 +1,10 @@
+# frozen_string_literal: true
+# rubocop:disable Metrics/MethodLength
+# rubocop:disable Metrics/LineLength
+# rubocop:disable Metrics/ClassLength
+# rubocop:disable Metrics/BlockLength
+# rubocop:disable Style/Documentation
+
module Gitlab
module BackgroundMigration
class NormalizeLdapExternUidsRange
diff --git a/lib/gitlab/background_migration/populate_fork_networks_range.rb b/lib/gitlab/background_migration/populate_fork_networks_range.rb
index 2ef3a207dd3..a976cb4c243 100644
--- a/lib/gitlab/background_migration/populate_fork_networks_range.rb
+++ b/lib/gitlab/background_migration/populate_fork_networks_range.rb
@@ -1,25 +1,55 @@
+# frozen_string_literal: true
+
module Gitlab
module BackgroundMigration
+ # This background migration is going to create all `fork_networks` and
+ # the `fork_network_members` for the roots of fork networks based on the
+ # existing `forked_project_links`.
+ #
+ # When the source of a fork is deleted, we will create the fork with the
+ # target project as the root. This way, when there are forks of the target
+ # project, they will be joined into the same fork network.
+ #
+ # When the `fork_networks` and memberships for the root projects are created
+ # the `CreateForkNetworkMembershipsRange` migration is scheduled. This
+ # migration will create the memberships for all remaining forks-of-forks
class PopulateForkNetworksRange
def perform(start_id, end_id)
- log("Creating fork networks for forked project links: #{start_id} - #{end_id}")
+ create_fork_networks_for_existing_projects(start_id, end_id)
+ create_fork_networks_for_missing_projects(start_id, end_id)
+ create_fork_networks_memberships_for_root_projects(start_id, end_id)
+
+ delay = BackgroundMigration::CreateForkNetworkMembershipsRange::RESCHEDULE_DELAY # rubocop:disable Metrics/LineLength
+ BackgroundMigrationWorker.perform_in(
+ delay, "CreateForkNetworkMembershipsRange", [start_id, end_id]
+ )
+ end
+ def create_fork_networks_for_existing_projects(start_id, end_id)
+ log("Creating fork networks: #{start_id} - #{end_id}")
ActiveRecord::Base.connection.execute <<~INSERT_NETWORKS
INSERT INTO fork_networks (root_project_id)
SELECT DISTINCT forked_project_links.forked_from_project_id
FROM forked_project_links
+ -- Exclude the forks that are not the first level fork of a project
WHERE NOT EXISTS (
SELECT true
FROM forked_project_links inner_links
WHERE inner_links.forked_to_project_id = forked_project_links.forked_from_project_id
)
+
+ /* Exclude the ones that are already created, in case the fork network
+ was already created for another fork of the project.
+ */
AND NOT EXISTS (
SELECT true
FROM fork_networks
WHERE forked_project_links.forked_from_project_id = fork_networks.root_project_id
)
+
+ -- Only create a fork network for a root project that still exists
AND EXISTS (
SELECT true
FROM projects
@@ -27,7 +57,45 @@ module Gitlab
)
AND forked_project_links.id BETWEEN #{start_id} AND #{end_id}
INSERT_NETWORKS
+ end
+
+ def create_fork_networks_for_missing_projects(start_id, end_id)
+ log("Creating fork networks with missing root: #{start_id} - #{end_id}")
+ ActiveRecord::Base.connection.execute <<~INSERT_NETWORKS
+ INSERT INTO fork_networks (root_project_id)
+ SELECT DISTINCT forked_project_links.forked_to_project_id
+
+ FROM forked_project_links
+
+ -- Exclude forks that are not the root forks
+ WHERE NOT EXISTS (
+ SELECT true
+ FROM forked_project_links inner_links
+ WHERE inner_links.forked_to_project_id = forked_project_links.forked_from_project_id
+ )
+
+ /* Exclude the ones that are already created, in case this migration is
+ re-run
+ */
+ AND NOT EXISTS (
+ SELECT true
+ FROM fork_networks
+ WHERE forked_project_links.forked_to_project_id = fork_networks.root_project_id
+ )
+
+ /* Exclude projects for which the project still exists, those are
+ Processed in the previous step of this migration
+ */
+ AND NOT EXISTS (
+ SELECT true
+ FROM projects
+ WHERE projects.id = forked_project_links.forked_from_project_id
+ )
+ AND forked_project_links.id BETWEEN #{start_id} AND #{end_id}
+ INSERT_NETWORKS
+ end
+ def create_fork_networks_memberships_for_root_projects(start_id, end_id)
log("Creating memberships for root projects: #{start_id} - #{end_id}")
ActiveRecord::Base.connection.execute <<~INSERT_ROOT
@@ -36,8 +104,12 @@ module Gitlab
FROM fork_networks
+ /* Joining both on forked_from- and forked_to- so we could create the
+ memberships for forks for which the source was deleted
+ */
INNER JOIN forked_project_links
ON forked_project_links.forked_from_project_id = fork_networks.root_project_id
+ OR forked_project_links.forked_to_project_id = fork_networks.root_project_id
WHERE NOT EXISTS (
SELECT true
@@ -46,9 +118,6 @@ module Gitlab
)
AND forked_project_links.id BETWEEN #{start_id} AND #{end_id}
INSERT_ROOT
-
- delay = BackgroundMigration::CreateForkNetworkMembershipsRange::RESCHEDULE_DELAY
- BackgroundMigrationWorker.perform_in(delay, "CreateForkNetworkMembershipsRange", [start_id, end_id])
end
def log(message)
diff --git a/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id.rb b/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id.rb
new file mode 100644
index 00000000000..dcac355e1b0
--- /dev/null
+++ b/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ class PopulateMergeRequestsLatestMergeRequestDiffId
+ BATCH_SIZE = 1_000
+
+ class MergeRequest < ActiveRecord::Base
+ self.table_name = 'merge_requests'
+
+ include ::EachBatch
+ end
+
+ def perform(start_id, stop_id)
+ update = '
+ latest_merge_request_diff_id = (
+ SELECT MAX(id)
+ FROM merge_request_diffs
+ WHERE merge_requests.id = merge_request_diffs.merge_request_id
+ )'.squish
+
+ MergeRequest
+ .where(id: start_id..stop_id)
+ .where(latest_merge_request_diff_id: nil)
+ .each_batch(of: BATCH_SIZE) do |relation|
+
+ relation.update_all(update)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 033ecd15749..d48ae17aeaf 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -61,9 +61,9 @@ module Gitlab
def import_wiki
return if project.wiki.repository_exists?
- path_with_namespace = "#{project.full_path}.wiki"
+ disk_path = project.wiki.disk_path
import_url = project.import_url.sub(/\.git\z/, ".git/wiki")
- gitlab_shell.import_repository(project.repository_storage_path, path_with_namespace, import_url)
+ gitlab_shell.import_repository(project.repository_storage_path, disk_path, import_url)
rescue StandardError => e
errors << { type: :wiki, errors: e.message }
end
diff --git a/lib/gitlab/changes_list.rb b/lib/gitlab/changes_list.rb
index 5b32fca00a4..9c9e6668e6f 100644
--- a/lib/gitlab/changes_list.rb
+++ b/lib/gitlab/changes_list.rb
@@ -16,6 +16,7 @@ module Gitlab
@changes ||= begin
@raw_changes.map do |change|
next if change.blank?
+
oldrev, newrev, ref = change.strip.split(' ')
{ oldrev: oldrev, newrev: newrev, ref: ref }
end.compact
diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb
index a788fb3fcbc..0bbd60d8ffe 100644
--- a/lib/gitlab/ci/build/artifacts/metadata.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata.rb
@@ -98,6 +98,7 @@ module Gitlab
def read_string(gz)
string_size = read_uint32(gz)
return nil unless string_size
+
gz.read(string_size)
end
diff --git a/lib/gitlab/ci/build/artifacts/metadata/entry.rb b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
index 22941d48edf..5b2f09e03ea 100644
--- a/lib/gitlab/ci/build/artifacts/metadata/entry.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
@@ -43,6 +43,7 @@ module Gitlab
def parent
return nil unless has_parent?
+
self.class.new(@path.to_s.chomp(basename), @entries)
end
@@ -64,6 +65,7 @@ module Gitlab
def directories(opts = {})
return [] unless directory?
+
dirs = children.select(&:directory?)
return dirs unless has_parent? && opts[:parent]
@@ -74,6 +76,7 @@ module Gitlab
def files
return [] unless directory?
+
children.select(&:file?)
end
diff --git a/lib/gitlab/ci/build/image.rb b/lib/gitlab/ci/build/image.rb
index b88b2e36d53..c811f88f483 100644
--- a/lib/gitlab/ci/build/image.rb
+++ b/lib/gitlab/ci/build/image.rb
@@ -8,6 +8,7 @@ module Gitlab
def from_image(job)
image = Gitlab::Ci::Build::Image.new(job.options[:image])
return unless image.valid?
+
image
end
diff --git a/lib/gitlab/ci/config/entry/image.rb b/lib/gitlab/ci/config/entry/image.rb
index 6555c589173..2844be80a84 100644
--- a/lib/gitlab/ci/config/entry/image.rb
+++ b/lib/gitlab/ci/config/entry/image.rb
@@ -37,6 +37,7 @@ module Gitlab
def value
return { name: @config } if string?
return @config if hash?
+
{}
end
end
diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb
index 0159179f0a9..eb606b57667 100644
--- a/lib/gitlab/ci/config/entry/validators.rb
+++ b/lib/gitlab/ci/config/entry/validators.rb
@@ -111,6 +111,7 @@ module Gitlab
def validate_string_or_regexp(value)
return false unless value.is_a?(String)
return validate_regexp(value) if look_like_regexp?(value)
+
true
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb
new file mode 100644
index 00000000000..a126dded1ae
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/build.rb
@@ -0,0 +1,58 @@
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ class Build < Chain::Base
+ include Chain::Helpers
+
+ def perform!
+ @pipeline.assign_attributes(
+ source: @command.source,
+ project: @project,
+ ref: ref,
+ sha: sha,
+ before_sha: before_sha,
+ tag: tag_exists?,
+ trigger_requests: Array(@command.trigger_request),
+ user: @current_user,
+ pipeline_schedule: @command.schedule,
+ protected: protected_ref?
+ )
+
+ @pipeline.set_config_source
+ end
+
+ def break?
+ false
+ end
+
+ private
+
+ def ref
+ @ref ||= Gitlab::Git.ref_name(origin_ref)
+ end
+
+ def sha
+ @project.commit(origin_sha || origin_ref).try(:id)
+ end
+
+ def origin_ref
+ @command.origin_ref
+ end
+
+ def origin_sha
+ @command.checkout_sha || @command.after_sha
+ end
+
+ def before_sha
+ @command.checkout_sha || @command.before_sha || Gitlab::Git::BLANK_SHA
+ end
+
+ def protected_ref?
+ @project.protected_for?(ref)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/sequence.rb b/lib/gitlab/ci/pipeline/chain/sequence.rb
index 015f2988327..e24630656d3 100644
--- a/lib/gitlab/ci/pipeline/chain/sequence.rb
+++ b/lib/gitlab/ci/pipeline/chain/sequence.rb
@@ -5,20 +5,19 @@ module Gitlab
class Sequence
def initialize(pipeline, command, sequence)
@pipeline = pipeline
+ @command = command
+ @sequence = sequence
@completed = []
-
- @sequence = sequence.map do |chain|
- chain.new(pipeline, command)
- end
end
def build!
- @sequence.each do |step|
- step.perform!
+ @sequence.each do |chain|
+ step = chain.new(@pipeline, @command)
+ step.perform!
break if step.break?
- @completed << step
+ @completed.push(step)
end
@pipeline.tap do
diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
index 2479b4a7706..9230894877f 100644
--- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
@@ -3,7 +3,6 @@ module Gitlab
class PlanEventFetcher < BaseEventFetcher
def initialize(*args)
@projections = [mr_diff_table[:id],
- mr_diff_table[:st_commits],
issue_metrics_table[:first_mentioned_in_commit_at]]
super(*args)
@@ -37,12 +36,7 @@ module Gitlab
def first_time_reference_commit(event)
return nil unless event && merge_request_diff_commits
- commits =
- if event['st_commits'].present?
- YAML.load(event['st_commits'])
- else
- merge_request_diff_commits[event['id'].to_i]
- end
+ commits = merge_request_diff_commits[event['id'].to_i]
return nil if commits.blank?
diff --git a/lib/gitlab/daemon.rb b/lib/gitlab/daemon.rb
index f07fd1dfdda..633de9f9776 100644
--- a/lib/gitlab/daemon.rb
+++ b/lib/gitlab/daemon.rb
@@ -2,6 +2,7 @@ module Gitlab
class Daemon
def self.initialize_instance(*args)
raise "#{name} singleton instance already initialized" if @instance
+
@instance = new(*args)
Kernel.at_exit(&@instance.method(:stop))
@instance
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index cd7b4c043da..96922e1a62f 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -50,6 +50,10 @@ module Gitlab
postgresql? && version.to_f >= 9.3
end
+ def self.replication_slots_supported?
+ postgresql? && version.to_f >= 9.4
+ end
+
def self.nulls_last_order(field, direction = 'ASC')
order = "#{field} #{direction}"
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 2c35da8f1aa..3f65bc912de 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -220,6 +220,15 @@ module Gitlab
# column - The name of the column to update.
# value - The value for the column.
#
+ # The `value` argument is typically a literal. To perform a computed
+ # update, an Arel literal can be used instead:
+ #
+ # update_value = Arel.sql('bar * baz')
+ #
+ # update_column_in_batches(:projects, :foo, update_value) do |table, query|
+ # query.where(table[:some_column].eq('hello'))
+ # end
+ #
# Rubocop's Metrics/AbcSize metric is disabled for this method as Rubocop
# determines this method to be too complex while there's no way to make it
# less "complex" without introducing extra methods (which actually will
@@ -694,14 +703,14 @@ into similar problems in the future (e.g. when new tables are created).
# We push multiple jobs at a time to reduce the time spent in
# Sidekiq/Redis operations. We're using this buffer based approach so we
# don't need to run additional queries for every range.
- BackgroundMigrationWorker.perform_bulk(jobs)
+ BackgroundMigrationWorker.bulk_perform_async(jobs)
jobs.clear
end
jobs << [job_class_name, [start_id, end_id]]
end
- BackgroundMigrationWorker.perform_bulk(jobs) unless jobs.empty?
+ BackgroundMigrationWorker.bulk_perform_async(jobs) unless jobs.empty?
end
# Queues background migration jobs for an entire table, batched by ID range.
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb
index 5481024db8e..7e492938eac 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb
@@ -68,6 +68,11 @@ module Gitlab
has_one :route, as: :source
self.table_name = 'projects'
+ HASHED_STORAGE_FEATURES = {
+ repository: 1,
+ attachments: 2
+ }.freeze
+
def repository_storage_path
Gitlab.config.repositories.storages[repository_storage]['path']
end
@@ -76,6 +81,13 @@ module Gitlab
def self.name
'Project'
end
+
+ def hashed_storage?(feature)
+ raise ArgumentError, "Invalid feature" unless HASHED_STORAGE_FEATURES.include?(feature)
+ return false unless respond_to?(:storage_version)
+
+ self.storage_version && self.storage_version >= HASHED_STORAGE_FEATURES[feature]
+ end
end
end
end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
index 75a75f61953..d32616862f0 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
@@ -22,9 +22,11 @@ module Gitlab
end
def move_project_folders(project, old_full_path, new_full_path)
- move_repository(project, old_full_path, new_full_path)
- move_repository(project, "#{old_full_path}.wiki", "#{new_full_path}.wiki")
- move_uploads(old_full_path, new_full_path)
+ unless project.hashed_storage?(:repository)
+ move_repository(project, old_full_path, new_full_path)
+ move_repository(project, "#{old_full_path}.wiki", "#{new_full_path}.wiki")
+ end
+ move_uploads(old_full_path, new_full_path) unless project.hashed_storage?(:attachments)
move_pages(old_full_path, new_full_path)
end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index ea5891a028a..d0cfe2386ca 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -25,6 +25,10 @@ module Gitlab
@repository = repository
@diff_refs = diff_refs
@fallback_diff_refs = fallback_diff_refs
+
+ # Ensure items are collected in the the batch
+ new_blob
+ old_blob
end
def position(position_marker, position_type: :text)
@@ -95,21 +99,15 @@ module Gitlab
end
def new_blob
- return @new_blob if defined?(@new_blob)
-
- sha = new_content_sha
- return @new_blob = nil unless sha
+ return unless new_content_sha
- @new_blob = repository.blob_at(sha, file_path)
+ Blob.lazy(repository.project, new_content_sha, file_path)
end
def old_blob
- return @old_blob if defined?(@old_blob)
-
- sha = old_content_sha
- return @old_blob = nil unless sha
+ return unless old_content_sha
- @old_blob = repository.blob_at(sha, old_path)
+ Blob.lazy(repository.project, old_content_sha, old_path)
end
def content_sha
diff --git a/lib/gitlab/diff/file_collection/base.rb b/lib/gitlab/diff/file_collection/base.rb
index 88ae65cb468..a6007ebf531 100644
--- a/lib/gitlab/diff/file_collection/base.rb
+++ b/lib/gitlab/diff/file_collection/base.rb
@@ -22,10 +22,7 @@ module Gitlab
end
def diff_files
- # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37445
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- @diff_files ||= @diffs.decorate! { |diff| decorate_diff!(diff) }
- end
+ @diff_files ||= @diffs.decorate! { |diff| decorate_diff!(diff) }
end
def diff_file_with_old_path(old_path)
diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb
index 55708d42161..2d7b57120a6 100644
--- a/lib/gitlab/diff/inline_diff.rb
+++ b/lib/gitlab/diff/inline_diff.rb
@@ -102,6 +102,7 @@ module Gitlab
new_char = b[pos]
break if old_char != new_char
+
length += 1
end
diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
index 7dc9cc7c281..8302f30a0a2 100644
--- a/lib/gitlab/diff/parser.rb
+++ b/lib/gitlab/diff/parser.rb
@@ -30,6 +30,7 @@ module Gitlab
line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0
next if line_old <= 1 && line_new <= 1 # top of file
+
yielder << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
line_obj_index += 1
next
diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb
index ccfb908bcca..690b27cde81 100644
--- a/lib/gitlab/diff/position.rb
+++ b/lib/gitlab/diff/position.rb
@@ -125,6 +125,7 @@ module Gitlab
def find_diff_file(repository)
return unless diff_refs.complete?
return unless comparison = diff_refs.compare_in(repository.project)
+
comparison.diffs(paths: paths, expanded: true).diff_files.first
end
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index efc2e46d289..4a9d3e52fae 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -31,16 +31,22 @@ module Gitlab
def check
ensure_patches_dir
- generate_patch(ce_branch, ce_patch_full_path)
+ add_remote('canonical-ce', "#{DEFAULT_CE_PROJECT_URL}.git")
+ generate_patch(branch: ce_branch, patch_path: ce_patch_full_path, remote: 'canonical-ce')
ensure_ee_repo
Dir.chdir(ee_repo_dir) do
step("In the #{ee_repo_dir} directory")
+ add_remote('canonical-ee', EE_REPO_URL)
+
status = catch(:halt_check) do
ce_branch_compat_check!
delete_ee_branches_locally!
ee_branch_presence_check!
+
+ step("Checking out #{ee_branch_found}", %W[git checkout -b #{ee_branch_found} canonical-ee/#{ee_branch_found}])
+ generate_patch(branch: ee_branch_found, patch_path: ee_patch_full_path, remote: 'canonical-ee')
ee_branch_compat_check!
end
@@ -56,6 +62,13 @@ module Gitlab
private
+ def add_remote(name, url)
+ step(
+ "Adding the #{name} remote (#{url})",
+ %W[git remote add #{name} #{url}]
+ )
+ end
+
def ensure_ee_repo
if Dir.exist?(ee_repo_dir)
step("#{ee_repo_dir} already exists")
@@ -71,14 +84,14 @@ module Gitlab
FileUtils.mkdir_p(patches_dir)
end
- def generate_patch(branch, patch_path)
+ def generate_patch(branch:, patch_path:, remote:)
FileUtils.rm(patch_path, force: true)
- find_merge_base_with_master(branch: branch)
+ find_merge_base_with_master(branch: branch, master_remote: remote)
step(
- "Generating the patch against origin/master in #{patch_path}",
- %w[git diff --binary origin/master...HEAD]
+ "Generating the patch against #{remote}/master in #{patch_path}",
+ %W[git diff --binary #{remote}/master...origin/#{branch}]
) do |output, status|
throw(:halt_check, :ko) unless status.zero?
@@ -96,14 +109,14 @@ module Gitlab
end
def ee_branch_presence_check!
- _, status = step("Fetching origin/#{ee_branch_prefix}", %W[git fetch origin #{ee_branch_prefix}])
+ _, status = step("Fetching origin/#{ee_branch_prefix}", %W[git fetch canonical-ee #{ee_branch_prefix}])
if status.zero?
@ee_branch_found = ee_branch_prefix
return
end
- _, status = step("Fetching origin/#{ee_branch_suffix}", %W[git fetch origin #{ee_branch_suffix}])
+ _, status = step("Fetching origin/#{ee_branch_suffix}", %W[git fetch canonical-ee #{ee_branch_suffix}])
if status.zero?
@ee_branch_found = ee_branch_suffix
@@ -116,10 +129,6 @@ module Gitlab
end
def ee_branch_compat_check!
- step("Checking out origin/#{ee_branch_found}", %W[git checkout -b #{ee_branch_found} FETCH_HEAD])
-
- generate_patch(ee_branch_found, ee_patch_full_path)
-
unless check_patch(ee_patch_full_path).zero?
puts
puts ee_branch_doesnt_apply_cleanly_msg
@@ -133,8 +142,7 @@ module Gitlab
def check_patch(patch_path)
step("Checking out master", %w[git checkout master])
- step("Resetting to latest master", %w[git reset --hard origin/master])
- step("Fetching CE/#{ce_branch}", %W[git fetch #{ce_repo_url} #{ce_branch}])
+ step("Resetting to latest master", %w[git reset --hard canonical-ee/master])
step(
"Checking if #{patch_path} applies cleanly to EE/master",
# Don't use --check here because it can result in a 0-exit status even
@@ -171,10 +179,10 @@ module Gitlab
command(%W[git branch --delete --force #{ee_branch_suffix}])
end
- def merge_base_found?
+ def merge_base_found?(master_remote:, branch:)
step(
- "Finding merge base with master",
- %w[git merge-base origin/master HEAD]
+ "Finding merge base with #{master_remote}/master",
+ %W[git merge-base #{master_remote}/master origin/#{branch}]
) do |output, status|
if status.zero?
puts "Merge base was found: #{output}"
@@ -183,7 +191,7 @@ module Gitlab
end
end
- def find_merge_base_with_master(branch:)
+ def find_merge_base_with_master(branch:, master_remote:)
# Start with (Math.exp(3).to_i = 20) until (Math.exp(6).to_i = 403)
# In total we go (20 + 54 + 148 + 403 = 625) commits deeper
depth = 20
@@ -192,19 +200,19 @@ module Gitlab
depth += Math.exp(factor).to_i
# Repository is initially cloned with a depth of 20 so we need to fetch
# deeper in the case the branch has more than 20 commits on top of master
- fetch(branch: branch, depth: depth)
- fetch(branch: 'master', depth: depth, remote: DEFAULT_CE_PROJECT_URL)
+ fetch(branch: branch, depth: depth, remote: 'origin')
+ fetch(branch: 'master', depth: depth, remote: master_remote)
- merge_base_found?
+ merge_base_found?(master_remote: master_remote, branch: branch)
end
- raise "\n#{branch} is too far behind master, please rebase it!\n" unless success
+ raise "\n#{branch} is too far behind #{master_remote}/master, please rebase it!\n" unless success
end
def fetch(branch:, depth:, remote: 'origin')
step(
"Fetching deeper...",
- %W[git fetch --depth=#{depth} --prune #{remote} +refs/heads/#{branch}:refs/remotes/origin/#{branch}]
+ %W[git fetch --depth=#{depth} --prune #{remote} +refs/heads/#{branch}:refs/remotes/#{remote}/#{branch}]
) do |output, status|
raise "Fetch failed: #{output}" unless status.zero?
end
@@ -304,8 +312,8 @@ module Gitlab
1. Create a new branch from master and cherry-pick your CE commits
# In the EE repo
- $ git fetch origin
- $ git checkout -b #{ee_branch_prefix} origin/master
+ $ git fetch #{EE_REPO_URL} master
+ $ git checkout -b #{ee_branch_prefix} FETCH_HEAD
$ git fetch #{ce_repo_url} #{ce_branch}
$ git cherry-pick SHA # Repeat for all the commits you want to pick
@@ -314,10 +322,9 @@ module Gitlab
2. Apply your branch's patch to EE
# In the EE repo
- $ git fetch origin master
- $ git checkout -b #{ee_branch_prefix} origin/master
- $ wget #{patch_url}
- $ git apply --3way #{ce_patch_name}
+ $ git fetch #{EE_REPO_URL} master
+ $ git checkout -b #{ee_branch_prefix} FETCH_HEAD
+ $ wget #{patch_url} && git apply --3way #{ce_patch_name}
At this point you might have conflicts such as:
diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb
index b07c68d1498..e08b5be8984 100644
--- a/lib/gitlab/email/handler.rb
+++ b/lib/gitlab/email/handler.rb
@@ -1,3 +1,4 @@
+require 'gitlab/email/handler/create_merge_request_handler'
require 'gitlab/email/handler/create_note_handler'
require 'gitlab/email/handler/create_issue_handler'
require 'gitlab/email/handler/unsubscribe_handler'
@@ -8,6 +9,7 @@ module Gitlab
HANDLERS = [
UnsubscribeHandler,
CreateNoteHandler,
+ CreateMergeRequestHandler,
CreateIssueHandler
].freeze
diff --git a/lib/gitlab/email/handler/create_merge_request_handler.rb b/lib/gitlab/email/handler/create_merge_request_handler.rb
new file mode 100644
index 00000000000..c63666b98c1
--- /dev/null
+++ b/lib/gitlab/email/handler/create_merge_request_handler.rb
@@ -0,0 +1,67 @@
+require 'gitlab/email/handler/base_handler'
+require 'gitlab/email/handler/reply_processing'
+
+module Gitlab
+ module Email
+ module Handler
+ class CreateMergeRequestHandler < BaseHandler
+ include ReplyProcessing
+ attr_reader :project_path, :incoming_email_token
+
+ def initialize(mail, mail_key)
+ super(mail, mail_key)
+ if m = /\A([^\+]*)\+merge-request\+(.*)/.match(mail_key.to_s)
+ @project_path, @incoming_email_token = m.captures
+ end
+ end
+
+ def can_handle?
+ @project_path && @incoming_email_token
+ end
+
+ def execute
+ raise ProjectNotFound unless project
+
+ validate_permission!(:create_merge_request)
+
+ verify_record!(
+ record: create_merge_request,
+ invalid_exception: InvalidMergeRequestError,
+ record_name: 'merge_request')
+ end
+
+ def author
+ @author ||= User.find_by(incoming_email_token: incoming_email_token)
+ end
+
+ def project
+ @project ||= Project.find_by_full_path(project_path)
+ end
+
+ def metrics_params
+ super.merge(project: project&.full_path)
+ end
+
+ private
+
+ def create_merge_request
+ merge_request = MergeRequests::BuildService.new(project, author, merge_request_params).execute
+
+ if merge_request.errors.any?
+ merge_request
+ else
+ MergeRequests::CreateService.new(project, author).create(merge_request)
+ end
+ end
+
+ def merge_request_params
+ {
+ source_project_id: project.id,
+ source_branch: mail.subject,
+ target_project_id: project.id
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/handler/unsubscribe_handler.rb b/lib/gitlab/email/handler/unsubscribe_handler.rb
index 5894384da5d..ea80e21532e 100644
--- a/lib/gitlab/email/handler/unsubscribe_handler.rb
+++ b/lib/gitlab/email/handler/unsubscribe_handler.rb
@@ -16,6 +16,7 @@ module Gitlab
noteable = sent_notification.noteable
raise NoteableNotFoundError unless noteable
+
noteable.unsubscribe(sent_notification.recipient)
end
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index c8f4591d060..d8c594ad0e7 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -13,8 +13,10 @@ module Gitlab
UserBlockedError = Class.new(ProcessingError)
UserNotAuthorizedError = Class.new(ProcessingError)
NoteableNotFoundError = Class.new(ProcessingError)
- InvalidNoteError = Class.new(ProcessingError)
- InvalidIssueError = Class.new(ProcessingError)
+ InvalidRecordError = Class.new(ProcessingError)
+ InvalidNoteError = Class.new(InvalidRecordError)
+ InvalidIssueError = Class.new(InvalidRecordError)
+ InvalidMergeRequestError = Class.new(InvalidRecordError)
UnknownIncomingEmail = Class.new(ProcessingError)
class Receiver
diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb
index 99dfee3dd9b..582028493e9 100644
--- a/lib/gitlab/encoding_helper.rb
+++ b/lib/gitlab/encoding_helper.rb
@@ -17,6 +17,10 @@ module Gitlab
return nil unless message.respond_to?(:force_encoding)
return message if message.encoding == Encoding::UTF_8 && message.valid_encoding?
+ if message.respond_to?(:frozen?) && message.frozen?
+ message = message.dup
+ end
+
message.force_encoding("UTF-8")
return message if message.valid_encoding?
diff --git a/lib/gitlab/fogbugz_import/client.rb b/lib/gitlab/fogbugz_import/client.rb
index 2152182b37f..acb000e3e23 100644
--- a/lib/gitlab/fogbugz_import/client.rb
+++ b/lib/gitlab/fogbugz_import/client.rb
@@ -45,6 +45,7 @@ module Gitlab
project_name = repo(project_id).name
res = @api.command(:search, q: "project:'#{project_name}'", cols: 'ixPersonAssignedTo,ixPersonOpenedBy,ixPersonClosedBy,sStatus,sPriority,sCategory,fOpen,sTitle,sLatestTextSummary,dtOpened,dtClosed,dtResolved,dtLastUpdated,events')
return [] unless res['cases']['count'].to_i > 0
+
res['cases']['case']
end
diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb
index 3dcee681c72..5e426b13ade 100644
--- a/lib/gitlab/fogbugz_import/importer.rb
+++ b/lib/gitlab/fogbugz_import/importer.rb
@@ -18,6 +18,7 @@ module Gitlab
def execute
return true unless repo.valid?
+
client = Gitlab::FogbugzImport::Client.new(token: fb_session[:token], uri: fb_session[:uri])
@cases = client.cases(@repo.id.to_i)
@@ -206,6 +207,7 @@ module Gitlab
def format_content(raw_content)
return raw_content if raw_content.nil?
+
linkify_issues(escape_for_markdown(raw_content))
end
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index cc6c7609ec7..228d97a87ab 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -49,6 +49,7 @@ module Gitlab
# Keep in mind that this method may allocate a lot of memory. It is up
# to the caller to limit the number of blobs and blob_size_limit.
#
+ # Gitaly migration issue: https://gitlab.com/gitlab-org/gitaly/issues/798
def batch(repository, blob_references, blob_size_limit: nil)
blob_size_limit ||= MAX_DATA_DISPLAY_SIZE
blob_references.map do |sha, path|
@@ -102,6 +103,7 @@ module Gitlab
if path_arr.size > 1
return nil unless entry[:type] == :tree
+
path_arr.shift
find_entry_by_path(repository, entry[:oid], path_arr.join('/'))
else
@@ -178,6 +180,8 @@ module Gitlab
)
end
end
+ rescue Rugged::ReferenceError
+ nil
end
def rugged_raw(repository, sha, limit:)
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index d5518814483..8900e2d7afe 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -213,11 +213,17 @@ module Gitlab
end
def shas_with_signatures(repository, shas)
- shas.select do |sha|
- begin
- Rugged::Commit.extract_signature(repository.rugged, sha)
- rescue Rugged::OdbError
- false
+ GitalyClient.migrate(:filter_shas_with_signatures) do |is_enabled|
+ if is_enabled
+ Gitlab::GitalyClient::CommitService.new(repository).filter_shas_with_signatures(shas)
+ else
+ shas.select do |sha|
+ begin
+ Rugged::Commit.extract_signature(repository.rugged, sha)
+ rescue Rugged::OdbError
+ false
+ end
+ end
end
end
end
@@ -418,6 +424,20 @@ module Gitlab
parent_ids.size > 1
end
+ def to_gitaly_commit
+ return raw_commit if raw_commit.is_a?(Gitaly::GitCommit)
+
+ message_split = raw_commit.message.split("\n", 2)
+ Gitaly::GitCommit.new(
+ id: raw_commit.oid,
+ subject: message_split[0] ? message_split[0].chomp.b : "",
+ body: raw_commit.message.b,
+ parent_ids: raw_commit.parent_ids,
+ author: gitaly_commit_author_from_rugged(raw_commit.author),
+ committer: gitaly_commit_author_from_rugged(raw_commit.committer)
+ )
+ end
+
private
def init_from_hash(hash)
@@ -463,6 +483,14 @@ module Gitlab
def serialize_keys
SERIALIZE_KEYS
end
+
+ def gitaly_commit_author_from_rugged(author_or_committer)
+ Gitaly::CommitAuthor.new(
+ name: author_or_committer[:name].b,
+ email: author_or_committer[:email].b,
+ date: Google::Protobuf::Timestamp.new(seconds: author_or_committer[:time].to_i)
+ )
+ end
end
end
end
diff --git a/lib/gitlab/git/conflict/file.rb b/lib/gitlab/git/conflict/file.rb
index fc1595f1faf..b2a625e08fa 100644
--- a/lib/gitlab/git/conflict/file.rb
+++ b/lib/gitlab/git/conflict/file.rb
@@ -2,7 +2,7 @@ module Gitlab
module Git
module Conflict
class File
- attr_reader :content, :their_path, :our_path, :our_mode, :repository
+ attr_reader :content, :their_path, :our_path, :our_mode, :repository, :commit_oid
def initialize(repository, commit_oid, conflict, content)
@repository = repository
diff --git a/lib/gitlab/git/conflict/resolver.rb b/lib/gitlab/git/conflict/resolver.rb
index df509c5f4ce..de8cce41b6d 100644
--- a/lib/gitlab/git/conflict/resolver.rb
+++ b/lib/gitlab/git/conflict/resolver.rb
@@ -75,7 +75,7 @@ module Gitlab
resolved_lines = file.resolve_lines(params[:sections])
new_file = resolved_lines.map { |line| line[:full_line] }.join("\n")
- new_file << "\n" if file.our_blob.data.ends_with?("\n")
+ new_file << "\n" if file.our_blob.data.end_with?("\n")
elsif params[:content]
new_file = file.resolve_content(params[:content])
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index cfb88a0c12b..eab04bcac65 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -18,6 +18,8 @@ module Gitlab
GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE
].freeze
SEARCH_CONTEXT_LINES = 3
+ REBASE_WORKTREE_PREFIX = 'rebase'.freeze
+ SQUASH_WORKTREE_PREFIX = 'squash'.freeze
NoRepository = Class.new(StandardError)
InvalidBlobName = Class.new(StandardError)
@@ -304,7 +306,13 @@ module Gitlab
end
def delete_all_refs_except(prefixes)
- delete_refs(*all_ref_names_except(prefixes))
+ gitaly_migrate(:ref_delete_refs) do |is_enabled|
+ if is_enabled
+ gitaly_ref_client.delete_refs(except_with_prefixes: prefixes)
+ else
+ delete_refs(*all_ref_names_except(prefixes))
+ end
+ end
end
# Returns an Array of all ref names, except when it's matching pattern
@@ -499,7 +507,7 @@ module Gitlab
# Counts the amount of commits between `from` and `to`.
def count_commits_between(from, to)
- Commit.between(self, from, to).size
+ count_commits(ref: "#{from}..#{to}")
end
# Returns the SHA of the most recent common ancestor of +from+ and +to+
@@ -803,44 +811,24 @@ module Gitlab
end
def cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
- OperationService.new(user, self).with_branch(
- branch_name,
- start_branch_name: start_branch_name,
- start_repository: start_repository
- ) do |start_commit|
-
- Gitlab::Git.check_namespace!(commit, start_repository)
-
- cherry_pick_tree_id = check_cherry_pick_content(commit, start_commit.sha)
- raise CreateTreeError unless cherry_pick_tree_id
-
- committer = user_to_committer(user)
+ gitaly_migrate(:cherry_pick) do |is_enabled|
+ args = {
+ user: user,
+ commit: commit,
+ branch_name: branch_name,
+ message: message,
+ start_branch_name: start_branch_name,
+ start_repository: start_repository
+ }
- create_commit(message: message,
- author: {
- email: commit.author_email,
- name: commit.author_name,
- time: commit.authored_date
- },
- committer: committer,
- tree: cherry_pick_tree_id,
- parents: [start_commit.sha])
+ if is_enabled
+ gitaly_operations_client.user_cherry_pick(args)
+ else
+ rugged_cherry_pick(args)
+ end
end
end
- def check_cherry_pick_content(target_commit, source_sha)
- args = [target_commit.sha, source_sha]
- args << 1 if target_commit.merge_commit?
-
- cherry_pick_index = rugged.cherrypick_commit(*args)
- return false if cherry_pick_index.conflicts?
-
- tree_id = cherry_pick_index.write_tree(rugged)
- return false unless diff_exists?(source_sha, tree_id)
-
- tree_id
- end
-
def diff_exists?(sha1, sha2)
rugged.diff(sha1, sha2).size > 0
end
@@ -984,6 +972,10 @@ module Gitlab
@attributes.attributes(path)
end
+ def gitattribute(path, name)
+ attributes(path)[name]
+ end
+
def languages(ref = nil)
Gitlab::GitalyClient.migrate(:commit_languages) do |is_enabled|
if is_enabled
@@ -1036,9 +1028,15 @@ module Gitlab
end
def with_repo_tmp_commit(start_repository, start_branch_name, sha)
+ source_ref = start_branch_name
+
+ unless Gitlab::Git.branch_ref?(source_ref)
+ source_ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_ref}"
+ end
+
tmp_ref = fetch_ref(
start_repository,
- source_ref: "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}",
+ source_ref: source_ref,
target_ref: "refs/tmp/#{SecureRandom.hex}"
)
@@ -1048,12 +1046,11 @@ module Gitlab
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)
- true
+ Gitlab::GitalyClient.migrate(:fetch_source_branch) do |is_enabled|
+ if is_enabled
+ gitaly_repository_client.fetch_source_branch(source_repository, source_branch, local_ref)
else
- false
+ rugged_fetch_source_branch(source_repository, source_branch, local_ref)
end
end
end
@@ -1075,13 +1072,8 @@ module Gitlab
raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
- command = [Gitlab.config.git.bin_path] + %w[update-ref --stdin -z]
input = "update #{ref_path}\x00#{ref}\x00\x00"
- output, status = circuit_breaker.perform do
- popen(command, path) { |stdin| stdin.write(input) }
- end
-
- raise GitError, output unless status.zero?
+ run_git!(%w[update-ref --stdin -z]) { |stdin| stdin.write(input) }
end
def fetch_ref(source_repository, source_ref:, target_ref:)
@@ -1103,12 +1095,22 @@ module Gitlab
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
- def run_git(args, env: {})
+ def run_git(args, chdir: path, env: {}, nice: false, &block)
+ cmd = [Gitlab.config.git.bin_path, *args]
+ cmd.unshift("nice") if nice
circuit_breaker.perform do
- popen([Gitlab.config.git.bin_path, *args], path, env)
+ popen(cmd, chdir, env, &block)
end
end
+ def run_git!(args, chdir: path, env: {}, nice: false, &block)
+ output, status = run_git(args, chdir: chdir, env: env, nice: nice, &block)
+
+ raise GitError, output unless status.zero?
+
+ output
+ end
+
# Refactoring aid; allows us to copy code from app/models/repository.rb
def run_git_with_timeout(args, timeout, env: {})
circuit_breaker.perform do
@@ -1141,16 +1143,23 @@ module Gitlab
@has_visible_content = has_local_branches?
end
- def fetch(remote = 'origin')
- args = %W(#{Gitlab.config.git.bin_path} fetch #{remote})
-
- popen(args, @path).last.zero?
+ # Like all public `Gitlab::Git::Repository` methods, this method is part
+ # of `Repository`'s interface through `method_missing`.
+ # `Repository` has its own `fetch_remote` which uses `gitlab-shell` and
+ # takes some extra attributes, so we qualify this method name to prevent confusion.
+ def fetch_remote_without_shell(remote = 'origin')
+ run_git(['fetch', remote]).last.zero?
end
def blob_at(sha, path)
Gitlab::Git::Blob.find(self, sha, path) unless Gitlab::Git.blank_ref?(sha)
end
+ # Items should be of format [[commit_id, path], [commit_id1, path1]]
+ def batch_blobs(items, blob_size_limit: nil)
+ Gitlab::Git::Blob.batch(self, items, blob_size_limit: blob_size_limit)
+ end
+
def commit_index(user, branch_name, index, options)
committer = user_to_committer(user)
@@ -1165,6 +1174,70 @@ module Gitlab
end
end
+ def fsck
+ output, status = run_git(%W[--git-dir=#{path} fsck], nice: true)
+
+ raise GitError.new("Could not fsck repository:\n#{output}") unless status.zero?
+ end
+
+ def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
+ rebase_path = worktree_path(REBASE_WORKTREE_PREFIX, rebase_id)
+ env = git_env_for_user(user)
+
+ with_worktree(rebase_path, branch, env: env) do
+ run_git!(
+ %W(pull --rebase #{remote_repository.path} #{remote_branch}),
+ chdir: rebase_path, env: env
+ )
+
+ rebase_sha = run_git!(%w(rev-parse HEAD), chdir: rebase_path, env: env).strip
+
+ Gitlab::Git::OperationService.new(user, self)
+ .update_branch(branch, rebase_sha, branch_sha)
+
+ rebase_sha
+ end
+ end
+
+ def rebase_in_progress?(rebase_id)
+ fresh_worktree?(worktree_path(REBASE_WORKTREE_PREFIX, rebase_id))
+ end
+
+ def squash(user, squash_id, branch:, start_sha:, end_sha:, author:, message:)
+ squash_path = worktree_path(SQUASH_WORKTREE_PREFIX, squash_id)
+ env = git_env_for_user(user).merge(
+ 'GIT_AUTHOR_NAME' => author.name,
+ 'GIT_AUTHOR_EMAIL' => author.email
+ )
+ diff_range = "#{start_sha}...#{end_sha}"
+ diff_files = run_git!(
+ %W(diff --name-only --diff-filter=a --binary #{diff_range})
+ ).chomp
+
+ with_worktree(squash_path, branch, sparse_checkout_files: diff_files, env: env) do
+ # Apply diff of the `diff_range` to the worktree
+ diff = run_git!(%W(diff --binary #{diff_range}))
+ run_git!(%w(apply --index), chdir: squash_path, env: env) do |stdin|
+ stdin.write(diff)
+ end
+
+ # Commit the `diff_range` diff
+ run_git!(%W(commit --no-verify --message #{message}), chdir: squash_path, env: env)
+
+ # Return the squash sha. May print a warning for ambiguous refs, but
+ # we can ignore that with `--quiet` and just take the SHA, if present.
+ # HEAD here always refers to the current HEAD commit, even if there is
+ # another ref called HEAD.
+ run_git!(
+ %w(rev-parse --quiet --verify HEAD), chdir: squash_path, env: env
+ ).chomp
+ end
+ end
+
+ def squash_in_progress?(squash_id)
+ fresh_worktree?(worktree_path(SQUASH_WORKTREE_PREFIX, squash_id))
+ end
+
def gitaly_repository
Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository)
end
@@ -1201,6 +1274,86 @@ module Gitlab
private
+ def fresh_worktree?(path)
+ File.exist?(path) && !clean_stuck_worktree(path)
+ end
+
+ def with_worktree(worktree_path, branch, sparse_checkout_files: nil, env:)
+ base_args = %w(worktree add --detach)
+
+ # Note that we _don't_ want to test for `.present?` here: If the caller
+ # passes an non nil empty value it means it still wants sparse checkout
+ # but just isn't interested in any file, perhaps because it wants to
+ # checkout files in by a changeset but that changeset only adds files.
+ if sparse_checkout_files
+ # Create worktree without checking out
+ run_git!(base_args + ['--no-checkout', worktree_path], env: env)
+ worktree_git_path = run_git!(%w(rev-parse --git-dir), chdir: worktree_path)
+
+ configure_sparse_checkout(worktree_git_path, sparse_checkout_files)
+
+ # After sparse checkout configuration, checkout `branch` in worktree
+ run_git!(%W(checkout --detach #{branch}), chdir: worktree_path, env: env)
+ else
+ # Create worktree and checkout `branch` in it
+ run_git!(base_args + [worktree_path, branch], env: env)
+ end
+
+ yield
+ ensure
+ FileUtils.rm_rf(worktree_path) if File.exist?(worktree_path)
+ FileUtils.rm_rf(worktree_git_path) if worktree_git_path && File.exist?(worktree_git_path)
+ end
+
+ def clean_stuck_worktree(path)
+ return false unless File.mtime(path) < 15.minutes.ago
+
+ FileUtils.rm_rf(path)
+ true
+ end
+
+ # Adding a worktree means checking out the repository. For large repos,
+ # this can be very expensive, so set up sparse checkout for the worktree
+ # to only check out the files we're interested in.
+ def configure_sparse_checkout(worktree_git_path, files)
+ run_git!(%w(config core.sparseCheckout true))
+
+ return if files.empty?
+
+ worktree_info_path = File.join(worktree_git_path, 'info')
+ FileUtils.mkdir_p(worktree_info_path)
+ File.write(File.join(worktree_info_path, 'sparse-checkout'), files)
+ end
+
+ def rugged_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)
+ true
+ else
+ false
+ end
+ end
+ end
+
+ def worktree_path(prefix, id)
+ id = id.to_s
+ raise ArgumentError, "worktree id can't be empty" unless id.present?
+ raise ArgumentError, "worktree id can't contain slashes " if id.include?("/")
+
+ File.join(path, 'gitlab-worktree', "#{prefix}-#{id}")
+ end
+
+ def git_env_for_user(user)
+ {
+ 'GIT_COMMITTER_NAME' => user.name,
+ 'GIT_COMMITTER_EMAIL' => user.email,
+ 'GL_ID' => Gitlab::GlId.gl_id(user),
+ 'GL_PROTOCOL' => Gitlab::Git::Hook::GL_PROTOCOL,
+ 'GL_REPOSITORY' => gl_repository
+ }
+ end
+
# Gitaly note: JV: Trying to get rid of the 'filter' option so we can implement this with 'git'.
def branches_filter(filter: nil, sort_by: nil)
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37464
@@ -1218,11 +1371,25 @@ module Gitlab
sort_branches(branches, sort_by)
end
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/695
def git_merged_branch_names(branch_names = [])
- lines = run_git(['branch', '--merged', root_ref] + branch_names)
- .first.lines
+ return [] unless root_ref
+
+ root_sha = find_branch(root_ref)&.target
+
+ return [] unless root_sha
+
+ git_arguments =
+ %W[branch --merged #{root_sha}
+ --format=%(refname:short)\ %(objectname)] + branch_names
+
+ lines = run_git(git_arguments).first.lines
+
+ lines.each_with_object([]) do |line, branches|
+ name, sha = line.strip.split(' ', 2)
- lines.map(&:strip)
+ branches << name if sha != root_sha
+ end
end
def log_using_shell?(options)
@@ -1376,6 +1543,7 @@ module Gitlab
end
return nil unless tmp_entry.type == :tree
+
tmp_entry = tmp_entry[dir]
end
end
@@ -1496,6 +1664,7 @@ module Gitlab
# Ref names must start with `refs/`.
def rugged_ref_exists?(ref_name)
raise ArgumentError, 'invalid refname' unless ref_name.start_with?('refs/')
+
rugged.references.exist?(ref_name)
rescue Rugged::ReferenceError
false
@@ -1562,6 +1731,7 @@ module Gitlab
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
rescue Rugged::ReferenceError => e
raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ /'refs\/heads\/#{ref}'/
+
raise InvalidRef.new("Invalid reference #{start_point}")
end
@@ -1615,6 +1785,45 @@ module Gitlab
raise InvalidRef, ex
end
+ def rugged_cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
+ OperationService.new(user, self).with_branch(
+ branch_name,
+ start_branch_name: start_branch_name,
+ start_repository: start_repository
+ ) do |start_commit|
+
+ Gitlab::Git.check_namespace!(commit, start_repository)
+
+ cherry_pick_tree_id = check_cherry_pick_content(commit, start_commit.sha)
+ raise CreateTreeError unless cherry_pick_tree_id
+
+ committer = user_to_committer(user)
+
+ create_commit(message: message,
+ author: {
+ email: commit.author_email,
+ name: commit.author_name,
+ time: commit.authored_date
+ },
+ committer: committer,
+ tree: cherry_pick_tree_id,
+ parents: [start_commit.sha])
+ end
+ end
+
+ def check_cherry_pick_content(target_commit, source_sha)
+ args = [target_commit.sha, source_sha]
+ args << 1 if target_commit.merge_commit?
+
+ cherry_pick_index = rugged.cherrypick_commit(*args)
+ return false if cherry_pick_index.conflicts?
+
+ tree_id = cherry_pick_index.write_tree(rugged)
+ return false unless diff_exists?(source_sha, tree_id)
+
+ tree_id
+ end
+
def local_fetch_ref(source_path, source_ref:, target_ref:)
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
run_git(args)
diff --git a/lib/gitlab/git/repository_mirroring.rb b/lib/gitlab/git/repository_mirroring.rb
index 637e7a0659c..392bef69e80 100644
--- a/lib/gitlab/git/repository_mirroring.rb
+++ b/lib/gitlab/git/repository_mirroring.rb
@@ -1,38 +1,47 @@
module Gitlab
module Git
module RepositoryMirroring
- IMPORT_HEAD_REFS = '+refs/heads/*:refs/heads/*'.freeze
- IMPORT_TAG_REFS = '+refs/tags/*:refs/tags/*'.freeze
- MIRROR_REMOTE = 'mirror'.freeze
+ REFMAPS = {
+ # With `:all_refs`, the repository is equivalent to the result of `git clone --mirror`
+ all_refs: '+refs/*:refs/*',
+ heads: '+refs/heads/*:refs/heads/*',
+ tags: '+refs/tags/*:refs/tags/*'
+ }.freeze
RemoteError = Class.new(StandardError)
- def set_remote_as_mirror(remote_name)
- # This is used to define repository as equivalent as "git clone --mirror"
- rugged.config["remote.#{remote_name}.fetch"] = 'refs/*:refs/*'
- rugged.config["remote.#{remote_name}.mirror"] = true
- rugged.config["remote.#{remote_name}.prune"] = true
- end
-
- def set_import_remote_as_mirror(remote_name)
- # Add first fetch with Rugged so it does not create its own.
- rugged.config["remote.#{remote_name}.fetch"] = IMPORT_HEAD_REFS
-
- add_remote_fetch_config(remote_name, IMPORT_TAG_REFS)
+ def set_remote_as_mirror(remote_name, refmap: :all_refs)
+ set_remote_refmap(remote_name, refmap)
rugged.config["remote.#{remote_name}.mirror"] = true
rugged.config["remote.#{remote_name}.prune"] = true
end
- def add_remote_fetch_config(remote_name, refspec)
- run_git(%W[config --add remote.#{remote_name}.fetch #{refspec}])
+ def set_remote_refmap(remote_name, refmap)
+ Array(refmap).each_with_index do |refspec, i|
+ refspec = REFMAPS[refspec] || refspec
+
+ # We need multiple `fetch` entries, but Rugged only allows replacing a config, not adding to it.
+ # To make sure we start from scratch, we set the first using rugged, and use `git` for any others
+ if i == 0
+ rugged.config["remote.#{remote_name}.fetch"] = refspec
+ else
+ run_git(%W[config --add remote.#{remote_name}.fetch #{refspec}])
+ end
+ end
end
- def fetch_mirror(url)
- add_remote(MIRROR_REMOTE, url)
- set_remote_as_mirror(MIRROR_REMOTE)
- fetch(MIRROR_REMOTE)
- remove_remote(MIRROR_REMOTE)
+ # Like all_refs public `Gitlab::Git::Repository` methods, this method is part
+ # of `Repository`'s interface through `method_missing`.
+ # `Repository` has its own `fetch_as_mirror` which uses `gitlab-shell` and
+ # takes some extra attributes, so we qualify this method name to prevent confusion.
+ def fetch_as_mirror_without_shell(url)
+ remote_name = "tmp-#{SecureRandom.hex}"
+ add_remote(remote_name, url)
+ set_remote_as_mirror(remote_name)
+ fetch_remote_without_shell(remote_name)
+ ensure
+ remove_remote(remote_name) if remote_name
end
def remote_tags(remote)
@@ -78,7 +87,7 @@ module Gitlab
def list_remote_tags(remote)
tag_list, exit_code, error = nil
- cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{full_path} ls-remote --tags #{remote})
+ cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path} ls-remote --tags #{remote})
Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thr|
tag_list = stdout.read
@@ -88,7 +97,7 @@ module Gitlab
raise RemoteError, error unless exit_code.zero?
- tag_list.split('\n')
+ tag_list.split("\n")
end
end
end
diff --git a/lib/gitlab/git/storage.rb b/lib/gitlab/git/storage.rb
index 99518c9b1e4..5933312b0b5 100644
--- a/lib/gitlab/git/storage.rb
+++ b/lib/gitlab/git/storage.rb
@@ -15,6 +15,7 @@ module Gitlab
Failing = Class.new(Inaccessible)
REDIS_KEY_PREFIX = 'storage_accessible:'.freeze
+ REDIS_KNOWN_KEYS = "#{REDIS_KEY_PREFIX}known_keys_set".freeze
def self.redis
Gitlab::Redis::SharedState
diff --git a/lib/gitlab/git/storage/circuit_breaker.rb b/lib/gitlab/git/storage/circuit_breaker.rb
index be7598ef011..4328c0ea29b 100644
--- a/lib/gitlab/git/storage/circuit_breaker.rb
+++ b/lib/gitlab/git/storage/circuit_breaker.rb
@@ -13,10 +13,8 @@ module Gitlab
delegate :last_failure, :failure_count, to: :failure_info
def self.reset_all!
- pattern = "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}*"
-
Gitlab::Git::Storage.redis.with do |redis|
- all_storage_keys = redis.scan_each(match: pattern).to_a
+ all_storage_keys = redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
redis.del(*all_storage_keys) unless all_storage_keys.empty?
end
@@ -135,23 +133,29 @@ module Gitlab
redis.hset(cache_key, :last_failure, last_failure.to_i)
redis.hincrby(cache_key, :failure_count, 1)
redis.expire(cache_key, failure_reset_time)
+ maintain_known_keys(redis)
end
end
end
def track_storage_accessible
- return if no_failures?
-
@failure_info = FailureInfo.new(nil, 0)
Gitlab::Git::Storage.redis.with do |redis|
redis.pipelined do
redis.hset(cache_key, :last_failure, nil)
redis.hset(cache_key, :failure_count, 0)
+ maintain_known_keys(redis)
end
end
end
+ def maintain_known_keys(redis)
+ expire_time = Time.now.to_i + failure_reset_time
+ redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, expire_time, cache_key)
+ redis.zremrangebyscore(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, '-inf', Time.now.to_i)
+ end
+
def get_failure_info
last_failure, failure_count = Gitlab::Git::Storage.redis.with do |redis|
redis.hmget(cache_key, :last_failure, :failure_count)
diff --git a/lib/gitlab/git/storage/health.rb b/lib/gitlab/git/storage/health.rb
index 7049772fe3b..90bbe85fd37 100644
--- a/lib/gitlab/git/storage/health.rb
+++ b/lib/gitlab/git/storage/health.rb
@@ -4,8 +4,8 @@ module Gitlab
class Health
attr_reader :storage_name, :info
- def self.pattern_for_storage(storage_name)
- "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage_name}:*"
+ def self.prefix_for_storage(storage_name)
+ "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage_name}:"
end
def self.for_all_storages
@@ -25,26 +25,15 @@ module Gitlab
private_class_method def self.all_keys_for_storages(storage_names, redis)
keys_per_storage = {}
+ all_keys = redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
- redis.pipelined do
- storage_names.each do |storage_name|
- pattern = pattern_for_storage(storage_name)
- matched_keys = redis.scan_each(match: pattern)
+ storage_names.each do |storage_name|
+ prefix = prefix_for_storage(storage_name)
- keys_per_storage[storage_name] = matched_keys
- end
+ keys_per_storage[storage_name] = all_keys.select { |key| key.starts_with?(prefix) }
end
- # We need to make sure each lazy-loaded `Enumerator` for matched keys
- # is loaded into an array.
- #
- # Otherwise it would be loaded in the second `Redis#pipelined` block
- # within `.load_for_keys`. In this pipelined call, the active
- # Redis-client changes again, so the values would not be available
- # until the end of that pipelined-block.
- keys_per_storage.each do |storage_name, key_future|
- keys_per_storage[storage_name] = key_future.to_a
- end
+ keys_per_storage
end
private_class_method def self.load_for_keys(keys_per_storage, redis)
diff --git a/lib/gitlab/git/user.rb b/lib/gitlab/git/user.rb
index e6b61417de1..e573cd0e143 100644
--- a/lib/gitlab/git/user.rb
+++ b/lib/gitlab/git/user.rb
@@ -8,7 +8,12 @@ module Gitlab
end
def self.from_gitaly(gitaly_user)
- new(gitaly_user.gl_username, gitaly_user.name, gitaly_user.email, gitaly_user.gl_id)
+ new(
+ gitaly_user.gl_username,
+ Gitlab::EncodingHelper.encode!(gitaly_user.name),
+ Gitlab::EncodingHelper.encode!(gitaly_user.email),
+ gitaly_user.gl_id
+ )
end
def initialize(username, name, email, gl_id)
@@ -23,7 +28,7 @@ module Gitlab
end
def to_gitaly
- Gitaly::User.new(gl_username: username, gl_id: gl_id, name: name, email: email)
+ Gitaly::User.new(gl_username: username, gl_id: gl_id, name: name.b, email: email.b)
end
end
end
diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb
index 022d1f249a9..d4a53d32c28 100644
--- a/lib/gitlab/git/wiki.rb
+++ b/lib/gitlab/git/wiki.rb
@@ -58,12 +58,12 @@ module Gitlab
end
end
- def pages
- @repository.gitaly_migrate(:wiki_get_all_pages) do |is_enabled|
+ def pages(limit: nil)
+ @repository.gitaly_migrate(:wiki_get_all_pages, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled|
if is_enabled
gitaly_get_all_pages
else
- gollum_get_all_pages
+ gollum_get_all_pages(limit: limit)
end
end
end
@@ -88,14 +88,23 @@ module Gitlab
end
end
- def page_versions(page_path)
+ # options:
+ # :page - The Integer page number.
+ # :per_page - The number of items per page.
+ # :limit - Total number of items to return.
+ def page_versions(page_path, options = {})
current_page = gollum_page_by_path(page_path)
- current_page.versions.map do |gollum_git_commit|
- gollum_page = gollum_wiki.page(current_page.title, gollum_git_commit.id)
- new_version(gollum_page, gollum_git_commit.id)
+
+ commits_from_page(current_page, options).map do |gitlab_git_commit|
+ gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id)
+ Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format)
end
end
+ def count_page_versions(page_path)
+ @repository.count_commits(ref: 'HEAD', path: page_path)
+ end
+
def preview_slug(title, format)
# Adapted from gollum gem (Gollum::Wiki#preview_page) to avoid
# using Rugged through a Gollum::Wiki instance
@@ -110,6 +119,22 @@ module Gitlab
private
+ # options:
+ # :page - The Integer page number.
+ # :per_page - The number of items per page.
+ # :limit - Total number of items to return.
+ def commits_from_page(gollum_page, options = {})
+ unless options[:limit]
+ options[:offset] = ([1, options.delete(:page).to_i].max - 1) * Gollum::Page.per_page
+ options[:limit] = (options.delete(:per_page) || Gollum::Page.per_page).to_i
+ end
+
+ @repository.log(ref: gollum_page.last_version.id,
+ path: gollum_page.path,
+ limit: options[:limit],
+ offset: options[:offset])
+ end
+
def gollum_wiki
@gollum_wiki ||= Gollum::Wiki.new(@repository.path)
end
@@ -126,8 +151,17 @@ module Gitlab
end
def new_version(gollum_page, commit_id)
- commit = Gitlab::Git::Commit.find(@repository, commit_id)
- Gitlab::Git::WikiPageVersion.new(commit, gollum_page&.format)
+ Gitlab::Git::WikiPageVersion.new(version(commit_id), gollum_page&.format)
+ end
+
+ def version(commit_id)
+ commit_find_proc = -> { Gitlab::Git::Commit.find(@repository, commit_id) }
+
+ if RequestStore.active?
+ RequestStore.fetch([:wiki_version_commit, commit_id]) { commit_find_proc.call }
+ else
+ commit_find_proc.call
+ end
end
def assert_type!(object, klass)
@@ -185,8 +219,8 @@ module Gitlab
Gitlab::Git::WikiFile.new(gollum_file)
end
- def gollum_get_all_pages
- gollum_wiki.pages.map { |gollum_page| new_page(gollum_page) }
+ def gollum_get_all_pages(limit: nil)
+ gollum_wiki.pages(limit: limit).map { |gollum_page| new_page(gollum_page) }
end
def gitaly_write_page(name, format, content, commit_details)
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 0b35a787e07..f27cd800bdd 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -31,14 +31,38 @@ module Gitlab
CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze
MUTEX = Mutex.new
- private_constant :MUTEX
+ METRICS_MUTEX = Mutex.new
+ private_constant :MUTEX, :METRICS_MUTEX
class << self
- attr_accessor :query_time, :migrate_histogram
+ attr_accessor :query_time
end
self.query_time = 0
- self.migrate_histogram = Gitlab::Metrics.histogram(:gitaly_migrate_call_duration, "Gitaly migration call execution timings")
+
+ def self.migrate_histogram
+ @migrate_histogram ||=
+ METRICS_MUTEX.synchronize do
+ # If a thread was blocked on the mutex, the value was set already
+ return @migrate_histogram if @migrate_histogram
+
+ Gitlab::Metrics.histogram(:gitaly_migrate_call_duration_seconds,
+ "Gitaly migration call execution timings",
+ gitaly_enabled: nil, feature: nil)
+ end
+ end
+
+ def self.gitaly_call_histogram
+ @gitaly_call_histogram ||=
+ METRICS_MUTEX.synchronize do
+ # If a thread was blocked on the mutex, the value was set already
+ return @gitaly_call_histogram if @gitaly_call_histogram
+
+ Gitlab::Metrics.histogram(:gitaly_controller_action_duration_seconds,
+ "Gitaly endpoint histogram by controller and action combination",
+ Gitlab::Metrics::Transaction::BASE_LABELS.merge(gitaly_service: nil, rpc: nil))
+ end
+ end
def self.stub(name, storage)
MUTEX.synchronize do
@@ -75,6 +99,10 @@ module Gitlab
address
end
+ def self.address_metadata(storage)
+ Base64.strict_encode64(JSON.dump({ storage => { 'address' => address(storage), 'token' => token(storage) } }))
+ end
+
# All Gitaly RPC call sites should use GitalyClient.call. This method
# makes sure that per-request authentication headers are set.
#
@@ -89,18 +117,30 @@ module Gitlab
# kwargs.merge(deadline: Time.now + 10)
# end
#
- def self.call(storage, service, rpc, request)
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ def self.call(storage, service, rpc, request, remote_storage: nil, timeout: nil)
+ start = Gitlab::Metrics::System.monotonic_time
enforce_gitaly_request_limits(:call)
- kwargs = request_kwargs(storage)
+ kwargs = request_kwargs(storage, timeout, remote_storage: remote_storage)
kwargs = yield(kwargs) if block_given?
+
stub(service, storage).__send__(rpc, request, kwargs) # rubocop:disable GitlabSecurity/PublicSend
ensure
- self.query_time += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
+ duration = Gitlab::Metrics::System.monotonic_time - start
+
+ # Keep track, seperately, for the performance bar
+ self.query_time += duration
+ gitaly_call_histogram.observe(
+ current_transaction_labels.merge(gitaly_service: service.to_s, rpc: rpc.to_s),
+ duration)
+ end
+
+ def self.current_transaction_labels
+ Gitlab::Metrics::Transaction.current&.labels || {}
end
+ private_class_method :current_transaction_labels
- def self.request_kwargs(storage)
+ def self.request_kwargs(storage, timeout, remote_storage: nil)
encoded_token = Base64.strict_encode64(token(storage).to_s)
metadata = {
'authorization' => "Bearer #{encoded_token}",
@@ -110,8 +150,24 @@ module Gitlab
feature_stack = Thread.current[:gitaly_feature_stack]
feature = feature_stack && feature_stack[0]
metadata['call_site'] = feature.to_s if feature
+ metadata['gitaly-servers'] = address_metadata(remote_storage) if remote_storage
- { metadata: metadata }
+ result = { metadata: metadata }
+
+ # nil timeout indicates that we should use the default
+ timeout = default_timeout if timeout.nil?
+
+ return result unless timeout > 0
+
+ # Do not use `Time.now` for deadline calculation, since it
+ # will be affected by Timecop in some tests, but grpc's c-core
+ # uses system time instead of timecop's time, so tests will fail
+ # `Time.at(Process.clock_gettime(Process::CLOCK_REALTIME))` will
+ # circumvent timecop
+ deadline = Time.at(Process.clock_gettime(Process::CLOCK_REALTIME)) + timeout
+ result[:deadline] = deadline
+
+ result
end
def self.token(storage)
@@ -172,10 +228,10 @@ module Gitlab
feature_stack = Thread.current[:gitaly_feature_stack] ||= []
feature_stack.unshift(feature)
begin
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ start = Gitlab::Metrics::System.monotonic_time
yield is_enabled
ensure
- total_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
+ total_time = Gitlab::Metrics::System.monotonic_time - start
migrate_histogram.observe({ gitaly_enabled: is_enabled, feature: feature }, total_time)
feature_stack.shift
Thread.current[:gitaly_feature_stack] = nil if feature_stack.empty?
@@ -284,6 +340,26 @@ module Gitlab
Google::Protobuf::RepeatedField.new(:bytes, a.map { |s| self.encode(s) } )
end
+ # The default timeout on all Gitaly calls
+ def self.default_timeout
+ return 0 if Sidekiq.server?
+
+ timeout(:gitaly_timeout_default)
+ end
+
+ def self.fast_timeout
+ timeout(:gitaly_timeout_fast)
+ end
+
+ def self.medium_timeout
+ timeout(:gitaly_timeout_medium)
+ end
+
+ def self.timeout(timeout_name)
+ Gitlab::CurrentSettings.current_application_settings[timeout_name]
+ end
+ private_class_method :timeout
+
# Count a stack. Used for n+1 detection
def self.count_stack
return unless RequestStore.active?
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index da5505cb2fe..7985f5b5457 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -16,7 +16,7 @@ module Gitlab
revision: GitalyClient.encode(revision)
)
- response = GitalyClient.call(@repository.storage, :commit_service, :list_files, request)
+ response = GitalyClient.call(@repository.storage, :commit_service, :list_files, request, timeout: GitalyClient.medium_timeout)
response.flat_map do |msg|
msg.paths.map { |d| EncodingHelper.encode!(d.dup) }
end
@@ -29,7 +29,7 @@ module Gitlab
child_id: child_id
)
- GitalyClient.call(@repository.storage, :commit_service, :commit_is_ancestor, request).value
+ GitalyClient.call(@repository.storage, :commit_service, :commit_is_ancestor, request, timeout: GitalyClient.fast_timeout).value
end
def diff(from, to, options = {})
@@ -77,7 +77,7 @@ module Gitlab
limit: limit.to_i
)
- response = GitalyClient.call(@repository.storage, :commit_service, :tree_entry, request)
+ response = GitalyClient.call(@repository.storage, :commit_service, :tree_entry, request, timeout: GitalyClient.medium_timeout)
entry = nil
data = ''
@@ -102,7 +102,7 @@ module Gitlab
path: path.present? ? GitalyClient.encode(path) : '.'
)
- response = GitalyClient.call(@repository.storage, :commit_service, :get_tree_entries, request)
+ response = GitalyClient.call(@repository.storage, :commit_service, :get_tree_entries, request, timeout: GitalyClient.medium_timeout)
response.flat_map do |message|
message.entries.map do |gitaly_tree_entry|
@@ -129,7 +129,7 @@ module Gitlab
request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present?
request.path = options[:path] if options[:path].present?
- GitalyClient.call(@repository.storage, :commit_service, :count_commits, request).count
+ GitalyClient.call(@repository.storage, :commit_service, :count_commits, request, timeout: GitalyClient.medium_timeout).count
end
def last_commit_for_path(revision, path)
@@ -139,7 +139,7 @@ module Gitlab
path: GitalyClient.encode(path.to_s)
)
- gitaly_commit = GitalyClient.call(@repository.storage, :commit_service, :last_commit_for_path, request).commit
+ gitaly_commit = GitalyClient.call(@repository.storage, :commit_service, :last_commit_for_path, request, timeout: GitalyClient.fast_timeout).commit
return unless gitaly_commit
Gitlab::Git::Commit.new(@repository, gitaly_commit)
@@ -152,7 +152,7 @@ module Gitlab
to: to
)
- response = GitalyClient.call(@repository.storage, :commit_service, :commits_between, request)
+ response = GitalyClient.call(@repository.storage, :commit_service, :commits_between, request, timeout: GitalyClient.medium_timeout)
consume_commits_response(response)
end
@@ -165,7 +165,7 @@ module Gitlab
)
request.order = opts[:order].upcase if opts[:order].present?
- response = GitalyClient.call(@repository.storage, :commit_service, :find_all_commits, request)
+ response = GitalyClient.call(@repository.storage, :commit_service, :find_all_commits, request, timeout: GitalyClient.medium_timeout)
consume_commits_response(response)
end
@@ -179,7 +179,7 @@ module Gitlab
offset: offset.to_i
)
- response = GitalyClient.call(@repository.storage, :commit_service, :commits_by_message, request)
+ response = GitalyClient.call(@repository.storage, :commit_service, :commits_by_message, request, timeout: GitalyClient.medium_timeout)
consume_commits_response(response)
end
@@ -197,7 +197,7 @@ module Gitlab
path: GitalyClient.encode(path)
)
- response = GitalyClient.call(@repository.storage, :commit_service, :raw_blame, request)
+ response = GitalyClient.call(@repository.storage, :commit_service, :raw_blame, request, timeout: GitalyClient.medium_timeout)
response.reduce("") { |memo, msg| memo << msg.data }
end
@@ -207,7 +207,7 @@ module Gitlab
revision: GitalyClient.encode(revision)
)
- response = GitalyClient.call(@repository.storage, :commit_service, :find_commit, request)
+ response = GitalyClient.call(@repository.storage, :commit_service, :find_commit, request, timeout: GitalyClient.medium_timeout)
response.commit
end
@@ -217,7 +217,7 @@ module Gitlab
repository: @gitaly_repo,
revision: GitalyClient.encode(revision)
)
- response = GitalyClient.call(@repository.storage, :diff_service, :commit_patch, request)
+ response = GitalyClient.call(@repository.storage, :diff_service, :commit_patch, request, timeout: GitalyClient.medium_timeout)
response.sum(&:data)
end
@@ -227,7 +227,7 @@ module Gitlab
repository: @gitaly_repo,
revision: GitalyClient.encode(revision)
)
- GitalyClient.call(@repository.storage, :commit_service, :commit_stats, request)
+ GitalyClient.call(@repository.storage, :commit_service, :commit_stats, request, timeout: GitalyClient.medium_timeout)
end
def find_commits(options)
@@ -245,11 +245,31 @@ module Gitlab
request.paths = GitalyClient.encode_repeated(Array(options[:path])) if options[:path].present?
- response = GitalyClient.call(@repository.storage, :commit_service, :find_commits, request)
+ response = GitalyClient.call(@repository.storage, :commit_service, :find_commits, request, timeout: GitalyClient.medium_timeout)
consume_commits_response(response)
end
+ def filter_shas_with_signatures(shas)
+ request = Gitaly::FilterShasWithSignaturesRequest.new(repository: @gitaly_repo)
+
+ enum = Enumerator.new do |y|
+ shas.each_slice(20) do |revs|
+ request.shas = GitalyClient.encode_repeated(revs)
+
+ y.yield request
+
+ request = Gitaly::FilterShasWithSignaturesRequest.new
+ end
+ end
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :filter_shas_with_signatures, enum)
+
+ response.flat_map do |msg|
+ msg.shas.map { |sha| EncodingHelper.encode!(sha) }
+ end
+ end
+
private
def call_commit_diff(request_params, options = {})
@@ -259,7 +279,7 @@ module Gitlab
request_params.merge!(Gitlab::Git::DiffCollection.collection_limits(options).to_h)
request = Gitaly::CommitDiffRequest.new(request_params)
- response = GitalyClient.call(@repository.storage, :diff_service, :commit_diff, request)
+ response = GitalyClient.call(@repository.storage, :diff_service, :commit_diff, request, timeout: GitalyClient.medium_timeout)
GitalyClient::DiffStitcher.new(response)
end
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 526d44a8b77..51de6a9a75d 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -122,6 +122,36 @@ module Gitlab
).branch_update
Gitlab::Git::OperationService::BranchUpdate.from_gitaly(branch_update)
end
+
+ def user_cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
+ request = Gitaly::UserCherryPickRequest.new(
+ repository: @gitaly_repo,
+ user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
+ commit: commit.to_gitaly_commit,
+ branch_name: GitalyClient.encode(branch_name),
+ message: GitalyClient.encode(message),
+ start_branch_name: GitalyClient.encode(start_branch_name.to_s),
+ start_repository: start_repository.gitaly_repository
+ )
+
+ response = GitalyClient.call(
+ @repository.storage,
+ :operation_service,
+ :user_cherry_pick,
+ request,
+ remote_storage: start_repository.storage
+ )
+
+ if response.pre_receive_error.presence
+ raise Gitlab::Git::HooksService::PreReceiveError, response.pre_receive_error
+ elsif response.commit_error.presence
+ raise Gitlab::Git::CommitError, response.commit_error
+ elsif response.create_tree_error.presence
+ raise Gitlab::Git::Repository::CreateTreeError, response.create_tree_error
+ else
+ Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index b0c73395cb1..066e4e183c0 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -46,7 +46,8 @@ module Gitlab
commit_id: commit_id,
prefix: ref_prefix
)
- encode!(GitalyClient.call(@storage, :ref_service, :find_ref_name, request).name.dup)
+ response = GitalyClient.call(@storage, :ref_service, :find_ref_name, request, timeout: GitalyClient.medium_timeout)
+ encode!(response.name.dup)
end
def count_tag_names
@@ -126,6 +127,15 @@ module Gitlab
GitalyClient.call(@repository.storage, :ref_service, :delete_branch, request)
end
+ def delete_refs(except_with_prefixes:)
+ request = Gitaly::DeleteRefsRequest.new(
+ repository: @gitaly_repo,
+ except_with_prefix: except_with_prefixes
+ )
+
+ GitalyClient.call(@repository.storage, :ref_service, :delete_refs, request)
+ end
+
private
def consume_refs_response(response)
@@ -137,6 +147,7 @@ module Gitlab
enum_value = Gitaly::FindLocalBranchesRequest::SortBy.resolve(sort_by.upcase.to_sym)
raise ArgumentError, "Invalid sort_by key `#{sort_by}`" unless enum_value
+
enum_value
end
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index cef692d3c2a..b9e606592d7 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -10,7 +10,9 @@ module Gitlab
def exists?
request = Gitaly::RepositoryExistsRequest.new(repository: @gitaly_repo)
- GitalyClient.call(@storage, :repository_service, :repository_exists, request).exists
+ response = GitalyClient.call(@storage, :repository_service, :repository_exists, request, timeout: GitalyClient.fast_timeout)
+
+ response.exists
end
def garbage_collect(create_bitmap)
@@ -30,7 +32,8 @@ module Gitlab
def repository_size
request = Gitaly::RepositorySizeRequest.new(repository: @gitaly_repo)
- GitalyClient.call(@storage, :repository_service, :repository_size, request).size
+ response = GitalyClient.call(@storage, :repository_service, :repository_size, request)
+ response.size
end
def apply_gitattributes(revision)
@@ -61,10 +64,29 @@ module Gitlab
def has_local_branches?
request = Gitaly::HasLocalBranchesRequest.new(repository: @gitaly_repo)
- response = GitalyClient.call(@storage, :repository_service, :has_local_branches, request)
+ response = GitalyClient.call(@storage, :repository_service, :has_local_branches, request, timeout: GitalyClient.fast_timeout)
response.value
end
+
+ def fetch_source_branch(source_repository, source_branch, local_ref)
+ request = Gitaly::FetchSourceBranchRequest.new(
+ repository: @gitaly_repo,
+ source_repository: source_repository.gitaly_repository,
+ source_branch: source_branch.b,
+ target_ref: local_ref.b
+ )
+
+ response = GitalyClient.call(
+ @storage,
+ :repository_service,
+ :fetch_source_branch,
+ request,
+ remote_storage: source_repository.storage
+ )
+
+ response.result
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb
index 8f05f40365e..c8f065f5881 100644
--- a/lib/gitlab/gitaly_client/wiki_service.rb
+++ b/lib/gitlab/gitaly_client/wiki_service.rb
@@ -94,6 +94,7 @@ module Gitlab
page, version = wiki_page_from_iterator(response) { |message| message.end_of_page }
break unless page && version
+
pages << [page, version]
end
diff --git a/lib/gitlab/github_import.rb b/lib/gitlab/github_import.rb
index d2ae4c1255e..65b5e30c70f 100644
--- a/lib/gitlab/github_import.rb
+++ b/lib/gitlab/github_import.rb
@@ -1,5 +1,9 @@
module Gitlab
module GithubImport
+ def self.refmap
+ [:heads, :tags, '+refs/pull/*/head:refs/merge-requests/*/head']
+ end
+
def self.new_client_for(project, token: nil, parallel: true)
token_to_use = token || project.import_data&.credentials&.fetch(:user)
diff --git a/lib/gitlab/github_import/importer/repository_importer.rb b/lib/gitlab/github_import/importer/repository_importer.rb
index 0b67fc8db73..9cf2e7fd871 100644
--- a/lib/gitlab/github_import/importer/repository_importer.rb
+++ b/lib/gitlab/github_import/importer/repository_importer.rb
@@ -45,27 +45,14 @@ module Gitlab
def import_repository
project.ensure_repository
- configure_repository_remote
-
- project.repository.fetch_remote('github', forced: true)
+ refmap = Gitlab::GithubImport.refmap
+ project.repository.fetch_as_mirror(project.import_url, refmap: refmap, forced: true, remote_name: 'github')
true
rescue Gitlab::Git::Repository::NoRepository, Gitlab::Shell::Error => e
fail_import("Failed to import the repository: #{e.message}")
end
- def configure_repository_remote
- return if project.repository.remote_exists?('github')
-
- project.repository.add_remote('github', project.import_url)
- project.repository.set_import_remote_as_mirror('github')
-
- project.repository.add_remote_fetch_config(
- 'github',
- '+refs/pull/*/head:refs/merge-requests/*/head'
- )
- end
-
def import_wiki_repository
wiki_path = "#{project.disk_path}.wiki"
wiki_url = project.import_url.sub(/\.git\z/, '.wiki.git')
diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb
index f1007daab5d..075b3982608 100644
--- a/lib/gitlab/gitlab_import/client.rb
+++ b/lib/gitlab/gitlab_import/client.rb
@@ -65,6 +65,7 @@ module Gitlab
y << item
end
break if items.empty? || items.size < per_page
+
page += 1
end
end
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index 50ee879129c..2066005dddc 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -3,7 +3,7 @@ module Gitlab
extend self
# For every version update, the version history in import_export.md has to be kept up to date.
- VERSION = '0.2.0'.freeze
+ VERSION = '0.2.1'.freeze
FILENAME_LIMIT = 50
def export_path(relative_path:)
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 263599831bf..f2b193c79cb 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -133,8 +133,6 @@ methods:
- :type
services:
- :type
- merge_request_diff:
- - :utf8_st_diffs
merge_request_diff_files:
- :utf8_diff
merge_requests:
diff --git a/lib/gitlab/import_export/merge_request_parser.rb b/lib/gitlab/import_export/merge_request_parser.rb
index 61db4bd9ccc..f3d7407383c 100644
--- a/lib/gitlab/import_export/merge_request_parser.rb
+++ b/lib/gitlab/import_export/merge_request_parser.rb
@@ -1,7 +1,7 @@
module Gitlab
module ImportExport
class MergeRequestParser
- FORKED_PROJECT_ID = -1
+ FORKED_PROJECT_ID = nil
def initialize(project, diff_head_sha, merge_request, relation_hash)
@project = project
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 639f4f0c3f0..c518943be59 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -60,6 +60,8 @@ module Gitlab
end
end
+ @project.merge_requests.set_latest_merge_request_diff_ids!
+
@saved
end
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 2b34ceb5831..d7d1b05e8b9 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -58,7 +58,6 @@ module Gitlab
def setup_models
case @relation_name
- when :merge_request_diff then setup_st_diff_commits
when :merge_request_diff_files then setup_diff
when :notes then setup_note
when :project_label, :project_labels then setup_label
@@ -208,13 +207,6 @@ module Gitlab
relation_class: relation_class)
end
- def setup_st_diff_commits
- @relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs')
-
- HashUtil.deep_symbolize_array!(@relation_hash['st_diffs'])
- HashUtil.deep_symbolize_array_with_date!(@relation_hash['st_commits'])
- end
-
def setup_diff
@relation_hash['diff'] = @relation_hash.delete('utf8_diff')
end
diff --git a/lib/gitlab/import_export/uploads_saver.rb b/lib/gitlab/import_export/uploads_saver.rb
index f9ae5079d7c..627a487d577 100644
--- a/lib/gitlab/import_export/uploads_saver.rb
+++ b/lib/gitlab/import_export/uploads_saver.rb
@@ -24,8 +24,7 @@ module Gitlab
end
def uploads_path
- # TODO: decide what to do with uploads. We will use UUIDs here too?
- File.join(Rails.root.join('public/uploads'), @project.path_with_namespace)
+ FileUploader.dynamic_path_segment(@project)
end
end
end
diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb
index 7a50f07f3c5..407cdefc04d 100644
--- a/lib/gitlab/kubernetes/helm.rb
+++ b/lib/gitlab/kubernetes/helm.rb
@@ -18,7 +18,7 @@ module Gitlab
def initialize(kubeclient)
@kubeclient = kubeclient
- @namespace = Namespace.new(NAMESPACE, kubeclient)
+ @namespace = Gitlab::Kubernetes::Namespace.new(NAMESPACE, kubeclient)
end
def install(command)
diff --git a/lib/gitlab/kubernetes/namespace.rb b/lib/gitlab/kubernetes/namespace.rb
index c8479fbc0e8..fbbddb7bffa 100644
--- a/lib/gitlab/kubernetes/namespace.rb
+++ b/lib/gitlab/kubernetes/namespace.rb
@@ -12,6 +12,7 @@ module Gitlab
@client.get_namespace(name)
rescue ::KubeException => ke
raise ke unless ke.error_code == 404
+
false
end
diff --git a/lib/gitlab/ldap/authentication.rb b/lib/gitlab/ldap/authentication.rb
index ed1de73f8c6..7274d1c3b43 100644
--- a/lib/gitlab/ldap/authentication.rb
+++ b/lib/gitlab/ldap/authentication.rb
@@ -62,6 +62,7 @@ module Gitlab
def user
return nil unless ldap_user
+
Gitlab::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider)
end
end
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index 4d5c67ed892..3945df27eed 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -9,11 +9,8 @@ module Gitlab
class User < Gitlab::OAuth::User
class << self
def find_by_uid_and_provider(uid, provider)
- uid = Gitlab::LDAP::Person.normalize_dn(uid)
+ identity = ::Identity.with_extern_uid(provider, uid).take
- identity = ::Identity
- .where(provider: provider)
- .where(extern_uid: uid).last
identity && identity.user
end
end
diff --git a/lib/gitlab/legacy_github_import/importer.rb b/lib/gitlab/legacy_github_import/importer.rb
index 12c968805f5..0526ef9eb13 100644
--- a/lib/gitlab/legacy_github_import/importer.rb
+++ b/lib/gitlab/legacy_github_import/importer.rb
@@ -3,6 +3,10 @@ module Gitlab
class Importer
include Gitlab::ShellAdapter
+ def self.refmap
+ Gitlab::GithubImport.refmap
+ end
+
attr_reader :errors, :project, :repo, :repo_url
def initialize(project)
@@ -15,6 +19,7 @@ module Gitlab
def client
return @client if defined?(@client)
+
unless credentials
raise Projects::ImportService::Error,
"Unable to find project import data credentials for project ID: #{@project.id}"
diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index 90235095306..65d55576ac2 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -6,29 +6,15 @@ module Gitlab
BASE_LABELS = { module: nil, method: nil }.freeze
attr_reader :real_time, :cpu_time, :call_count, :labels
- def self.call_real_duration_histogram
- return @call_real_duration_histogram if @call_real_duration_histogram
-
- MUTEX.synchronize do
- @call_real_duration_histogram ||= Gitlab::Metrics.histogram(
- :gitlab_method_call_real_duration_seconds,
- 'Method calls real duration',
- Transaction::BASE_LABELS.merge(BASE_LABELS),
- [0.1, 0.2, 0.5, 1, 2, 5, 10]
- )
- end
- end
-
- def self.call_cpu_duration_histogram
- return @call_cpu_duration_histogram if @call_cpu_duration_histogram
+ def self.call_duration_histogram
+ return @call_duration_histogram if @call_duration_histogram
MUTEX.synchronize do
@call_duration_histogram ||= Gitlab::Metrics.histogram(
- :gitlab_method_call_cpu_duration_seconds,
- 'Method calls cpu duration',
+ :gitlab_method_call_duration_seconds,
+ 'Method calls real duration',
Transaction::BASE_LABELS.merge(BASE_LABELS),
- [0.1, 0.2, 0.5, 1, 2, 5, 10]
- )
+ [0.01, 0.05, 0.1, 0.5, 1])
end
end
@@ -59,8 +45,9 @@ module Gitlab
@cpu_time += cpu_time
@call_count += 1
- self.class.call_real_duration_histogram.observe(@transaction.labels.merge(labels), real_time / 1000.0)
- self.class.call_cpu_duration_histogram.observe(@transaction.labels.merge(labels), cpu_time / 1000.0)
+ if call_measurement_enabled? && above_threshold?
+ self.class.call_duration_histogram.observe(@transaction.labels.merge(labels), real_time / 1000.0)
+ end
retval
end
@@ -83,6 +70,10 @@ module Gitlab
def above_threshold?
real_time >= Metrics.method_call_threshold
end
+
+ def call_measurement_enabled?
+ Feature.get(:prometheus_metrics_method_instrumentation).enabled?
+ end
end
end
end
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index 8b5a60e6b8b..436a9e9550d 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -96,6 +96,7 @@ module Gitlab
def worker_label
return {} unless defined?(Unicorn::Worker)
+
worker_no = ::Prometheus::Client::Support::Unicorn.worker_id
if worker_no
diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb
index 064299f40c8..ead1acb8d44 100644
--- a/lib/gitlab/metrics/subscribers/active_record.rb
+++ b/lib/gitlab/metrics/subscribers/active_record.rb
@@ -7,6 +7,7 @@ module Gitlab
def sql(event)
return unless current_transaction
+
metric_sql_duration_seconds.observe(current_transaction.labels, event.duration / 1000.0)
current_transaction.increment(:sql_duration, event.duration, false)
diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb
index cfc6b2a2029..c6a56277922 100644
--- a/lib/gitlab/middleware/go.rb
+++ b/lib/gitlab/middleware/go.rb
@@ -42,12 +42,11 @@ module Gitlab
project_url = URI.join(config.gitlab.url, path)
import_prefix = strip_url(project_url.to_s)
- repository_url = case current_application_settings.enabled_git_access_protocol
- when 'ssh'
+ repository_url = if current_application_settings.enabled_git_access_protocol == 'ssh'
shell = config.gitlab_shell
port = ":#{shell.ssh_port}" unless shell.ssh_port == 22
"ssh://#{shell.ssh_user}@#{shell.ssh_host}#{port}/#{path}.git"
- when 'http', nil
+ else
"#{project_url}.git"
end
@@ -66,6 +65,7 @@ module Gitlab
project_path_match = "#{path_info}/".match(PROJECT_PATH_REGEX)
return unless project_path_match
+
path = project_path_match[1]
# Go subpackages may be in the form of `namespace/project/path1/path2/../pathN`.
diff --git a/lib/gitlab/middleware/read_only.rb b/lib/gitlab/middleware/read_only.rb
index 5e4932e4e57..c26656704d7 100644
--- a/lib/gitlab/middleware/read_only.rb
+++ b/lib/gitlab/middleware/read_only.rb
@@ -58,7 +58,7 @@ module Gitlab
end
def last_visited_url
- @env['HTTP_REFERER'] || rack_session['user_return_to'] || Rails.application.routes.url_helpers.root_url
+ @env['HTTP_REFERER'] || rack_session['user_return_to'] || Gitlab::Routing.url_helpers.root_url
end
def route_hash
@@ -74,10 +74,16 @@ module Gitlab
end
def grack_route
+ # Calling route_hash may be expensive. Only do it if we think there's a possible match
+ return false unless request.path.end_with?('.git/git-upload-pack')
+
route_hash[:controller] == 'projects/git_http' && route_hash[:action] == 'git_upload_pack'
end
def lfs_route
+ # Calling route_hash may be expensive. Only do it if we think there's a possible match
+ return false unless request.path.end_with?('/info/lfs/objects/batch')
+
route_hash[:controller] == 'projects/lfs_api' && route_hash[:action] == 'batch'
end
end
diff --git a/lib/gitlab/multi_collection_paginator.rb b/lib/gitlab/multi_collection_paginator.rb
index eb3c9002710..c22d0a84860 100644
--- a/lib/gitlab/multi_collection_paginator.rb
+++ b/lib/gitlab/multi_collection_paginator.rb
@@ -55,7 +55,9 @@ module Gitlab
def first_collection_last_page_size
return @first_collection_last_page_size if defined?(@first_collection_last_page_size)
- @first_collection_last_page_size = paginated_first_collection(first_collection_page_count).count
+ @first_collection_last_page_size = paginated_first_collection(first_collection_page_count)
+ .except(:select)
+ .size
end
end
end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index b4b3b00c84d..552133234a3 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -157,7 +157,7 @@ module Gitlab
end
def find_by_uid_and_provider
- identity = Identity.find_by(provider: auth_hash.provider, extern_uid: auth_hash.uid)
+ identity = Identity.with_extern_uid(auth_hash.provider, auth_hash.uid).take
identity && identity.user
end
diff --git a/lib/gitlab/optimistic_locking.rb b/lib/gitlab/optimistic_locking.rb
index 962ff4d3985..1d9a5d1a20a 100644
--- a/lib/gitlab/optimistic_locking.rb
+++ b/lib/gitlab/optimistic_locking.rb
@@ -11,6 +11,7 @@ module Gitlab
rescue ActiveRecord::StaleObjectError
retries -= 1
raise unless retries >= 0
+
subject.reload
end
end
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index 9a91f8bf96a..7e5dfd33502 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -51,7 +51,6 @@ module Gitlab
slash-command-logo.png
snippets
u
- unicorn_test
unsubscribes
uploads
users
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 561aa9e162c..e2662fc362b 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -47,8 +47,11 @@ module Gitlab
startline = 0
result.each_line.each_with_index do |line, index|
- if line =~ /^.*:.*:\d+:/
- ref, filename, startline = line.split(':')
+ matches = line.match(/^(?<ref>[^:]*):(?<filename>.*):(?<startline>\d+):/)
+ if matches
+ ref = matches[:ref]
+ filename = matches[:filename]
+ startline = matches[:startline]
startline = startline.to_i - index
extname = Regexp.escape(File.extname(filename))
basename = filename.sub(/#{extname}$/, '')
diff --git a/lib/gitlab/prometheus/queries/query_additional_metrics.rb b/lib/gitlab/prometheus/queries/query_additional_metrics.rb
index 7ac6162b54d..5cddc96a643 100644
--- a/lib/gitlab/prometheus/queries/query_additional_metrics.rb
+++ b/lib/gitlab/prometheus/queries/query_additional_metrics.rb
@@ -76,7 +76,7 @@ module Gitlab
timeframe_start: timeframe_start,
timeframe_end: timeframe_end,
ci_environment_slug: environment.slug,
- kube_namespace: environment.project.kubernetes_service&.actual_namespace || '',
+ kube_namespace: environment.project.deployment_platform&.actual_namespace || '',
environment_filter: %{container_name!="POD",environment="#{environment.slug}"}
}
end
diff --git a/lib/gitlab/routing.rb b/lib/gitlab/routing.rb
index 910533076b0..2c994536060 100644
--- a/lib/gitlab/routing.rb
+++ b/lib/gitlab/routing.rb
@@ -46,10 +46,10 @@ module Gitlab
# Only replace the last occurence of `path`.
#
# `request.fullpath` includes the querystring
- path = request.path.sub(%r{/#{path}/*(?!.*#{path})}, "/-/#{path}/")
- path << "?#{request.query_string}" if request.query_string.present?
+ new_path = request.path.sub(%r{/#{path}(/*)(?!.*#{path})}, "/-/#{path}\\1")
+ new_path << "?#{request.query_string}" if request.query_string.present?
- path
+ new_path
end
paths.each do |path|
diff --git a/lib/gitlab/saml/user.rb b/lib/gitlab/saml/user.rb
index e0a9d1dee77..d8faf7aad8c 100644
--- a/lib/gitlab/saml/user.rb
+++ b/lib/gitlab/saml/user.rb
@@ -28,6 +28,7 @@ module Gitlab
def changed?
return true unless gl_user
+
gl_user.changed? || gl_user.identities.any?(&:changed?)
end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index efe8095beea..fef9d3e31d4 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -30,7 +30,7 @@ module Gitlab
def initialize(current_user, limit_projects, query)
@current_user = current_user
@limit_projects = limit_projects || Project.all
- @query = Shellwords.shellescape(query) if query.present?
+ @query = query
end
def objects(scope, page = nil)
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index f9ab9bd466f..30df7e4a831 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -8,7 +8,8 @@ end
module Gitlab
class Seeder
def self.quiet
- mute_mailer
+ mute_mailer unless Rails.env.test?
+
SeedFu.quiet = true
yield
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index a37112ae5c4..a22a63665be 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -101,8 +101,7 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387
def import_repository(storage, name, url)
- # Timeout should be less than 900 ideally, to prevent the memory killer
- # to silently kill the process without knowing we are timing out here.
+ # The timeout ensures the subprocess won't hang forever
cmd = [gitlab_shell_projects_path, 'import-project',
storage, "#{name}.git", url, "#{Gitlab.config.gitlab_shell.git_timeout}"]
gitlab_shell_fast_execute_raise_error(cmd)
@@ -144,20 +143,27 @@ module Gitlab
storage, "#{path}.git", "#{new_path}.git"])
end
- # Fork repository to new namespace
+ # Fork repository to new path
# forked_from_storage - forked-from project's storage path
- # path - project path with namespace
+ # forked_from_disk_path - project disk path
# forked_to_storage - forked-to project's storage path
- # fork_namespace - namespace for forked project
+ # forked_to_disk_path - forked project disk path
#
# Ex.
- # fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "randx")
+ # fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "new-namespace/gitlab-ci")
#
# Gitaly note: JV: not easy to migrate because this involves two Gitaly servers, not one.
- def fork_repository(forked_from_storage, path, forked_to_storage, fork_namespace)
- gitlab_shell_fast_execute([gitlab_shell_projects_path, 'fork-project',
- forked_from_storage, "#{path}.git", forked_to_storage,
- fork_namespace])
+ def fork_repository(forked_from_storage, forked_from_disk_path, forked_to_storage, forked_to_disk_path)
+ gitlab_shell_fast_execute(
+ [
+ gitlab_shell_projects_path,
+ 'fork-repository',
+ forked_from_storage,
+ "#{forked_from_disk_path}.git",
+ forked_to_storage,
+ "#{forked_to_disk_path}.git"
+ ]
+ )
end
# Remove repository from file system
@@ -368,6 +374,7 @@ module Gitlab
output, status = gitlab_shell_fast_execute_helper(cmd, vars)
raise Error, output unless status.zero?
+
true
end
diff --git a/lib/gitlab/shell_adapter.rb b/lib/gitlab/shell_adapter.rb
index fbe2a7a0d72..053dd4ab9e0 100644
--- a/lib/gitlab/shell_adapter.rb
+++ b/lib/gitlab/shell_adapter.rb
@@ -5,7 +5,7 @@
module Gitlab
module ShellAdapter
def gitlab_shell
- Gitlab::Shell.new
+ @gitlab_shell ||= Gitlab::Shell.new
end
end
end
diff --git a/lib/gitlab/sidekiq_config.rb b/lib/gitlab/sidekiq_config.rb
new file mode 100644
index 00000000000..dc9886732b5
--- /dev/null
+++ b/lib/gitlab/sidekiq_config.rb
@@ -0,0 +1,50 @@
+require 'yaml'
+
+module Gitlab
+ module SidekiqConfig
+ def self.redis_queues
+ @redis_queues ||= Sidekiq::Queue.all.map(&:name)
+ end
+
+ # This method is called by `bin/sidekiq-cluster` in EE, which runs outside
+ # of bundler/Rails context, so we cannot use any gem or Rails methods.
+ def self.config_queues(rails_path = Rails.root.to_s)
+ @config_queues ||= begin
+ config = YAML.load_file(File.join(rails_path, 'config', 'sidekiq_queues.yml'))
+ config[:queues].map(&:first)
+ end
+ end
+
+ def self.cron_workers
+ @cron_workers ||= Settings.cron_jobs.map { |job_name, options| options['job_class'].constantize }
+ end
+
+ def self.workers
+ @workers ||= find_workers(Rails.root.join('app', 'workers'))
+ end
+
+ def self.default_queues
+ [ActionMailer::DeliveryJob.queue_name, 'default']
+ end
+
+ def self.worker_queues
+ @worker_queues ||= (workers.map(&:queue) + default_queues).uniq
+ end
+
+ def self.find_workers(root)
+ concerns = root.join('concerns').to_s
+
+ workers = Dir[root.join('**', '*.rb')]
+ .reject { |path| path.start_with?(concerns) }
+
+ workers.map! do |path|
+ ns = Pathname.new(path).relative_path_from(root).to_s.gsub('.rb', '')
+
+ ns.camelize.constantize
+ end
+
+ # Skip concerns
+ workers.select { |w| w < Sidekiq::Worker }
+ end
+ end
+end
diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb
index 7c2d1d8f887..5f0c98cb5a4 100644
--- a/lib/gitlab/sql/pattern.rb
+++ b/lib/gitlab/sql/pattern.rb
@@ -4,9 +4,15 @@ module Gitlab
extend ActiveSupport::Concern
MIN_CHARS_FOR_PARTIAL_MATCHING = 3
- REGEX_QUOTED_WORD = /(?<=^| )"[^"]+"(?= |$)/
+ REGEX_QUOTED_WORD = /(?<=\A| )"[^"]+"(?= |\z)/
class_methods do
+ def fuzzy_search(query, columns)
+ matches = columns.map { |col| fuzzy_arel_match(col, query) }.compact.reduce(:or)
+
+ where(matches)
+ end
+
def to_pattern(query)
if partial_matching?(query)
"%#{sanitize_sql_like(query)}%"
@@ -19,12 +25,19 @@ module Gitlab
query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING
end
- def to_fuzzy_arel(column, query)
- words = select_fuzzy_words(query)
+ def fuzzy_arel_match(column, query)
+ query = query.squish
+ return nil unless query.present?
- matches = words.map { |word| arel_table[column].matches(to_pattern(word)) }
+ words = select_fuzzy_words(query)
- matches.reduce { |result, match| result.and(match) }
+ if words.any?
+ words.map { |word| arel_table[column].matches(to_pattern(word)) }.reduce(:and)
+ else
+ # No words of at least 3 chars, but we can search for an exact
+ # case insensitive match with the query as a whole
+ arel_table[column].matches(sanitize_sql_like(query))
+ end
end
def select_fuzzy_words(query)
@@ -32,7 +45,7 @@ module Gitlab
query = quoted_words.reduce(query) { |q, quoted_word| q.sub(quoted_word, '') }
- words = query.split(/\s+/)
+ words = query.split
quoted_words.map! { |quoted_word| quoted_word[1..-2] }
diff --git a/lib/gitlab/string_range_marker.rb b/lib/gitlab/string_range_marker.rb
index 11aeec1ebfa..f9faa134206 100644
--- a/lib/gitlab/string_range_marker.rb
+++ b/lib/gitlab/string_range_marker.rb
@@ -90,6 +90,7 @@ module Gitlab
# Takes an array of integers, and returns an array of ranges covering the same integers
def collapse_ranges(positions)
return [] if positions.empty?
+
ranges = []
start = prev = positions[0]
diff --git a/lib/gitlab/template/finders/repo_template_finder.rb b/lib/gitlab/template/finders/repo_template_finder.rb
index cb7957e2af9..33f07fa0120 100644
--- a/lib/gitlab/template/finders/repo_template_finder.rb
+++ b/lib/gitlab/template/finders/repo_template_finder.rb
@@ -18,6 +18,7 @@ module Gitlab
def read(path)
blob = @repository.blob_at(@commit.id, path) if @commit
raise FileNotFoundError if blob.nil?
+
blob.data
end
diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb
index 1caa791c1be..59331c827af 100644
--- a/lib/gitlab/url_sanitizer.rb
+++ b/lib/gitlab/url_sanitizer.rb
@@ -70,6 +70,7 @@ module Gitlab
def generate_full_url
return @url unless valid_credentials?
+
@full_url = @url.dup
@full_url.password = credentials[:password] if credentials[:password].present?
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 112d4939582..2adcc9809b3 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -79,7 +79,7 @@ module Gitlab
def features_usage_data_ce
{
- signup: current_application_settings.signup_enabled?,
+ signup: current_application_settings.allow_signup?,
ldap: Gitlab.config.ldap.enabled,
gravatar: current_application_settings.gravatar_enabled?,
omniauth: Gitlab.config.omniauth.enabled,
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index c60bd91ea6e..11472ce6cce 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -99,6 +99,7 @@ module Gitlab
def level_value(level)
return level.to_i if level.to_i.to_s == level.to_s && string_options.key(level.to_i)
+
string_options[level] || PRIVATE
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index e1219df1b25..5ab6cd5a4ef 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -58,7 +58,7 @@ module Gitlab
end
def artifact_upload_ok
- { TempPath: ArtifactUploader.artifacts_upload_path }
+ { TempPath: JobArtifactUploader.artifacts_upload_path }
end
def send_git_blob(repository, blob)
@@ -174,6 +174,7 @@ module Gitlab
@secret ||= begin
bytes = Base64.strict_decode64(File.read(secret_path).chomp)
raise "#{secret_path} does not contain #{SECRET_LENGTH} bytes" if bytes.length != SECRET_LENGTH
+
bytes
end
end
diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb
index 9242cbe840c..b0563fb2d69 100644
--- a/lib/google_api/cloud_platform/client.rb
+++ b/lib/google_api/cloud_platform/client.rb
@@ -44,7 +44,7 @@ module GoogleApi
service = Google::Apis::ContainerV1::ContainerService.new
service.authorization = access_token
- service.get_zone_cluster(project_id, zone, cluster_id)
+ service.get_zone_cluster(project_id, zone, cluster_id, options: user_agent_header)
end
def projects_zones_clusters_create(project_id, zone, cluster_name, cluster_size, machine_type:)
@@ -62,14 +62,14 @@ module GoogleApi
}
} )
- service.create_cluster(project_id, zone, request_body)
+ service.create_cluster(project_id, zone, request_body, options: user_agent_header)
end
def projects_zones_operations(project_id, zone, operation_id)
service = Google::Apis::ContainerV1::ContainerService.new
service.authorization = access_token
- service.get_zone_operation(project_id, zone, operation_id)
+ service.get_zone_operation(project_id, zone, operation_id, options: user_agent_header)
end
def parse_operation_id(self_link)
@@ -82,6 +82,12 @@ module GoogleApi
def token_life_time(expires_at)
DateTime.strptime(expires_at, '%s').to_time.utc - Time.now.utc
end
+
+ def user_agent_header
+ Google::Apis::RequestOptions.new.tap do |options|
+ options.header = { 'User-Agent': "GitLab/#{Gitlab::VERSION.match('(\d+\.\d+)').captures.first} (GPN:GitLab;)" }
+ end
+ end
end
end
end
diff --git a/lib/haml_lint/inline_javascript.rb b/lib/haml_lint/inline_javascript.rb
index 05668c69006..f5485eb89fa 100644
--- a/lib/haml_lint/inline_javascript.rb
+++ b/lib/haml_lint/inline_javascript.rb
@@ -9,6 +9,7 @@ unless Rails.env.production?
def visit_filter(node)
return unless node.filter_type == 'javascript'
+
record_lint(node, 'Inline JavaScript is discouraged (https://docs.gitlab.com/ee/development/gotchas.html#do-not-use-inline-javascript-in-views)')
end
end
diff --git a/lib/milestone_array.rb b/lib/milestone_array.rb
new file mode 100644
index 00000000000..4ed8485b36a
--- /dev/null
+++ b/lib/milestone_array.rb
@@ -0,0 +1,40 @@
+module MilestoneArray
+ class << self
+ def sort(array, sort_method)
+ case sort_method
+ when 'due_date_asc'
+ sort_asc_nulls_last(array, 'due_date')
+ when 'due_date_desc'
+ sort_desc_nulls_last(array, 'due_date')
+ when 'start_date_asc'
+ sort_asc_nulls_last(array, 'start_date')
+ when 'start_date_desc'
+ sort_desc_nulls_last(array, 'start_date')
+ when 'name_asc'
+ sort_asc(array, 'title')
+ when 'name_desc'
+ sort_asc(array, 'title').reverse
+ else
+ array
+ end
+ end
+
+ private
+
+ def sort_asc_nulls_last(array, attribute)
+ attribute = attribute.to_sym
+
+ array.select(&attribute).sort_by(&attribute) + array.reject(&attribute)
+ end
+
+ def sort_desc_nulls_last(array, attribute)
+ attribute = attribute.to_sym
+
+ array.select(&attribute).sort_by(&attribute).reverse + array.reject(&attribute)
+ end
+
+ def sort_asc(array, attribute)
+ array.sort_by(&attribute.to_sym)
+ end
+ end
+end
diff --git a/lib/rouge/lexers/math.rb b/lib/rouge/lexers/math.rb
deleted file mode 100644
index 939b23a3421..00000000000
--- a/lib/rouge/lexers/math.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module Rouge
- module Lexers
- class Math < PlainText
- title "A passthrough lexer used for LaTeX input"
- desc "PLEASE REFACTOR - this should be handled by SyntaxHighlightFilter"
- tag 'math'
- end
- end
-end
diff --git a/lib/rouge/lexers/plantuml.rb b/lib/rouge/lexers/plantuml.rb
deleted file mode 100644
index 63c461764fc..00000000000
--- a/lib/rouge/lexers/plantuml.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module Rouge
- module Lexers
- class Plantuml < PlainText
- title "A passthrough lexer used for PlantUML input"
- desc "PLEASE REFACTOR - this should be handled by SyntaxHighlightFilter"
- tag 'plantuml'
- end
- end
-end
diff --git a/lib/system_check/simple_executor.rb b/lib/system_check/simple_executor.rb
index 00221f77cf4..8b145fb4511 100644
--- a/lib/system_check/simple_executor.rb
+++ b/lib/system_check/simple_executor.rb
@@ -24,6 +24,7 @@ module SystemCheck
# @param [BaseCheck] check class
def <<(check)
raise ArgumentError unless check.is_a?(Class) && check < BaseCheck
+
@checks << check
end
diff --git a/lib/tasks/brakeman.rake b/lib/tasks/brakeman.rake
index 99b3168d9eb..2301ec9b228 100644
--- a/lib/tasks/brakeman.rake
+++ b/lib/tasks/brakeman.rake
@@ -2,7 +2,7 @@ desc 'Security check via brakeman'
task :brakeman do
# We get 0 warnings at level 'w3' but we would like to reach 'w2'. Merge
# requests are welcome!
- if system(*%w(brakeman --no-progress --skip-files lib/backup/repository.rb,app/controllers/unicorn_test_controller.rb -w3 -z))
+ if system(*%w(brakeman --no-progress --skip-files lib/backup/repository.rb -w3 -z))
puts 'Security check succeed'
else
puts 'Security check failed'
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 8ae1b6a626a..eb0f757aea7 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -1,11 +1,14 @@
namespace :gitlab do
namespace :cleanup do
+ HASHED_REPOSITORY_NAME = '@hashed'.freeze
+
desc "GitLab | Cleanup | Clean namespaces"
task dirs: :environment do
warn_user_is_not_gitlab
remove_flag = ENV['REMOVE']
- namespaces = Namespace.pluck(:path)
+ namespaces = Namespace.pluck(:path)
+ namespaces << HASHED_REPOSITORY_NAME # add so that it will be ignored
Gitlab.config.repositories.storages.each do |name, repository_storage|
git_base_path = repository_storage['path']
all_dirs = Dir.glob(git_base_path + '/*')
@@ -59,7 +62,11 @@ namespace :gitlab do
.sub(%r{^/*}, '')
.chomp('.git')
.chomp('.wiki')
- next if Project.find_by_full_path(repo_with_namespace)
+
+ # TODO ignoring hashed repositories for now. But revisit to fully support
+ # possible orphaned hashed repos
+ next if repo_with_namespace.start_with?("#{HASHED_REPOSITORY_NAME}/") || Project.find_by_full_path(repo_with_namespace)
+
new_path = path + move_suffix
puts path.inspect + ' -> ' + new_path.inspect
File.rename(path, new_path)
@@ -75,6 +82,7 @@ namespace :gitlab do
User.find_each do |user|
next unless user.ldap_user?
+
print "#{user.name} (#{user.ldap_identity.extern_uid}) ..."
if Gitlab::LDAP::Access.allowed?(user)
puts " [OK]".color(:green)
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index 8377fe3269d..4d880c05f99 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -14,18 +14,18 @@ namespace :gitlab do
checkout_or_clone_version(version: version, repo: args.repo, target_dir: args.dir)
+ command = %w[/usr/bin/env -u RUBYOPT -u BUNDLE_GEMFILE]
+
_, status = Gitlab::Popen.popen(%w[which gmake])
- command = status.zero? ? ['gmake'] : ['make']
+ command << (status.zero? ? 'gmake' : 'make')
- if Rails.env.test?
- command += %W[BUNDLE_PATH=#{Bundler.bundle_path}]
- end
+ command << 'BUNDLE_FLAGS=--no-deployment' if Rails.env.test?
Dir.chdir(args.dir) do
create_gitaly_configuration
# In CI we run scripts/gitaly-test-build instead of this command
unless ENV['CI'].present?
- Bundler.with_original_env { run_command!(%w[/usr/bin/env -u RUBYOPT -u BUNDLE_GEMFILE] + command) }
+ Bundler.with_original_env { run_command!(command) }
end
end
end
@@ -78,13 +78,18 @@ namespace :gitlab do
config[:auth] = { token: 'secret' } if Rails.env.test?
config[:'gitaly-ruby'] = { dir: File.join(Dir.pwd, 'ruby') } if gitaly_ruby
config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path }
+ config[:bin_dir] = Gitlab.config.gitaly.client_path
+
TOML.dump(config)
end
def create_gitaly_configuration
- File.open("config.toml", "w") do |f|
+ File.open("config.toml", File::WRONLY | File::CREAT | File::EXCL) do |f|
f.puts gitaly_configuration_toml
end
+ rescue Errno::EEXIST
+ puts "Skipping config.toml generation:"
+ puts "A configuration file already exists."
rescue ArgumentError => e
puts "Skipping config.toml generation:"
puts e.message
diff --git a/lib/tasks/gitlab/sidekiq.rake b/lib/tasks/gitlab/sidekiq.rake
deleted file mode 100644
index 6cbc83b8973..00000000000
--- a/lib/tasks/gitlab/sidekiq.rake
+++ /dev/null
@@ -1,47 +0,0 @@
-namespace :gitlab do
- namespace :sidekiq do
- QUEUE = 'queue:post_receive'.freeze
-
- desc 'Drop all Sidekiq PostReceive jobs for a given project'
- task :drop_post_receive, [:project] => :environment do |t, args|
- unless args.project.present?
- abort "Please specify the project you want to drop PostReceive jobs for:\n rake gitlab:sidekiq:drop_post_receive[group/project]"
- end
- project_path = Project.find_by_full_path(args.project).repository.path_to_repo
-
- Sidekiq.redis do |redis|
- unless redis.exists(QUEUE)
- abort "Queue #{QUEUE} is empty"
- end
-
- temp_queue = "#{QUEUE}_#{Time.now.to_i}"
- redis.rename(QUEUE, temp_queue)
-
- # At this point, then post_receive queue is empty. It may be receiving
- # new jobs already. We will repopulate it with the old jobs, skipping the
- # ones we want to drop.
- dropped = 0
- while (job = redis.lpop(temp_queue))
- if repo_path(job) == project_path
- dropped += 1
- else
- redis.rpush(QUEUE, job)
- end
- end
- # The temp_queue will delete itself after we have popped all elements
- # from it
-
- puts "Dropped #{dropped} jobs containing #{project_path} from #{QUEUE}"
- end
- end
-
- def repo_path(job)
- job_args = JSON.parse(job)['args']
- if job_args
- job_args.first
- else
- nil
- end
- end
- end
-end
diff --git a/lib/tasks/gitlab/storage.rake b/lib/tasks/gitlab/storage.rake
index e05be4a3405..8ac73bc8ff2 100644
--- a/lib/tasks/gitlab/storage.rake
+++ b/lib/tasks/gitlab/storage.rake
@@ -2,10 +2,10 @@ namespace :gitlab do
namespace :storage do
desc 'GitLab | Storage | Migrate existing projects to Hashed Storage'
task migrate_to_hashed: :environment do
- legacy_projects_count = Project.with_legacy_storage.count
+ legacy_projects_count = Project.with_unmigrated_storage.count
if legacy_projects_count == 0
- puts 'There are no projects using legacy storage. Nothing to do!'
+ puts 'There are no projects requiring storage migration. Nothing to do!'
next
end
@@ -23,22 +23,42 @@ namespace :gitlab do
desc 'Gitlab | Storage | Summary of existing projects using Legacy Storage'
task legacy_projects: :environment do
- projects_summary(Project.with_legacy_storage)
+ relation_summary('projects', Project.without_storage_feature(:repository))
end
desc 'Gitlab | Storage | List existing projects using Legacy Storage'
task list_legacy_projects: :environment do
- projects_list(Project.with_legacy_storage)
+ projects_list('projects using Legacy Storage', Project.without_storage_feature(:repository))
end
desc 'Gitlab | Storage | Summary of existing projects using Hashed Storage'
task hashed_projects: :environment do
- projects_summary(Project.with_hashed_storage)
+ relation_summary('projects using Hashed Storage', Project.with_storage_feature(:repository))
end
desc 'Gitlab | Storage | List existing projects using Hashed Storage'
task list_hashed_projects: :environment do
- projects_list(Project.with_hashed_storage)
+ projects_list('projects using Hashed Storage', Project.with_storage_feature(:repository))
+ end
+
+ desc 'Gitlab | Storage | Summary of project attachments using Legacy Storage'
+ task legacy_attachments: :environment do
+ relation_summary('attachments using Legacy Storage', legacy_attachments_relation)
+ end
+
+ desc 'Gitlab | Storage | List existing project attachments using Legacy Storage'
+ task list_legacy_attachments: :environment do
+ attachments_list('attachments using Legacy Storage', legacy_attachments_relation)
+ end
+
+ desc 'Gitlab | Storage | Summary of project attachments using Hashed Storage'
+ task hashed_attachments: :environment do
+ relation_summary('attachments using Hashed Storage', hashed_attachments_relation)
+ end
+
+ desc 'Gitlab | Storage | List existing project attachments using Hashed Storage'
+ task list_hashed_attachments: :environment do
+ attachments_list('attachments using Hashed Storage', hashed_attachments_relation)
end
def batch_size
@@ -46,29 +66,43 @@ namespace :gitlab do
end
def project_id_batches(&block)
- Project.with_legacy_storage.in_batches(of: batch_size, start: ENV['ID_FROM'], finish: ENV['ID_TO']) do |relation| # rubocop: disable Cop/InBatches
+ Project.with_unmigrated_storage.in_batches(of: batch_size, start: ENV['ID_FROM'], finish: ENV['ID_TO']) do |relation| # rubocop: disable Cop/InBatches
ids = relation.pluck(:id)
yield ids.min, ids.max
end
end
- def projects_summary(relation)
- projects_count = relation.count
- puts "* Found #{projects_count} projects".color(:green)
+ def legacy_attachments_relation
+ Upload.joins(<<~SQL).where('projects.storage_version < :version OR projects.storage_version IS NULL', version: Project::HASHED_STORAGE_FEATURES[:attachments])
+ JOIN projects
+ ON (uploads.model_type='Project' AND uploads.model_id=projects.id)
+ SQL
+ end
+
+ def hashed_attachments_relation
+ Upload.joins(<<~SQL).where('projects.storage_version >= :version', version: Project::HASHED_STORAGE_FEATURES[:attachments])
+ JOIN projects
+ ON (uploads.model_type='Project' AND uploads.model_id=projects.id)
+ SQL
+ end
+
+ def relation_summary(relation_name, relation)
+ relation_count = relation.count
+ puts "* Found #{relation_count} #{relation_name}".color(:green)
- projects_count
+ relation_count
end
- def projects_list(relation)
- projects_count = projects_summary(relation)
+ def projects_list(relation_name, relation)
+ relation_count = relation_summary(relation_name, relation)
projects = relation.with_route
limit = ENV.fetch('LIMIT', 500).to_i
- return unless projects_count > 0
+ return unless relation_count > 0
- puts " ! Displaying first #{limit} projects..." if projects_count > limit
+ puts " ! Displaying first #{limit} #{relation_name}..." if relation_count > limit
counter = 0
projects.find_in_batches(batch_size: batch_size) do |batch|
@@ -81,5 +115,26 @@ namespace :gitlab do
end
end
end
+
+ def attachments_list(relation_name, relation)
+ relation_count = relation_summary(relation_name, relation)
+
+ limit = ENV.fetch('LIMIT', 500).to_i
+
+ return unless relation_count > 0
+
+ puts " ! Displaying first #{limit} #{relation_name}..." if relation_count > limit
+
+ counter = 0
+ relation.find_in_batches(batch_size: batch_size) do |batch|
+ batch.each do |upload|
+ counter += 1
+
+ puts " - #{upload.path} (id: #{upload.id})".color(:red)
+
+ return if counter >= limit # rubocop:disable Lint/NonLocalExitFromIterator
+ end
+ end
+ end
end
end