summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorFilipe Freire <livrofubia@gmail.com>2018-01-25 20:23:19 +0000
committerFilipe Freire <livrofubia@gmail.com>2018-01-25 20:23:19 +0000
commit03b87ec943ec9e48106a8d94350f6d5011e30519 (patch)
tree77bc2b00229c4d5598cb94db379f6beae240f792 /lib
parent350dbca41c2be6717d4c9f5800ef6dd60b06d932 (diff)
parent10d8026f69efe83a0f4759f91c5087effd676191 (diff)
downloadgitlab-ce-03b87ec943ec9e48106a8d94350f6d5011e30519.tar.gz
Merge branch 'master' of https://gitlab.com/filipefreire/gitlab-ce into filipefreire_155
Diffstat (limited to 'lib')
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/applications.rb27
-rw-r--r--lib/api/deploy_keys.rb65
-rw-r--r--lib/api/deployments.rb4
-rw-r--r--lib/api/entities.rb26
-rw-r--r--lib/api/helpers/internal_helpers.rb15
-rw-r--r--lib/api/members.rb2
-rw-r--r--lib/api/project_snippets.rb2
-rw-r--r--lib/api/projects.rb4
-rw-r--r--lib/api/protected_branches.rb2
-rw-r--r--lib/api/services.rb2
-rw-r--r--lib/api/system_hooks.rb1
-rw-r--r--lib/api/v3/deploy_keys.rb46
-rw-r--r--lib/api/v3/entities.rb5
-rw-r--r--lib/api/v3/members.rb2
-rw-r--r--lib/api/v3/projects.rb2
-rw-r--r--lib/api/v3/services.rb2
-rw-r--r--lib/banzai/filter/relative_link_filter.rb6
-rw-r--r--lib/gitlab/background_migration/copy_column.rb2
-rw-r--r--lib/gitlab/bare_repository_import/repository.rb2
-rw-r--r--lib/gitlab/checks/change_access.rb38
-rw-r--r--lib/gitlab/ci/config/entry/validators.rb16
-rw-r--r--lib/gitlab/ci/status/build/action.rb5
-rw-r--r--lib/gitlab/database/grant.rb50
-rw-r--r--lib/gitlab/database/migration_helpers.rb5
-rw-r--r--lib/gitlab/git/attributes_at_ref_parser.rb14
-rw-r--r--lib/gitlab/git/attributes_parser.rb (renamed from lib/gitlab/git/attributes.rb)53
-rw-r--r--lib/gitlab/git/blob.rb24
-rw-r--r--lib/gitlab/git/commit.rb18
-rw-r--r--lib/gitlab/git/info_attributes.rb49
-rw-r--r--lib/gitlab/git/ref.rb4
-rw-r--r--lib/gitlab/git/remote_mirror.rb24
-rw-r--r--lib/gitlab/git/repository.rb279
-rw-r--r--lib/gitlab/git/rev_list.rb2
-rw-r--r--lib/gitlab/git/wiki_page.rb3
-rw-r--r--lib/gitlab/gitaly_client.rb21
-rw-r--r--lib/gitlab/gitaly_client/blob_service.rb22
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb41
-rw-r--r--lib/gitlab/gitaly_client/conflicts_service.rb5
-rw-r--r--lib/gitlab/gitaly_client/health_check_service.rb19
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb72
-rw-r--r--lib/gitlab/gitaly_client/remote_service.rb27
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb42
-rw-r--r--lib/gitlab/github_import/importer/pull_requests_importer.rb5
-rw-r--r--lib/gitlab/gpg/commit.rb8
-rw-r--r--lib/gitlab/health_checks/gitaly_check.rb53
-rw-r--r--lib/gitlab/import_export/command_line_util.rb9
-rw-r--r--lib/gitlab/import_export/file_importer.rb6
-rw-r--r--lib/gitlab/import_export/repo_restorer.rb2
-rw-r--r--lib/gitlab/import_export/repo_saver.rb2
-rw-r--r--lib/gitlab/import_export/saver.rb2
-rw-r--r--lib/gitlab/import_export/shared.rb14
-rw-r--r--lib/gitlab/import_export/wiki_repo_saver.rb2
-rw-r--r--lib/gitlab/o_auth.rb6
-rw-r--r--lib/gitlab/o_auth/user.rb11
-rw-r--r--lib/gitlab/performance_bar.rb1
-rw-r--r--lib/gitlab/profiler.rb142
-rw-r--r--lib/gitlab/project_search_results.rb2
-rw-r--r--lib/gitlab/regex.rb2
-rw-r--r--lib/gitlab/search_results.rb66
-rw-r--r--lib/gitlab/snippet_search_results.rb2
-rw-r--r--lib/gitlab/utils.rb4
-rw-r--r--lib/gitlab/workhorse.rb5
-rw-r--r--lib/tasks/gitlab/check.rake2
-rw-r--r--lib/tasks/gitlab/cleanup.rake2
-rw-r--r--lib/tasks/migrate/setup_postgresql.rake2
66 files changed, 1092 insertions, 309 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb
index ae161efb358..f3f64244589 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -106,6 +106,7 @@ module API
# Keep in alphabetical order
mount ::API::AccessRequests
+ mount ::API::Applications
mount ::API::AwardEmoji
mount ::API::Boards
mount ::API::Branches
diff --git a/lib/api/applications.rb b/lib/api/applications.rb
new file mode 100644
index 00000000000..b122cdefe4e
--- /dev/null
+++ b/lib/api/applications.rb
@@ -0,0 +1,27 @@
+module API
+ # External applications API
+ class Applications < Grape::API
+ before { authenticated_as_admin! }
+
+ resource :applications do
+ desc 'Create a new application' do
+ detail 'This feature was introduced in GitLab 10.5'
+ success Entities::ApplicationWithSecret
+ end
+ params do
+ requires :name, type: String, desc: 'Application name'
+ requires :redirect_uri, type: String, desc: 'Application redirect URI'
+ requires :scopes, type: String, desc: 'Application scopes'
+ end
+ post do
+ application = Doorkeeper::Application.new(declared_params)
+
+ if application.save
+ present application, with: Entities::ApplicationWithSecret
+ else
+ render_validation_error! application
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 281269b1190..b0b7b50998f 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -4,6 +4,16 @@ module API
before { authenticate! }
+ helpers do
+ def add_deploy_keys_project(project, attrs = {})
+ project.deploy_keys_projects.create(attrs)
+ end
+
+ def find_by_deploy_key(project, key_id)
+ project.deploy_keys_projects.find_by!(deploy_key: key_id)
+ end
+ end
+
desc 'Return all deploy keys'
params do
use :pagination
@@ -21,28 +31,31 @@ module API
before { authorize_admin_project }
desc "Get a specific project's deploy keys" do
- success Entities::SSHKey
+ success Entities::DeployKeysProject
end
params do
use :pagination
end
get ":id/deploy_keys" do
- present paginate(user_project.deploy_keys), with: Entities::SSHKey
+ keys = user_project.deploy_keys_projects.preload(:deploy_key)
+
+ present paginate(keys), with: Entities::DeployKeysProject
end
desc 'Get single deploy key' do
- success Entities::SSHKey
+ success Entities::DeployKeysProject
end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
get ":id/deploy_keys/:key_id" do
- key = user_project.deploy_keys.find params[:key_id]
- present key, with: Entities::SSHKey
+ key = find_by_deploy_key(user_project, params[:key_id])
+
+ present key, with: Entities::DeployKeysProject
end
desc 'Add new deploy key to currently authenticated user' do
- success Entities::SSHKey
+ success Entities::DeployKeysProject
end
params do
requires :key, type: String, desc: 'The new deploy key'
@@ -53,24 +66,31 @@ module API
params[:key].strip!
# Check for an existing key joined to this project
- key = user_project.deploy_keys.find_by(key: params[:key])
+ key = user_project.deploy_keys_projects
+ .joins(:deploy_key)
+ .find_by(keys: { key: params[:key] })
+
if key
- present key, with: Entities::SSHKey
+ present key, with: Entities::DeployKeysProject
break
end
# Check for available deploy keys in other projects
key = current_user.accessible_deploy_keys.find_by(key: params[:key])
if key
- user_project.deploy_keys << key
- present key, with: Entities::SSHKey
+ added_key = add_deploy_keys_project(user_project, deploy_key: key, can_push: !!params[:can_push])
+
+ present added_key, with: Entities::DeployKeysProject
break
end
# Create a new deploy key
- key = DeployKey.new(declared_params(include_missing: false))
- if key.valid? && user_project.deploy_keys << key
- present key, with: Entities::SSHKey
+ key_attributes = { can_push: !!params[:can_push],
+ deploy_key_attributes: declared_params.except(:can_push) }
+ key = add_deploy_keys_project(user_project, key_attributes)
+
+ if key.valid?
+ present key, with: Entities::DeployKeysProject
else
render_validation_error!(key)
end
@@ -86,14 +106,21 @@ module API
at_least_one_of :title, :can_push
end
put ":id/deploy_keys/:key_id" do
- key = DeployKey.find(params.delete(:key_id))
+ deploy_keys_project = find_by_deploy_key(user_project, params[:key_id])
- authorize!(:update_deploy_key, key)
+ authorize!(:update_deploy_key, deploy_keys_project.deploy_key)
- if key.update_attributes(declared_params(include_missing: false))
- present key, with: Entities::SSHKey
+ can_push = params[:can_push].nil? ? deploy_keys_project.can_push : params[:can_push]
+ title = params[:title] || deploy_keys_project.deploy_key.title
+
+ result = deploy_keys_project.update_attributes(can_push: can_push,
+ deploy_key_attributes: { id: params[:key_id],
+ title: title })
+
+ if result
+ present deploy_keys_project, with: Entities::DeployKeysProject
else
- render_validation_error!(key)
+ render_validation_error!(deploy_keys_project)
end
end
@@ -122,7 +149,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
delete ":id/deploy_keys/:key_id" do
- key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
+ key = user_project.deploy_keys.find(params[:key_id])
not_found!('Deploy Key') unless key
destroy_conditionally!(key)
diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb
index 1efee9a1324..184fae0eb76 100644
--- a/lib/api/deployments.rb
+++ b/lib/api/deployments.rb
@@ -15,11 +15,13 @@ module API
end
params do
use :pagination
+ optional :order_by, type: String, values: %w[id iid created_at ref], default: 'id', desc: 'Return deployments ordered by `id` or `iid` or `created_at` or `ref`'
+ optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'
end
get ':id/deployments' do
authorize! :read_deployment, user_project
- present paginate(user_project.deployments), with: Entities::Deployment
+ present paginate(user_project.deployments.order(params[:order_by] => params[:sort])), with: Entities::Deployment
end
desc 'Gets a specific deployment' do
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index c4ef2c74658..7b9a80a234b 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -65,12 +65,12 @@ module API
end
class Hook < Grape::Entity
- expose :id, :url, :created_at, :push_events, :tag_push_events, :repository_update_events
+ expose :id, :url, :created_at, :push_events, :tag_push_events, :merge_requests_events, :repository_update_events
expose :enable_ssl_verification
end
class ProjectHook < Hook
- expose :project_id, :issues_events, :merge_requests_events
+ expose :project_id, :issues_events
expose :note_events, :pipeline_events, :wiki_page_events
expose :job_events
end
@@ -554,13 +554,18 @@ module API
end
class SSHKey < Grape::Entity
- expose :id, :title, :key, :created_at, :can_push
+ expose :id, :title, :key, :created_at
end
class SSHKeyWithUser < SSHKey
expose :user, using: Entities::UserPublic
end
+ class DeployKeysProject < Grape::Entity
+ expose :deploy_key, merge: true, using: Entities::SSHKey
+ expose :can_push
+ end
+
class GPGKey < Grape::Entity
expose :id, :key, :created_at
end
@@ -714,10 +719,7 @@ module API
expose :job_events
# Expose serialized properties
expose :properties do |service, options|
- field_names = service.fields
- .select { |field| options[:include_passwords] || field[:type] != 'password' }
- .map { |field| field[:name] }
- service.properties.slice(*field_names)
+ service.properties.slice(*service.api_field_names)
end
end
@@ -1155,5 +1157,15 @@ module API
pages_domain
end
end
+
+ class Application < Grape::Entity
+ expose :uid, as: :application_id
+ expose :redirect_uri, as: :callback_url
+ end
+
+ # Use with care, this exposes the secret
+ class ApplicationWithSecret < Application
+ expose :secret
+ end
end
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index eff1c5b70ea..eb67de81a0d 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -1,11 +1,6 @@
module API
module Helpers
module InternalHelpers
- SSH_GITALY_FEATURES = {
- 'git-receive-pack' => [:ssh_receive_pack, Gitlab::GitalyClient::MigrationStatus::OPT_IN],
- 'git-upload-pack' => [:ssh_upload_pack, Gitlab::GitalyClient::MigrationStatus::OPT_OUT]
- }.freeze
-
attr_reader :redirected_path
def wiki?
@@ -102,8 +97,14 @@ module API
# Return the Gitaly Address if it is enabled
def gitaly_payload(action)
- feature, status = SSH_GITALY_FEATURES[action]
- return unless feature && Gitlab::GitalyClient.feature_enabled?(feature, status: status)
+ return unless %w[git-receive-pack git-upload-pack].include?(action)
+
+ if action == 'git-receive-pack'
+ return unless Gitlab::GitalyClient.feature_enabled?(
+ :ssh_receive_pack,
+ status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT
+ )
+ end
{
repository: repository.gitaly_repository,
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 5446f6b54b1..130c6d6da71 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -22,7 +22,7 @@ module API
source = find_source(source_type, params[:id])
users = source.users
- users = users.merge(User.search(params[:query])) if params[:query]
+ users = users.merge(User.search(params[:query])) if params[:query].present?
present paginate(users), with: Entities::Member, source: source
end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 5bed58c2d63..39c03c40bab 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -143,7 +143,7 @@ module API
get ":id/snippets/:snippet_id/user_agent_detail" do
authenticated_as_admin!
- snippet = Snippet.find_by!(id: params[:id])
+ snippet = Snippet.find_by!(id: params[:snippet_id], project_id: params[:id])
return not_found!('UserAgentDetail') unless snippet.user_agent_detail
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 653126e79ea..8b5e4f8edcc 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -76,9 +76,9 @@ module API
def present_projects(projects, options = {})
projects = reorder_projects(projects)
- projects = projects.with_statistics if params[:statistics]
- projects = projects.with_issues_enabled if params[:with_issues_enabled]
+ projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
+ projects = projects.with_statistics if params[:statistics]
projects = paginate(projects)
if current_user
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index 614822509f0..c15c487deb4 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -2,7 +2,7 @@ module API
class ProtectedBranches < Grape::API
include PaginationParams
- BRANCH_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX)
+ BRANCH_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(name: API::NO_SLASH_URL_PART_REGEX)
before { authorize_admin_project }
diff --git a/lib/api/services.rb b/lib/api/services.rb
index a7f44e2869c..51e33e2c686 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -785,7 +785,7 @@ module API
service_params = declared_params(include_missing: false).merge(active: true)
if service.update_attributes(service_params)
- present service, with: Entities::ProjectService, include_passwords: current_user.admin?
+ present service, with: Entities::ProjectService
else
render_api_error!('400 Bad Request', 400)
end
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 6b6a03e3300..c7a460df46a 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -26,6 +26,7 @@ module API
optional :token, type: String, desc: 'The token used to validate payloads'
optional :push_events, type: Boolean, desc: "Trigger hook on push events"
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
+ optional :merge_requests_events, type: Boolean, desc: "Trigger hook on tag push events"
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
end
post do
diff --git a/lib/api/v3/deploy_keys.rb b/lib/api/v3/deploy_keys.rb
index b90e2061da3..47e54ca85a5 100644
--- a/lib/api/v3/deploy_keys.rb
+++ b/lib/api/v3/deploy_keys.rb
@@ -3,6 +3,16 @@ module API
class DeployKeys < Grape::API
before { authenticate! }
+ helpers do
+ def add_deploy_keys_project(project, attrs = {})
+ project.deploy_keys_projects.create(attrs)
+ end
+
+ def find_by_deploy_key(project, key_id)
+ project.deploy_keys_projects.find_by!(deploy_key: key_id)
+ end
+ end
+
get "deploy_keys" do
authenticated_as_admin!
@@ -18,25 +28,28 @@ module API
%w(keys deploy_keys).each do |path|
desc "Get a specific project's deploy keys" do
- success ::API::Entities::SSHKey
+ success ::API::Entities::DeployKeysProject
end
get ":id/#{path}" do
- present user_project.deploy_keys, with: ::API::Entities::SSHKey
+ keys = user_project.deploy_keys_projects.preload(:deploy_key)
+
+ present keys, with: ::API::Entities::DeployKeysProject
end
desc 'Get single deploy key' do
- success ::API::Entities::SSHKey
+ success ::API::Entities::DeployKeysProject
end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
get ":id/#{path}/:key_id" do
- key = user_project.deploy_keys.find params[:key_id]
- present key, with: ::API::Entities::SSHKey
+ key = find_by_deploy_key(user_project, params[:key_id])
+
+ present key, with: ::API::Entities::DeployKeysProject
end
desc 'Add new deploy key to currently authenticated user' do
- success ::API::Entities::SSHKey
+ success ::API::Entities::DeployKeysProject
end
params do
requires :key, type: String, desc: 'The new deploy key'
@@ -47,24 +60,31 @@ module API
params[:key].strip!
# Check for an existing key joined to this project
- key = user_project.deploy_keys.find_by(key: params[:key])
+ key = user_project.deploy_keys_projects
+ .joins(:deploy_key)
+ .find_by(keys: { key: params[:key] })
+
if key
- present key, with: ::API::Entities::SSHKey
+ present key, with: ::API::Entities::DeployKeysProject
break
end
# Check for available deploy keys in other projects
key = current_user.accessible_deploy_keys.find_by(key: params[:key])
if key
- user_project.deploy_keys << key
- present key, with: ::API::Entities::SSHKey
+ added_key = add_deploy_keys_project(user_project, deploy_key: key, can_push: !!params[:can_push])
+
+ present added_key, with: ::API::Entities::DeployKeysProject
break
end
# Create a new deploy key
- key = DeployKey.new(declared_params(include_missing: false))
- if key.valid? && user_project.deploy_keys << key
- present key, with: ::API::Entities::SSHKey
+ key_attributes = { can_push: !!params[:can_push],
+ deploy_key_attributes: declared_params.except(:can_push) }
+ key = add_deploy_keys_project(user_project, key_attributes)
+
+ if key.valid?
+ present key, with: ::API::Entities::DeployKeysProject
else
render_validation_error!(key)
end
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index 64758dae7d3..2ccbb9da1c5 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -257,10 +257,7 @@ module API
expose :job_events, as: :build_events
# Expose serialized properties
expose :properties do |service, options|
- field_names = service.fields
- .select { |field| options[:include_passwords] || field[:type] != 'password' }
- .map { |field| field[:name] }
- service.properties.slice(*field_names)
+ service.properties.slice(*service.api_field_names)
end
end
diff --git a/lib/api/v3/members.rb b/lib/api/v3/members.rb
index de226e4e573..46145cac7a5 100644
--- a/lib/api/v3/members.rb
+++ b/lib/api/v3/members.rb
@@ -23,7 +23,7 @@ module API
source = find_source(source_type, params[:id])
users = source.users
- users = users.merge(User.search(params[:query])) if params[:query]
+ users = users.merge(User.search(params[:query])) if params[:query].present?
present paginate(users), with: ::API::Entities::Member, source: source
end
diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb
index 446f804124b..a7f0813bf74 100644
--- a/lib/api/v3/projects.rb
+++ b/lib/api/v3/projects.rb
@@ -175,7 +175,7 @@ module API
end
get "/search/:query", requirements: { query: /[^\/]+/ } do
search_service = Search::GlobalService.new(current_user, search: params[:query]).execute
- projects = search_service.objects('projects', params[:page])
+ projects = search_service.objects('projects', params[:page], false)
projects = projects.reorder(params[:order_by] => params[:sort])
present paginate(projects), with: ::API::V3::Entities::Project
diff --git a/lib/api/v3/services.rb b/lib/api/v3/services.rb
index 44ed94d2869..20ca1021c71 100644
--- a/lib/api/v3/services.rb
+++ b/lib/api/v3/services.rb
@@ -622,7 +622,7 @@ module API
end
get ":id/services/:service_slug" do
service = user_project.find_or_initialize_service(params[:service_slug].underscore)
- present service, with: Entities::ProjectService, include_passwords: current_user.admin?
+ present service, with: Entities::ProjectService
end
end
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index f6169b2c85d..9bdedeb6615 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -50,7 +50,7 @@ module Banzai
end
def process_link_to_upload_attr(html_attr)
- path_parts = [html_attr.value]
+ path_parts = [Addressable::URI.unescape(html_attr.value)]
if group
path_parts.unshift(relative_url_root, 'groups', group.full_path, '-')
@@ -58,13 +58,13 @@ module Banzai
path_parts.unshift(relative_url_root, project.full_path)
end
- path = File.join(*path_parts)
+ path = Addressable::URI.escape(File.join(*path_parts))
html_attr.value =
if context[:only_path]
path
else
- URI.join(Gitlab.config.gitlab.base_url, path).to_s
+ Addressable::URI.join(Gitlab.config.gitlab.base_url, path).to_s
end
end
diff --git a/lib/gitlab/background_migration/copy_column.rb b/lib/gitlab/background_migration/copy_column.rb
index a2cb215c230..ef70f37d5eb 100644
--- a/lib/gitlab/background_migration/copy_column.rb
+++ b/lib/gitlab/background_migration/copy_column.rb
@@ -28,6 +28,8 @@ module Gitlab
UPDATE #{quoted_table}
SET #{quoted_copy_to} = #{quoted_copy_from}
WHERE id BETWEEN #{start_id} AND #{end_id}
+ AND #{quoted_copy_from} IS NOT NULL
+ AND #{quoted_copy_to} IS NULL
SQL
end
diff --git a/lib/gitlab/bare_repository_import/repository.rb b/lib/gitlab/bare_repository_import/repository.rb
index c0c666dfb7b..fe267248275 100644
--- a/lib/gitlab/bare_repository_import/repository.rb
+++ b/lib/gitlab/bare_repository_import/repository.rb
@@ -1,3 +1,5 @@
+# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/953
+#
module Gitlab
module BareRepositoryImport
class Repository
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index ef92fc5a0a0..945d70e7a24 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -16,7 +16,7 @@ module Gitlab
lfs_objects_missing: 'LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".'
}.freeze
- attr_reader :user_access, :project, :skip_authorization, :protocol
+ attr_reader :user_access, :project, :skip_authorization, :protocol, :oldrev, :newrev, :ref, :branch_name, :tag_name
def initialize(
change, user_access:, project:, skip_authorization: false,
@@ -51,9 +51,9 @@ module Gitlab
end
def branch_checks
- return unless @branch_name
+ return unless branch_name
- if deletion? && @branch_name == project.default_branch
+ if deletion? && branch_name == project.default_branch
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_default_branch]
end
@@ -61,7 +61,7 @@ module Gitlab
end
def protected_branch_checks
- return unless ProtectedBranch.protected?(project, @branch_name)
+ return unless ProtectedBranch.protected?(project, branch_name)
if forced_push?
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:force_push_protected_branch]
@@ -75,29 +75,29 @@ module Gitlab
end
def protected_branch_deletion_checks
- unless user_access.can_delete_branch?(@branch_name)
+ unless user_access.can_delete_branch?(branch_name)
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_master_delete_protected_branch]
end
- unless protocol == 'web'
+ unless updated_from_web?
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_web_delete_protected_branch]
end
end
def protected_branch_push_checks
if matching_merge_request?
- unless user_access.can_merge_to_branch?(@branch_name) || user_access.can_push_to_branch?(@branch_name)
+ unless user_access.can_merge_to_branch?(branch_name) || user_access.can_push_to_branch?(branch_name)
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:merge_protected_branch]
end
else
- unless user_access.can_push_to_branch?(@branch_name)
+ unless user_access.can_push_to_branch?(branch_name)
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_protected_branch]
end
end
end
def tag_checks
- return unless @tag_name
+ return unless tag_name
if tag_exists? && user_access.cannot_do_action?(:admin_project)
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:change_existing_tags]
@@ -107,40 +107,44 @@ module Gitlab
end
def protected_tag_checks
- return unless ProtectedTag.protected?(project, @tag_name)
+ return unless ProtectedTag.protected?(project, tag_name)
raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:update_protected_tag]) if update?
raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_protected_tag]) if deletion?
- unless user_access.can_create_tag?(@tag_name)
+ unless user_access.can_create_tag?(tag_name)
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:create_protected_tag]
end
end
private
+ def updated_from_web?
+ protocol == 'web'
+ end
+
def tag_exists?
- project.repository.tag_exists?(@tag_name)
+ project.repository.tag_exists?(tag_name)
end
def forced_push?
- Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev)
+ Gitlab::Checks::ForcePush.force_push?(project, oldrev, newrev)
end
def update?
- !Gitlab::Git.blank_ref?(@oldrev) && !deletion?
+ !Gitlab::Git.blank_ref?(oldrev) && !deletion?
end
def deletion?
- Gitlab::Git.blank_ref?(@newrev)
+ Gitlab::Git.blank_ref?(newrev)
end
def matching_merge_request?
- Checks::MatchingMergeRequest.new(@newrev, @branch_name, @project).match?
+ Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
end
def lfs_objects_exist_check
- lfs_check = Checks::LfsIntegrity.new(project, @newrev)
+ lfs_check = Checks::LfsIntegrity.new(project, newrev)
if lfs_check.objects_missing?
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:lfs_objects_missing]
diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb
index eb606b57667..55658900628 100644
--- a/lib/gitlab/ci/config/entry/validators.rb
+++ b/lib/gitlab/ci/config/entry/validators.rb
@@ -64,10 +64,24 @@ module Gitlab
include LegacyValidationHelpers
def validate_each(record, attribute, value)
- unless validate_string(value)
+ if validate_string(value)
+ validate_path(record, attribute, value)
+ else
record.errors.add(attribute, 'should be a string or symbol')
end
end
+
+ private
+
+ def validate_path(record, attribute, value)
+ path = CGI.unescape(value.to_s)
+
+ if path.include?('/')
+ record.errors.add(attribute, 'cannot contain the "/" character')
+ elsif path == '.' || path == '..'
+ record.errors.add(attribute, 'cannot be "." or ".."')
+ end
+ end
end
class RegexpValidator < ActiveModel::EachValidator
diff --git a/lib/gitlab/ci/status/build/action.rb b/lib/gitlab/ci/status/build/action.rb
index 45fd0d4aa07..6c9125647ad 100644
--- a/lib/gitlab/ci/status/build/action.rb
+++ b/lib/gitlab/ci/status/build/action.rb
@@ -2,6 +2,9 @@ module Gitlab
module Ci
module Status
module Build
+ ##
+ # Extended status for playable manual actions.
+ #
class Action < Status::Extended
def label
if has_action?
@@ -12,7 +15,7 @@ module Gitlab
end
def self.matches?(build, user)
- build.action?
+ build.playable?
end
end
end
diff --git a/lib/gitlab/database/grant.rb b/lib/gitlab/database/grant.rb
index 9f76967fc77..d32837f5793 100644
--- a/lib/gitlab/database/grant.rb
+++ b/lib/gitlab/database/grant.rb
@@ -12,30 +12,40 @@ module Gitlab
# Returns true if the current user can create and execute triggers on the
# given table.
def self.create_and_execute_trigger?(table)
- priv =
- if Database.postgresql?
- where(privilege_type: 'TRIGGER', table_name: table)
- .where('grantee = user')
- else
- queries = [
- Grant.select(1)
- .from('information_schema.user_privileges')
- .where("PRIVILEGE_TYPE = 'SUPER'")
- .where("GRANTEE = CONCAT('\\'', REPLACE(CURRENT_USER(), '@', '\\'@\\''), '\\'')"),
+ if Database.postgresql?
+ # We _must not_ use quote_table_name as this will produce double
+ # quotes on PostgreSQL and for "has_table_privilege" we need single
+ # quotes.
+ quoted_table = connection.quote(table)
- Grant.select(1)
- .from('information_schema.schema_privileges')
- .where("PRIVILEGE_TYPE = 'TRIGGER'")
- .where('TABLE_SCHEMA = ?', Gitlab::Database.database_name)
- .where("GRANTEE = CONCAT('\\'', REPLACE(CURRENT_USER(), '@', '\\'@\\''), '\\'')")
- ]
+ begin
+ from(nil)
+ .pluck("has_table_privilege(#{quoted_table}, 'TRIGGER')")
+ .first
+ rescue ActiveRecord::StatementInvalid
+ # This error is raised when using a non-existing table name. In this
+ # case we just want to return false as a user technically can't
+ # create triggers for such a table.
+ false
+ end
+ else
+ queries = [
+ Grant.select(1)
+ .from('information_schema.user_privileges')
+ .where("PRIVILEGE_TYPE = 'SUPER'")
+ .where("GRANTEE = CONCAT('\\'', REPLACE(CURRENT_USER(), '@', '\\'@\\''), '\\'')"),
- union = SQL::Union.new(queries).to_sql
+ Grant.select(1)
+ .from('information_schema.schema_privileges')
+ .where("PRIVILEGE_TYPE = 'TRIGGER'")
+ .where('TABLE_SCHEMA = ?', Gitlab::Database.database_name)
+ .where("GRANTEE = CONCAT('\\'', REPLACE(CURRENT_USER(), '@', '\\'@\\''), '\\'')")
+ ]
- Grant.from("(#{union}) privs")
- end
+ union = SQL::Union.new(queries).to_sql
- priv.any?
+ Grant.from("(#{union}) privs").any?
+ end
end
end
end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 592a1956ceb..dbe6259fce7 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -525,8 +525,9 @@ module Gitlab
install_rename_triggers(table, column, temp_column)
# Schedule the jobs that will copy the data from the old column to the
- # new one.
- relation.each_batch(of: batch_size) do |batch, index|
+ # new one. Rows with NULL values in our source column are skipped since
+ # the target column is already NULL at this point.
+ relation.where.not(column => nil).each_batch(of: batch_size) do |batch, index|
start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
max_index = index
diff --git a/lib/gitlab/git/attributes_at_ref_parser.rb b/lib/gitlab/git/attributes_at_ref_parser.rb
new file mode 100644
index 00000000000..26b5bd520d5
--- /dev/null
+++ b/lib/gitlab/git/attributes_at_ref_parser.rb
@@ -0,0 +1,14 @@
+module Gitlab
+ module Git
+ # Parses root .gitattributes file at a given ref
+ class AttributesAtRefParser
+ delegate :attributes, to: :@parser
+
+ def initialize(repository, ref)
+ blob = repository.blob_at(ref, '.gitattributes')
+
+ @parser = AttributesParser.new(blob&.data)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/attributes.rb b/lib/gitlab/git/attributes_parser.rb
index 2d20cd473a7..d8aeabb6cba 100644
--- a/lib/gitlab/git/attributes.rb
+++ b/lib/gitlab/git/attributes_parser.rb
@@ -1,42 +1,26 @@
-# Gitaly note: JV: not sure what to make of this class. Why does it use
-# the full disk path of the repository to look up attributes This is
-# problematic in Gitaly, because Gitaly hides the full disk path to the
-# repository from gitlab-ce.
-
module Gitlab
module Git
# Class for parsing Git attribute files and extracting the attributes for
# file patterns.
- #
- # Unlike Rugged this parser only needs a single IO call (a call to `open`),
- # vastly reducing the time spent in extracting attributes.
- #
- # This class _only_ supports parsing the attributes file located at
- # `$GIT_DIR/info/attributes` as GitLab doesn't use any other files
- # (`.gitattributes` is copied to this particular path).
- #
- # Basic usage:
- #
- # attributes = Gitlab::Git::Attributes.new(some_repo.path)
- #
- # attributes.attributes('README.md') # => { "eol" => "lf }
- class Attributes
- # path - The path to the Git repository.
- def initialize(path)
- @path = File.expand_path(path)
- @patterns = nil
+ class AttributesParser
+ def initialize(attributes_data)
+ @data = attributes_data || ""
+
+ if @data.is_a?(File)
+ @patterns = parse_file
+ end
end
# Returns all the Git attributes for the given path.
#
- # path - A path to a file for which to get the attributes.
+ # file_path - A path to a file for which to get the attributes.
#
# Returns a Hash.
- def attributes(path)
- full_path = File.join(@path, path)
+ def attributes(file_path)
+ absolute_path = File.join('/', file_path)
patterns.each do |pattern, attrs|
- return attrs if File.fnmatch?(pattern, full_path)
+ return attrs if File.fnmatch?(pattern, absolute_path)
end
{}
@@ -98,16 +82,10 @@ module Gitlab
# Iterates over every line in the attributes file.
def each_line
- full_path = File.join(@path, 'info/attributes')
+ @data.each_line do |line|
+ break unless line.valid_encoding?
- return unless File.exist?(full_path)
-
- File.open(full_path, 'r') do |handle|
- handle.each_line do |line|
- break unless line.valid_encoding?
-
- yield line.strip
- end
+ yield line.strip
end
end
@@ -125,7 +103,8 @@ module Gitlab
parsed = attrs ? parse_attributes(attrs) : {}
- pairs << [File.join(@path, pattern), parsed]
+ absolute_pattern = File.join('/', pattern)
+ pairs << [absolute_pattern, parsed]
end
# Newer entries take precedence over older entries.
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index 031fccba92b..13120120223 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -34,7 +34,7 @@ module Gitlab
def raw(repository, sha)
Gitlab::GitalyClient.migrate(:git_blob_raw) do |is_enabled|
if is_enabled
- Gitlab::GitalyClient::BlobService.new(repository).get_blob(oid: sha, limit: MAX_DATA_DISPLAY_SIZE)
+ repository.gitaly_blob_client.get_blob(oid: sha, limit: MAX_DATA_DISPLAY_SIZE)
else
rugged_raw(repository, sha, limit: MAX_DATA_DISPLAY_SIZE)
end
@@ -70,11 +70,17 @@ module Gitlab
# Returns array of Gitlab::Git::Blob
# Does not guarantee blob data will be set
def batch_lfs_pointers(repository, blob_ids)
- blob_ids.lazy
- .select { |sha| possible_lfs_blob?(repository, sha) }
- .map { |sha| rugged_raw(repository, sha, limit: LFS_POINTER_MAX_SIZE) }
- .select(&:lfs_pointer?)
- .force
+ repository.gitaly_migrate(:batch_lfs_pointers) do |is_enabled|
+ if is_enabled
+ repository.gitaly_blob_client.batch_lfs_pointers(blob_ids.to_a)
+ else
+ blob_ids.lazy
+ .select { |sha| possible_lfs_blob?(repository, sha) }
+ .map { |sha| rugged_raw(repository, sha, limit: LFS_POINTER_MAX_SIZE) }
+ .select(&:lfs_pointer?)
+ .force
+ end
+ end
end
def binary?(data)
@@ -132,6 +138,8 @@ module Gitlab
end
def find_by_gitaly(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE)
+ return unless path
+
path = path.sub(/\A\/*/, '')
path = '/' if path.empty?
name = File.basename(path)
@@ -173,6 +181,8 @@ module Gitlab
end
def find_by_rugged(repository, sha, path, limit:)
+ return unless path
+
rugged_commit = repository.lookup(sha)
root_tree = rugged_commit.tree
@@ -254,7 +264,7 @@ module Gitlab
Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled|
@data = begin
if is_enabled
- Gitlab::GitalyClient::BlobService.new(repository).get_blob(oid: id, limit: -1).data
+ repository.gitaly_blob_client.get_blob(oid: id, limit: -1).data
else
repository.lookup(id).content
end
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 46e0c0e82a2..768617e2cae 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -239,6 +239,24 @@ module Gitlab
end
end
end
+
+ def extract_signature(repository, commit_id)
+ repository.gitaly_migrate(:extract_commit_signature) do |is_enabled|
+ if is_enabled
+ repository.gitaly_commit_client.extract_signature(commit_id)
+ else
+ rugged_extract_signature(repository, commit_id)
+ end
+ end
+ end
+
+ def rugged_extract_signature(repository, commit_id)
+ begin
+ Rugged::Commit.extract_signature(repository.rugged, commit_id)
+ rescue Rugged::OdbError
+ nil
+ end
+ end
end
def initialize(repository, raw_commit, head = nil)
diff --git a/lib/gitlab/git/info_attributes.rb b/lib/gitlab/git/info_attributes.rb
new file mode 100644
index 00000000000..e79a440950b
--- /dev/null
+++ b/lib/gitlab/git/info_attributes.rb
@@ -0,0 +1,49 @@
+# Gitaly note: JV: not sure what to make of this class. Why does it use
+# the full disk path of the repository to look up attributes This is
+# problematic in Gitaly, because Gitaly hides the full disk path to the
+# repository from gitlab-ce.
+
+module Gitlab
+ module Git
+ # Parses gitattributes at `$GIT_DIR/info/attributes`
+ #
+ # Unlike Rugged this parser only needs a single IO call (a call to `open`),
+ # vastly reducing the time spent in extracting attributes.
+ #
+ # This class _only_ supports parsing the attributes file located at
+ # `$GIT_DIR/info/attributes` as GitLab doesn't use any other files
+ # (`.gitattributes` is copied to this particular path).
+ #
+ # Basic usage:
+ #
+ # attributes = Gitlab::Git::InfoAttributes.new(some_repo.path)
+ #
+ # attributes.attributes('README.md') # => { "eol" => "lf }
+ class InfoAttributes
+ delegate :attributes, :patterns, to: :parser
+
+ # path - The path to the Git repository.
+ def initialize(path)
+ @repo_path = File.expand_path(path)
+ end
+
+ def parser
+ @parser ||= begin
+ if File.exist?(attributes_path)
+ File.open(attributes_path, 'r') do |file_handle|
+ AttributesParser.new(file_handle)
+ end
+ else
+ AttributesParser.new("")
+ end
+ end
+ end
+
+ private
+
+ def attributes_path
+ @attributes_path ||= File.join(@repo_path, 'info/attributes')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/ref.rb b/lib/gitlab/git/ref.rb
index 372ce005b94..a3ba9475ad0 100644
--- a/lib/gitlab/git/ref.rb
+++ b/lib/gitlab/git/ref.rb
@@ -33,9 +33,9 @@ module Gitlab
object
end
- def initialize(repository, name, target, derefenced_target)
+ def initialize(repository, name, target, dereferenced_target)
@name = Gitlab::Git.ref_name(name)
- @dereferenced_target = derefenced_target
+ @dereferenced_target = dereferenced_target
@target = if target.respond_to?(:oid)
target.oid
elsif target.respond_to?(:name)
diff --git a/lib/gitlab/git/remote_mirror.rb b/lib/gitlab/git/remote_mirror.rb
index 38e9d2a8554..ebe46722890 100644
--- a/lib/gitlab/git/remote_mirror.rb
+++ b/lib/gitlab/git/remote_mirror.rb
@@ -6,7 +6,23 @@ module Gitlab
@ref_name = ref_name
end
- def update(only_branches_matching: [], only_tags_matching: [])
+ def update(only_branches_matching: [])
+ @repository.gitaly_migrate(:remote_update_remote_mirror) do |is_enabled|
+ if is_enabled
+ gitaly_update(only_branches_matching)
+ else
+ rugged_update(only_branches_matching)
+ end
+ end
+ end
+
+ private
+
+ def gitaly_update(only_branches_matching)
+ @repository.gitaly_remote_client.update_remote_mirror(@ref_name, only_branches_matching)
+ end
+
+ def rugged_update(only_branches_matching)
local_branches = refs_obj(@repository.local_branches, only_refs_matching: only_branches_matching)
remote_branches = refs_obj(@repository.remote_branches(@ref_name), only_refs_matching: only_branches_matching)
@@ -15,8 +31,8 @@ module Gitlab
delete_refs(local_branches, remote_branches)
- local_tags = refs_obj(@repository.tags, only_refs_matching: only_tags_matching)
- remote_tags = refs_obj(@repository.remote_tags(@ref_name), only_refs_matching: only_tags_matching)
+ local_tags = refs_obj(@repository.tags)
+ remote_tags = refs_obj(@repository.remote_tags(@ref_name))
updated_tags = changed_refs(local_tags, remote_tags)
@repository.push_remote_branches(@ref_name, updated_tags.keys) if updated_tags.present?
@@ -24,8 +40,6 @@ module Gitlab
delete_refs(local_tags, remote_tags)
end
- private
-
def refs_obj(refs, only_refs_matching: [])
refs.each_with_object({}) do |ref, refs|
next if only_refs_matching.present? && !only_refs_matching.include?(ref.name)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index d0467bca992..638d335b523 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -102,7 +102,7 @@ module Gitlab
)
@path = File.join(storage_path, @relative_path)
@name = @relative_path.split("/").last
- @attributes = Gitlab::Git::Attributes.new(path)
+ @attributes = Gitlab::Git::InfoAttributes.new(path)
end
def ==(other)
@@ -133,7 +133,7 @@ module Gitlab
end
def exists?
- Gitlab::GitalyClient.migrate(:repository_exists) do |enabled|
+ Gitlab::GitalyClient.migrate(:repository_exists, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled|
if enabled
gitaly_repository_client.exists?
else
@@ -563,6 +563,8 @@ module Gitlab
return false if ancestor_id.nil? || descendant_id.nil?
merge_base_commit(ancestor_id, descendant_id) == ancestor_id
+ rescue Rugged::OdbError
+ false
end
# Returns true is +from+ is direct ancestor to +to+, otherwise false
@@ -621,37 +623,6 @@ module Gitlab
end
end
- # Returns branch names collection that contains the special commit(SHA1
- # or name)
- #
- # Ex.
- # repo.branch_names_contains('master')
- #
- def branch_names_contains(commit)
- branches_contains(commit).map { |c| c.name }
- end
-
- # Returns branch collection that contains the special commit(SHA1 or name)
- #
- # Ex.
- # repo.branch_names_contains('master')
- #
- def branches_contains(commit)
- commit_obj = rugged.rev_parse(commit)
- parent = commit_obj.parents.first unless commit_obj.parents.empty?
-
- walker = Rugged::Walker.new(rugged)
-
- rugged.branches.select do |branch|
- walker.push(branch.target_id)
- walker.hide(parent) if parent
- result = walker.any? { |c| c.oid == commit_obj.oid }
- walker.reset
-
- result
- end
- end
-
# Get refs hash which key is SHA1
# and value is a Rugged::Reference
def refs_hash
@@ -1026,6 +997,18 @@ module Gitlab
attributes(path)[name]
end
+ # Check .gitattributes for a given ref
+ #
+ # This only checks the root .gitattributes file,
+ # it does not traverse subfolders to find additional .gitattributes files
+ #
+ # This method is around 30 times slower than `attributes`,
+ # which uses `$GIT_DIR/info/attributes`
+ def attributes_at(ref, file_path)
+ parser = AttributesAtRefParser.new(self, ref)
+ parser.attributes(file_path)
+ end
+
def languages(ref = nil)
Gitlab::GitalyClient.migrate(:commit_languages) do |is_enabled|
if is_enabled
@@ -1126,19 +1109,6 @@ module Gitlab
end
end
- def shell_write_ref(ref_path, ref, old_ref)
- raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
- raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
- raise ArgumentError, "invalid old_ref #{old_ref.inspect}" if !old_ref.nil? && old_ref.include?("\x00")
-
- input = "update #{ref_path}\x00#{ref}\x00#{old_ref}\x00"
- run_git!(%w[update-ref --stdin -z]) { |stdin| stdin.write(input) }
- end
-
- def rugged_write_ref(ref_path, ref)
- rugged.references.create(ref_path, ref, force: true)
- end
-
def fetch_ref(source_repository, source_ref:, target_ref:)
Gitlab::Git.check_namespace!(source_repository)
source_repository = RemoteRepository.new(source_repository) unless source_repository.is_a?(RemoteRepository)
@@ -1158,23 +1128,6 @@ module Gitlab
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
- 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(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
popen_with_timeout([Gitlab.config.git.bin_path, *args], timeout, path, env)
@@ -1235,6 +1188,19 @@ module Gitlab
end
end
+ def create_from_bundle(bundle_path)
+ gitaly_migrate(:create_repo_from_bundle) do |is_enabled|
+ if is_enabled
+ gitaly_repository_client.create_from_bundle(bundle_path)
+ else
+ run_git!(%W(clone --bare -- #{bundle_path} #{path}), chdir: nil)
+ self.class.create_hooks(path, File.expand_path(Gitlab.config.gitlab_shell.hooks_path))
+ end
+ end
+
+ true
+ end
+
def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
gitaly_migrate(:rebase) do |is_enabled|
if is_enabled
@@ -1316,42 +1282,41 @@ module Gitlab
success || gitlab_projects_error
end
+ def bundle_to_disk(save_path)
+ gitaly_migrate(:bundle_to_disk) do |is_enabled|
+ if is_enabled
+ gitaly_repository_client.create_bundle(save_path)
+ else
+ run_git!(%W(bundle create #{save_path} --all))
+ end
+ end
+
+ true
+ end
+
# rubocop:disable Metrics/ParameterLists
def multi_action(
user, branch_name:, message:, actions:,
author_email: nil, author_name: nil,
start_branch_name: nil, start_repository: self)
- OperationService.new(user, self).with_branch(
- branch_name,
- start_branch_name: start_branch_name,
- start_repository: start_repository
- ) do |start_commit|
- index = Gitlab::Git::Index.new(self)
- parents = []
-
- if start_commit
- index.read_tree(start_commit.rugged_commit.tree)
- parents = [start_commit.sha]
+ gitaly_migrate(:operation_user_commit_files) do |is_enabled|
+ if is_enabled
+ gitaly_operation_client.user_commit_files(user, branch_name,
+ message, actions, author_email, author_name,
+ start_branch_name, start_repository)
+ else
+ rugged_multi_action(user, branch_name, message, actions,
+ author_email, author_name, start_branch_name, start_repository)
end
-
- actions.each { |opts| index.apply(opts.delete(:action), opts) }
-
- committer = user_to_committer(user)
- author = Gitlab::Git.committer_hash(email: author_email, name: author_name) || committer
- options = {
- tree: index.write_tree,
- message: message,
- parents: parents,
- author: author,
- committer: committer
- }
-
- create_commit(options)
end
end
# rubocop:enable Metrics/ParameterLists
+ def write_config(full_path:)
+ rugged.config['gitlab.fullpath'] = full_path if full_path.present?
+ end
+
def gitaly_repository
Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository)
end
@@ -1380,6 +1345,10 @@ module Gitlab
@gitaly_remote_client ||= Gitlab::GitalyClient::RemoteService.new(self)
end
+ def gitaly_blob_client
+ @gitaly_blob_client ||= Gitlab::GitalyClient::BlobService.new(self)
+ end
+
def gitaly_conflicts_client(our_commit_oid, their_commit_oid)
Gitlab::GitalyClient::ConflictsService.new(self, our_commit_oid, their_commit_oid)
end
@@ -1394,8 +1363,89 @@ module Gitlab
raise CommandError.new(e)
end
+ def refs_contains_sha(ref_type, sha)
+ args = %W(#{ref_type} --contains #{sha})
+ names = run_git(args).first
+
+ if names.respond_to?(:split)
+ names = names.split("\n").map(&:strip)
+
+ names.each do |name|
+ name.slice! '* '
+ end
+
+ names
+ else
+ []
+ end
+ end
+
+ def search_files_by_content(query, ref)
+ return [] if empty? || query.blank?
+
+ offset = 2
+ args = %W(grep -i -I -n -z --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
+
+ run_git(args).first.scrub.split(/^--$/)
+ end
+
+ def search_files_by_name(query, ref)
+ safe_query = Regexp.escape(query.sub(/^\/*/, ""))
+
+ return [] if empty? || safe_query.blank?
+
+ args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{safe_query})
+
+ run_git(args).first.lines.map(&:strip)
+ end
+
+ def find_commits_by_message(query, ref, path, limit, offset)
+ gitaly_migrate(:commits_by_message) do |is_enabled|
+ if is_enabled
+ find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
+ else
+ find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
+ end
+ end
+ end
+
private
+ def shell_write_ref(ref_path, ref, old_ref)
+ raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
+ raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
+ raise ArgumentError, "invalid old_ref #{old_ref.inspect}" if !old_ref.nil? && old_ref.include?("\x00")
+
+ input = "update #{ref_path}\x00#{ref}\x00#{old_ref}\x00"
+ run_git!(%w[update-ref --stdin -z]) { |stdin| stdin.write(input) }
+ end
+
+ def rugged_write_ref(ref_path, ref)
+ rugged.references.create(ref_path, ref, force: true)
+ rescue Rugged::ReferenceError => ex
+ Rails.logger.error "Unable to create #{ref_path} reference for repository #{path}: #{ex}"
+ rescue Rugged::OSError => ex
+ raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
+
+ Rails.logger.error "Unable to create #{ref_path} reference for repository #{path}: #{ex}"
+ end
+
+ 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(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
+
def fresh_worktree?(path)
File.exist?(path) && !clean_stuck_worktree(path)
end
@@ -2131,6 +2181,39 @@ module Gitlab
remove_remote(remote_name)
end
+ def rugged_multi_action(
+ user, branch_name, message, actions, author_email, author_name,
+ 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|
+ index = Gitlab::Git::Index.new(self)
+ parents = []
+
+ if start_commit
+ index.read_tree(start_commit.rugged_commit.tree)
+ parents = [start_commit.sha]
+ end
+
+ actions.each { |opts| index.apply(opts.delete(:action), opts) }
+
+ committer = user_to_committer(user)
+ author = Gitlab::Git.committer_hash(email: author_email, name: author_name) || committer
+ options = {
+ tree: index.write_tree,
+ message: message,
+ parents: parents,
+ author: author,
+ committer: committer
+ }
+
+ create_commit(options)
+ end
+ end
+
def fetch_remote(remote_name = 'origin', env: nil)
run_git(['fetch', remote_name], env: env).last.zero?
end
@@ -2138,6 +2221,26 @@ module Gitlab
def gitlab_projects_error
raise CommandError, @gitlab_projects.output
end
+
+ def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
+ ref ||= root_ref
+
+ args = %W(
+ log #{ref} --pretty=%H --skip #{offset}
+ --max-count #{limit} --grep=#{query} --regexp-ignore-case
+ )
+ args = args.concat(%W(-- #{path})) if path.present?
+
+ git_log_results = run_git(args).first.lines
+
+ git_log_results.map { |c| commit(c.chomp) }.compact
+ end
+
+ def find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
+ gitaly_commit_client
+ .commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset)
+ .map { |c| commit(c) }
+ end
end
end
end
diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb
index 4974205b8fd..f8b2e7e0e21 100644
--- a/lib/gitlab/git/rev_list.rb
+++ b/lib/gitlab/git/rev_list.rb
@@ -95,7 +95,7 @@ module Gitlab
object_output.map do |output_line|
sha, path = output_line.split(' ', 2)
- next if require_path && path.blank?
+ next if require_path && path.to_s.empty?
sha
end.reject(&:nil?)
diff --git a/lib/gitlab/git/wiki_page.rb b/lib/gitlab/git/wiki_page.rb
index a06bac4414f..669ae11a423 100644
--- a/lib/gitlab/git/wiki_page.rb
+++ b/lib/gitlab/git/wiki_page.rb
@@ -1,7 +1,7 @@
module Gitlab
module Git
class WikiPage
- attr_reader :url_path, :title, :format, :path, :version, :raw_data, :name, :text_data, :historical
+ attr_reader :url_path, :title, :format, :path, :version, :raw_data, :name, :text_data, :historical, :formatted_data
# This class is meant to be serializable so that it can be constructed
# by Gitaly and sent over the network to GitLab.
@@ -21,6 +21,7 @@ module Gitlab
@raw_data = gollum_page.raw_data
@name = gollum_page.name
@historical = gollum_page.historical?
+ @formatted_data = gollum_page.formatted_data if gollum_page.is_a?(Gollum::Page)
@version = version
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 4507ea923b4..6bd256f57c7 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -1,6 +1,8 @@
require 'base64'
require 'gitaly'
+require 'grpc/health/v1/health_pb'
+require 'grpc/health/v1/health_services_pb'
module Gitlab
module GitalyClient
@@ -69,14 +71,27 @@ module Gitlab
@stubs ||= {}
@stubs[storage] ||= {}
@stubs[storage][name] ||= begin
- klass = Gitaly.const_get(name.to_s.camelcase.to_sym).const_get(:Stub)
- addr = address(storage)
- addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp'
+ klass = stub_class(name)
+ addr = stub_address(storage)
klass.new(addr, :this_channel_is_insecure)
end
end
end
+ def self.stub_class(name)
+ if name == :health_check
+ Grpc::Health::V1::Health::Stub
+ else
+ Gitaly.const_get(name.to_s.camelcase.to_sym).const_get(:Stub)
+ end
+ end
+
+ def self.stub_address(storage)
+ addr = address(storage)
+ addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp'
+ addr
+ end
+
def self.clear_stubs!
MUTEX.synchronize do
@stubs = nil
diff --git a/lib/gitlab/gitaly_client/blob_service.rb b/lib/gitlab/gitaly_client/blob_service.rb
index a250eb75bd4..d70a1a7665e 100644
--- a/lib/gitlab/gitaly_client/blob_service.rb
+++ b/lib/gitlab/gitaly_client/blob_service.rb
@@ -32,6 +32,28 @@ module Gitlab
binary: Gitlab::Git::Blob.binary?(data)
)
end
+
+ def batch_lfs_pointers(blob_ids)
+ return [] if blob_ids.empty?
+
+ request = Gitaly::GetLFSPointersRequest.new(
+ repository: @gitaly_repo,
+ blob_ids: blob_ids
+ )
+
+ response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request)
+
+ response.flat_map do |message|
+ message.lfs_pointers.map do |lfs_pointer|
+ Gitlab::Git::Blob.new(
+ id: lfs_pointer.oid,
+ size: lfs_pointer.size,
+ data: lfs_pointer.data,
+ binary: Gitlab::Git::Blob.binary?(lfs_pointer.data)
+ )
+ end
+ end
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 71b212023d6..cadc7149301 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -38,19 +38,27 @@ module Gitlab
from_id = case from
when NilClass
EMPTY_TREE_ID
- when Rugged::Commit
- from.oid
else
- from
+ if from.respond_to?(:oid)
+ # This is meant to match a Rugged::Commit. This should be impossible in
+ # the future.
+ from.oid
+ else
+ from
+ end
end
to_id = case to
when NilClass
EMPTY_TREE_ID
- when Rugged::Commit
- to.oid
else
- to
+ if to.respond_to?(:oid)
+ # This is meant to match a Rugged::Commit. This should be impossible in
+ # the future.
+ to.oid
+ else
+ to
+ end
end
request_params = diff_between_commits_request_params(from_id, to_id, options)
@@ -125,11 +133,11 @@ module Gitlab
def commit_count(ref, options = {})
request = Gitaly::CountCommitsRequest.new(
repository: @gitaly_repo,
- revision: ref
+ revision: encode_binary(ref)
)
request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present?
request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present?
- request.path = options[:path] if options[:path].present?
+ request.path = encode_binary(options[:path]) if options[:path].present?
request.max_count = options[:max_count] if options[:max_count].present?
GitalyClient.call(@repository.storage, :commit_service, :count_commits, request, timeout: GitalyClient.medium_timeout).count
@@ -282,6 +290,23 @@ module Gitlab
end
end
+ def extract_signature(commit_id)
+ request = Gitaly::ExtractCommitSignatureRequest.new(repository: @gitaly_repo, commit_id: commit_id)
+ response = GitalyClient.call(@repository.storage, :commit_service, :extract_commit_signature, request)
+
+ signature = ''.b
+ signed_text = ''.b
+
+ response.each do |message|
+ signature << message.signature
+ signed_text << message.signed_text
+ end
+
+ return if signature.blank? && signed_text.blank?
+
+ [signature, signed_text]
+ end
+
private
def call_commit_diff(request_params, options = {})
diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb
index 2565d537aff..e14734495a8 100644
--- a/lib/gitlab/gitaly_client/conflicts_service.rb
+++ b/lib/gitlab/gitaly_client/conflicts_service.rb
@@ -25,6 +25,11 @@ module Gitlab
def conflicts?
list_conflict_files.any?
+ rescue GRPC::FailedPrecondition
+ # The server raises this exception when it encounters ConflictSideMissing, which
+ # means a conflict exists but its `theirs` or `ours` data is nil due to a non-existent
+ # file in one of the trees.
+ true
end
def resolve_conflicts(target_repository, resolution, source_branch, target_branch)
diff --git a/lib/gitlab/gitaly_client/health_check_service.rb b/lib/gitlab/gitaly_client/health_check_service.rb
new file mode 100644
index 00000000000..6c1213f5e20
--- /dev/null
+++ b/lib/gitlab/gitaly_client/health_check_service.rb
@@ -0,0 +1,19 @@
+module Gitlab
+ module GitalyClient
+ class HealthCheckService
+ def initialize(storage)
+ @storage = storage
+ end
+
+ # Sends a gRPC health ping to the Gitaly server for the storage shard.
+ def check
+ request = Grpc::Health::V1::HealthCheckRequest.new
+ response = GitalyClient.call(@storage, :health_check, :check, request, timeout: GitalyClient.fast_timeout)
+
+ { success: response&.status == :SERVING }
+ rescue GRPC::BadStatus => e
+ { success: false, message: e.to_s }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 7319de69d13..c2b4155e6a5 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -3,6 +3,8 @@ module Gitlab
class OperationService
include Gitlab::EncodingHelper
+ MAX_MSG_SIZE = 128.kilobytes.freeze
+
def initialize(repository)
@gitaly_repo = repository.gitaly_repository
@repository = repository
@@ -175,6 +177,49 @@ module Gitlab
end
end
+ def user_commit_files(
+ user, branch_name, commit_message, actions, author_email, author_name,
+ start_branch_name, start_repository)
+
+ req_enum = Enumerator.new do |y|
+ header = user_commit_files_request_header(user, branch_name,
+ commit_message, actions, author_email, author_name,
+ start_branch_name, start_repository)
+
+ y.yield Gitaly::UserCommitFilesRequest.new(header: header)
+
+ actions.each do |action|
+ action_header = user_commit_files_action_header(action)
+ y.yield Gitaly::UserCommitFilesRequest.new(
+ action: Gitaly::UserCommitFilesAction.new(header: action_header)
+ )
+
+ reader = binary_stringio(action[:content])
+
+ until reader.eof?
+ chunk = reader.read(MAX_MSG_SIZE)
+
+ y.yield Gitaly::UserCommitFilesRequest.new(
+ action: Gitaly::UserCommitFilesAction.new(content: chunk)
+ )
+ end
+ end
+ end
+
+ response = GitalyClient.call(@repository.storage, :operation_service,
+ :user_commit_files, req_enum, remote_storage: start_repository.storage)
+
+ if (pre_receive_error = response.pre_receive_error.presence)
+ raise Gitlab::Git::HooksService::PreReceiveError, pre_receive_error
+ end
+
+ if (index_error = response.index_error.presence)
+ raise Gitlab::Git::Index::IndexError, index_error
+ end
+
+ Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update)
+ end
+
private
def call_cherry_pick_or_revert(rpc, user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
@@ -212,6 +257,33 @@ module Gitlab
Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update)
end
end
+
+ def user_commit_files_request_header(
+ user, branch_name, commit_message, actions, author_email, author_name,
+ start_branch_name, start_repository)
+
+ Gitaly::UserCommitFilesRequestHeader.new(
+ repository: @gitaly_repo,
+ user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
+ branch_name: encode_binary(branch_name),
+ commit_message: encode_binary(commit_message),
+ commit_author_name: encode_binary(author_name),
+ commit_author_email: encode_binary(author_email),
+ start_branch_name: encode_binary(start_branch_name),
+ start_repository: start_repository.gitaly_repository
+ )
+ end
+
+ def user_commit_files_action_header(action)
+ Gitaly::UserCommitFilesActionHeader.new(
+ action: action[:action].upcase.to_sym,
+ file_path: encode_binary(action[:file_path]),
+ previous_path: encode_binary(action[:previous_path]),
+ base64_content: action[:encoding] == 'base64'
+ )
+ rescue RangeError
+ raise ArgumentError, "Unknown action '#{action[:action]}'"
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/remote_service.rb b/lib/gitlab/gitaly_client/remote_service.rb
index e58f641d69a..58c356edfd1 100644
--- a/lib/gitlab/gitaly_client/remote_service.rb
+++ b/lib/gitlab/gitaly_client/remote_service.rb
@@ -1,6 +1,8 @@
module Gitlab
module GitalyClient
class RemoteService
+ MAX_MSG_SIZE = 128.kilobytes.freeze
+
def initialize(repository)
@repository = repository
@gitaly_repo = repository.gitaly_repository
@@ -38,6 +40,31 @@ module Gitlab
response.result
end
+
+ def update_remote_mirror(ref_name, only_branches_matching)
+ req_enum = Enumerator.new do |y|
+ y.yield Gitaly::UpdateRemoteMirrorRequest.new(
+ repository: @gitaly_repo,
+ ref_name: ref_name
+ )
+
+ current_size = 0
+
+ slices = only_branches_matching.slice_before do |branch_name|
+ current_size += branch_name.bytesize
+
+ next false if current_size < MAX_MSG_SIZE
+
+ current_size = 0
+ end
+
+ slices.each do |slice|
+ y.yield Gitaly::UpdateRemoteMirrorRequest.new(only_branches_matching: slice)
+ end
+ end
+
+ GitalyClient.call(@storage, :remote_service, :update_remote_mirror, req_enum)
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index 12016aee2a6..b0dbaf11598 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -3,6 +3,8 @@ module Gitlab
class RepositoryService
include Gitlab::EncodingHelper
+ MAX_MSG_SIZE = 128.kilobytes.freeze
+
def initialize(repository)
@repository = repository
@gitaly_repo = repository.gitaly_repository
@@ -161,6 +163,46 @@ module Gitlab
return response.error.b, 1
end
end
+
+ def create_bundle(save_path)
+ request = Gitaly::CreateBundleRequest.new(repository: @gitaly_repo)
+ response = GitalyClient.call(
+ @storage,
+ :repository_service,
+ :create_bundle,
+ request,
+ timeout: GitalyClient.default_timeout
+ )
+
+ File.open(save_path, 'wb') do |f|
+ response.each do |message|
+ f.write(message.data)
+ end
+ end
+ end
+
+ def create_from_bundle(bundle_path)
+ request = Gitaly::CreateRepositoryFromBundleRequest.new(repository: @gitaly_repo)
+ enum = Enumerator.new do |y|
+ File.open(bundle_path, 'rb') do |f|
+ while data = f.read(MAX_MSG_SIZE)
+ request.data = data
+
+ y.yield request
+
+ request = Gitaly::CreateRepositoryFromBundleRequest.new
+ end
+ end
+ end
+
+ GitalyClient.call(
+ @storage,
+ :repository_service,
+ :create_repository_from_bundle,
+ enum,
+ timeout: GitalyClient.default_timeout
+ )
+ end
end
end
end
diff --git a/lib/gitlab/github_import/importer/pull_requests_importer.rb b/lib/gitlab/github_import/importer/pull_requests_importer.rb
index 5437e32e9f1..e70361c163b 100644
--- a/lib/gitlab/github_import/importer/pull_requests_importer.rb
+++ b/lib/gitlab/github_import/importer/pull_requests_importer.rb
@@ -57,10 +57,7 @@ module Gitlab
end
def commit_exists?(sha)
- project.repository.lookup(sha)
- true
- rescue Rugged::Error
- false
+ project.repository.commit(sha).present?
end
def collection_method
diff --git a/lib/gitlab/gpg/commit.rb b/lib/gitlab/gpg/commit.rb
index 0f4ba6f83fc..672b5579dfd 100644
--- a/lib/gitlab/gpg/commit.rb
+++ b/lib/gitlab/gpg/commit.rb
@@ -4,12 +4,8 @@ module Gitlab
def initialize(commit)
@commit = commit
- @signature_text, @signed_text =
- begin
- Rugged::Commit.extract_signature(@commit.project.repository.rugged, @commit.sha)
- rescue Rugged::OdbError
- nil
- end
+ repo = commit.project.repository.raw_repository
+ @signature_text, @signed_text = Gitlab::Git::Commit.extract_signature(repo, commit.sha)
end
def has_signature?
diff --git a/lib/gitlab/health_checks/gitaly_check.rb b/lib/gitlab/health_checks/gitaly_check.rb
new file mode 100644
index 00000000000..11416c002e3
--- /dev/null
+++ b/lib/gitlab/health_checks/gitaly_check.rb
@@ -0,0 +1,53 @@
+module Gitlab
+ module HealthChecks
+ class GitalyCheck
+ extend BaseAbstractCheck
+
+ METRIC_PREFIX = 'gitaly_health_check'.freeze
+
+ class << self
+ def readiness
+ repository_storages.map do |storage_name|
+ check(storage_name)
+ end
+ end
+
+ def metrics
+ repository_storages.flat_map do |storage_name|
+ result, elapsed = with_timing { check(storage_name) }
+ labels = { shard: storage_name }
+
+ [
+ metric("#{metric_prefix}_success", successful?(result) ? 1 : 0, **labels),
+ metric("#{metric_prefix}_latency_seconds", elapsed, **labels)
+ ].flatten
+ end
+ end
+
+ def check(storage_name)
+ serv = Gitlab::GitalyClient::HealthCheckService.new(storage_name)
+ result = serv.check
+ HealthChecks::Result.new(result[:success], result[:message], shard: storage_name)
+ end
+
+ private
+
+ def metric_prefix
+ METRIC_PREFIX
+ end
+
+ def successful?(result)
+ result[:success]
+ end
+
+ def repository_storages
+ storages.keys
+ end
+
+ def storages
+ Gitlab.config.repositories.storages
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb
index dd5d35feab9..2f163db936b 100644
--- a/lib/gitlab/import_export/command_line_util.rb
+++ b/lib/gitlab/import_export/command_line_util.rb
@@ -11,15 +11,6 @@ module Gitlab
untar_with_options(archive: archive, dir: dir, options: 'zxf')
end
- def git_bundle(repo_path:, bundle_path:)
- execute(%W(#{git_bin_path} --git-dir=#{repo_path} bundle create #{bundle_path} --all))
- end
-
- def git_clone_bundle(repo_path:, bundle_path:)
- execute(%W(#{git_bin_path} clone --bare -- #{bundle_path} #{repo_path}))
- Gitlab::Git::Repository.create_hooks(repo_path, File.expand_path(Gitlab.config.gitlab_shell.hooks_path))
- end
-
def mkdir_p(path)
FileUtils.mkdir_p(path, mode: DEFAULT_MODE)
FileUtils.chmod(DEFAULT_MODE, path)
diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb
index 989342389bc..5c971564a73 100644
--- a/lib/gitlab/import_export/file_importer.rb
+++ b/lib/gitlab/import_export/file_importer.rb
@@ -17,12 +17,16 @@ module Gitlab
def import
mkdir_p(@shared.export_path)
+ remove_symlinks!
+
wait_for_archived_file do
decompress_archive
end
rescue => e
@shared.error(e)
false
+ ensure
+ remove_symlinks!
end
private
@@ -43,7 +47,7 @@ module Gitlab
raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result
- remove_symlinks!
+ result
end
def remove_symlinks!
diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb
index d0e5cfcfd3e..5a9bbceac67 100644
--- a/lib/gitlab/import_export/repo_restorer.rb
+++ b/lib/gitlab/import_export/repo_restorer.rb
@@ -13,7 +13,7 @@ module Gitlab
def restore
return true unless File.exist?(@path_to_bundle)
- git_clone_bundle(repo_path: @project.repository.path_to_repo, bundle_path: @path_to_bundle)
+ @project.repository.create_from_bundle(@path_to_bundle)
rescue => e
@shared.error(e)
false
diff --git a/lib/gitlab/import_export/repo_saver.rb b/lib/gitlab/import_export/repo_saver.rb
index a7028a32570..695462c7dd2 100644
--- a/lib/gitlab/import_export/repo_saver.rb
+++ b/lib/gitlab/import_export/repo_saver.rb
@@ -21,7 +21,7 @@ module Gitlab
def bundle_to_disk
mkdir_p(@shared.export_path)
- git_bundle(repo_path: path_to_repo, bundle_path: @full_path)
+ @project.repository.bundle_to_disk(@full_path)
rescue => e
@shared.error(e)
false
diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb
index 6130c124dd1..2daeba90a51 100644
--- a/lib/gitlab/import_export/saver.rb
+++ b/lib/gitlab/import_export/saver.rb
@@ -37,7 +37,7 @@ module Gitlab
end
def archive_file
- @archive_file ||= File.join(@shared.export_path, '..', Gitlab::ImportExport.export_filename(project: @project))
+ @archive_file ||= File.join(@shared.archive_path, Gitlab::ImportExport.export_filename(project: @project))
end
end
end
diff --git a/lib/gitlab/import_export/shared.rb b/lib/gitlab/import_export/shared.rb
index 9fd0b709ef2..d03cbc880fd 100644
--- a/lib/gitlab/import_export/shared.rb
+++ b/lib/gitlab/import_export/shared.rb
@@ -9,7 +9,11 @@ module Gitlab
end
def export_path
- @export_path ||= Gitlab::ImportExport.export_path(relative_path: opts[:relative_path])
+ @export_path ||= Gitlab::ImportExport.export_path(relative_path: relative_path)
+ end
+
+ def archive_path
+ @archive_path ||= Gitlab::ImportExport.export_path(relative_path: relative_archive_path)
end
def error(error)
@@ -21,6 +25,14 @@ module Gitlab
private
+ def relative_path
+ File.join(opts[:relative_path], SecureRandom.hex)
+ end
+
+ def relative_archive_path
+ File.join(opts[:relative_path], '..')
+ end
+
def error_out(message, caller)
Rails.logger.error("Import/Export error raised on #{caller}: #{message}")
end
diff --git a/lib/gitlab/import_export/wiki_repo_saver.rb b/lib/gitlab/import_export/wiki_repo_saver.rb
index 1e6722a7bba..5fa2e101e29 100644
--- a/lib/gitlab/import_export/wiki_repo_saver.rb
+++ b/lib/gitlab/import_export/wiki_repo_saver.rb
@@ -10,7 +10,7 @@ module Gitlab
def bundle_to_disk(full_path)
mkdir_p(@shared.export_path)
- git_bundle(repo_path: path_to_repo, bundle_path: full_path)
+ @wiki.repository.bundle_to_disk(full_path)
rescue => e
@shared.error(e)
false
diff --git a/lib/gitlab/o_auth.rb b/lib/gitlab/o_auth.rb
new file mode 100644
index 00000000000..5ad8d83bd6e
--- /dev/null
+++ b/lib/gitlab/o_auth.rb
@@ -0,0 +1,6 @@
+module Gitlab
+ module OAuth
+ SignupDisabledError = Class.new(StandardError)
+ SigninDisabledForProviderError = Class.new(StandardError)
+ end
+end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index d33f33d192f..fff9360ea27 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -5,8 +5,6 @@
#
module Gitlab
module OAuth
- SignupDisabledError = Class.new(StandardError)
-
class User
attr_accessor :auth_hash, :gl_user
@@ -29,7 +27,8 @@ module Gitlab
end
def save(provider = 'OAuth')
- unauthorized_to_create unless gl_user
+ raise SigninDisabledForProviderError if oauth_provider_disabled?
+ raise SignupDisabledError unless gl_user
block_after_save = needs_blocking?
@@ -226,8 +225,10 @@ module Gitlab
Gitlab::AppLogger
end
- def unauthorized_to_create
- raise SignupDisabledError
+ def oauth_provider_disabled?
+ Gitlab::CurrentSettings.current_application_settings
+ .disabled_oauth_sign_in_sources
+ .include?(auth_hash.provider)
end
end
end
diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb
index e73245b82c1..e29e168fc5a 100644
--- a/lib/gitlab/performance_bar.rb
+++ b/lib/gitlab/performance_bar.rb
@@ -6,6 +6,7 @@ module Gitlab
EXPIRY_TIME = 5.minutes
def self.enabled?(user = nil)
+ return true if Rails.env.development?
return false unless user && allowed_group_id
allowed_user_ids.include?(user.id)
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
new file mode 100644
index 00000000000..95d94b3cc68
--- /dev/null
+++ b/lib/gitlab/profiler.rb
@@ -0,0 +1,142 @@
+# coding: utf-8
+module Gitlab
+ module Profiler
+ FILTERED_STRING = '[FILTERED]'.freeze
+
+ IGNORE_BACKTRACES = %w[
+ lib/gitlab/i18n.rb
+ lib/gitlab/request_context.rb
+ config/initializers
+ lib/gitlab/database/load_balancing/
+ lib/gitlab/etag_caching/
+ lib/gitlab/metrics/
+ lib/gitlab/middleware/
+ lib/gitlab/performance_bar/
+ lib/gitlab/request_profiler/
+ lib/gitlab/profiler.rb
+ ].freeze
+
+ # Takes a URL to profile (can be a fully-qualified URL, or an absolute path)
+ # and returns the ruby-prof profile result. Formatting that result is the
+ # caller's responsibility. Requests are GET requests unless post_data is
+ # passed.
+ #
+ # Optional arguments:
+ # - logger: will be used for SQL logging, including a summary at the end of
+ # the log file of the total time spent per model class.
+ #
+ # - post_data: a string of raw POST data to use. Changes the HTTP verb to
+ # POST.
+ #
+ # - user: a user to authenticate as. Only works if the user has a valid
+ # personal access token.
+ #
+ # - private_token: instead of providing a user instance, the token can be
+ # given as a string. Takes precedence over the user option.
+ def self.profile(url, logger: nil, post_data: nil, user: nil, private_token: nil)
+ app = ActionDispatch::Integration::Session.new(Rails.application)
+ verb = :get
+ headers = {}
+
+ if post_data
+ verb = :post
+ headers['Content-Type'] = 'application/json'
+ end
+
+ if user
+ private_token ||= user.personal_access_tokens.active.pluck(:token).first
+ end
+
+ headers['Private-Token'] = private_token if private_token
+ logger = create_custom_logger(logger, private_token: private_token)
+
+ RequestStore.begin!
+
+ # Make an initial call for an asset path in development mode to avoid
+ # sprockets dominating the profiler output.
+ ActionController::Base.helpers.asset_path('katex.css') if Rails.env.development?
+
+ # Rails loads internationalization files lazily the first time a
+ # translation is needed. Running this prevents this overhead from showing
+ # up in profiles.
+ ::I18n.t('.')[:test_string]
+
+ # Remove API route mounting from the profile.
+ app.get('/api/v4/users')
+
+ result = with_custom_logger(logger) do
+ RubyProf.profile { app.public_send(verb, url, post_data, headers) } # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ RequestStore.end!
+
+ log_load_times_by_model(logger)
+
+ result
+ end
+
+ def self.create_custom_logger(logger, private_token: nil)
+ return unless logger
+
+ logger.dup.tap do |new_logger|
+ new_logger.instance_variable_set(:@private_token, private_token)
+
+ class << new_logger
+ attr_reader :load_times_by_model, :private_token
+
+ def debug(message, *)
+ message.gsub!(private_token, FILTERED_STRING) if private_token
+
+ _, type, time = *message.match(/(\w+) Load \(([0-9.]+)ms\)/)
+
+ if type && time
+ @load_times_by_model ||= {}
+ @load_times_by_model[type] ||= 0
+ @load_times_by_model[type] += time.to_f
+ end
+
+ super
+
+ backtrace = Rails.backtrace_cleaner.clean(caller)
+
+ backtrace.each do |caller_line|
+ next if caller_line.match(Regexp.union(IGNORE_BACKTRACES))
+
+ stripped_caller_line = caller_line.sub("#{Rails.root}/", '')
+
+ super(" ↳ #{stripped_caller_line}")
+ end
+ end
+ end
+ end
+ end
+
+ def self.with_custom_logger(logger)
+ original_colorize_logging = ActiveSupport::LogSubscriber.colorize_logging
+ original_activerecord_logger = ActiveRecord::Base.logger
+ original_actioncontroller_logger = ActionController::Base.logger
+
+ if logger
+ ActiveSupport::LogSubscriber.colorize_logging = false
+ ActiveRecord::Base.logger = logger
+ ActionController::Base.logger = logger
+ end
+
+ result = yield
+
+ ActiveSupport::LogSubscriber.colorize_logging = original_colorize_logging
+ ActiveRecord::Base.logger = original_activerecord_logger
+ ActionController::Base.logger = original_actioncontroller_logger
+
+ result
+ end
+
+ def self.log_load_times_by_model(logger)
+ return unless logger.respond_to?(:load_times_by_model)
+
+ logger.load_times_by_model.to_a.sort_by(&:last).reverse.each do |(model, time)|
+ logger.info("#{model} total: #{time.round(2)}ms")
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 7771b15069b..4823f703ba4 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -20,7 +20,7 @@ module Gitlab
when 'commits'
Kaminari.paginate_array(commits).page(page).per(per_page)
else
- super
+ super(scope, page, false)
end
end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 0002c7da8f1..7ab85e1c35c 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -67,7 +67,7 @@ module Gitlab
end
def build_trace_section_regex
- @build_trace_section_regexp ||= /section_((?:start)|(?:end)):(\d+):([^\r]+)\r\033\[0K/.freeze
+ @build_trace_section_regexp ||= /section_((?:start)|(?:end)):(\d+):([a-zA-Z0-9_.-]+)\r\033\[0K/.freeze
end
end
end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 70b639501fd..7362514167f 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -40,19 +40,21 @@ module Gitlab
@default_project_filter = default_project_filter
end
- def objects(scope, page = nil)
- case scope
- when 'projects'
- projects.page(page).per(per_page)
- when 'issues'
- issues.page(page).per(per_page)
- when 'merge_requests'
- merge_requests.page(page).per(per_page)
- when 'milestones'
- milestones.page(page).per(per_page)
- else
- Kaminari.paginate_array([]).page(page).per(per_page)
- end
+ def objects(scope, page = nil, without_count = true)
+ collection = case scope
+ when 'projects'
+ projects.page(page).per(per_page)
+ when 'issues'
+ issues.page(page).per(per_page)
+ when 'merge_requests'
+ merge_requests.page(page).per(per_page)
+ when 'milestones'
+ milestones.page(page).per(per_page)
+ else
+ Kaminari.paginate_array([]).page(page).per(per_page)
+ end
+
+ without_count ? collection.without_count : collection
end
def projects_count
@@ -71,18 +73,46 @@ module Gitlab
@milestones_count ||= milestones.count
end
+ def limited_projects_count
+ @limited_projects_count ||= projects.limit(count_limit).count
+ end
+
+ def limited_issues_count
+ return @limited_issues_count if @limited_issues_count
+
+ # By default getting limited count (e.g. 1000+) is fast on issuable
+ # collections except for issues, where filtering both not confidential
+ # and confidential issues user has access to, is too complex.
+ # It's faster to try to fetch all public issues first, then only
+ # if necessary try to fetch all issues.
+ sum = issues(public_only: true).limit(count_limit).count
+ @limited_issues_count = sum < count_limit ? issues.limit(count_limit).count : sum
+ end
+
+ def limited_merge_requests_count
+ @limited_merge_requests_count ||= merge_requests.limit(count_limit).count
+ end
+
+ def limited_milestones_count
+ @limited_milestones_count ||= milestones.limit(count_limit).count
+ end
+
def single_commit_result?
false
end
+ def count_limit
+ 1001
+ end
+
private
def projects
limit_projects.search(query)
end
- def issues
- issues = IssuesFinder.new(current_user).execute
+ def issues(finder_params = {})
+ issues = IssuesFinder.new(current_user, finder_params).execute
unless default_project_filter
issues = issues.where(project_id: project_ids_relation)
end
@@ -94,13 +124,13 @@ module Gitlab
issues.full_search(query)
end
- issues.order('updated_at DESC')
+ issues.reorder('updated_at DESC')
end
def milestones
milestones = Milestone.where(project_id: project_ids_relation)
milestones = milestones.search(query)
- milestones.order('updated_at DESC')
+ milestones.reorder('updated_at DESC')
end
def merge_requests
@@ -116,7 +146,7 @@ module Gitlab
merge_requests.full_search(query)
end
- merge_requests.order('updated_at DESC')
+ merge_requests.reorder('updated_at DESC')
end
def default_scope
diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb
index b85f70e450e..4f86b3e8f73 100644
--- a/lib/gitlab/snippet_search_results.rb
+++ b/lib/gitlab/snippet_search_results.rb
@@ -16,7 +16,7 @@ module Gitlab
when 'snippet_blobs'
snippet_blobs.page(page).per(per_page)
else
- super
+ super(scope, nil, false)
end
end
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index b3baaf036d8..fa22f0e37b2 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -27,6 +27,10 @@ module Gitlab
.gsub(/(\A-+|-+\z)/, '')
end
+ def remove_line_breaks(str)
+ str.gsub(/\r?\n/, '')
+ end
+
def to_boolean(value)
return value if [true, false].include?(value)
return true if value =~ /^(true|t|yes|y|1|on)$/i
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 0de183858aa..633da44b22d 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -34,7 +34,10 @@ module Gitlab
feature_enabled = case action.to_s
when 'git_receive_pack'
- Gitlab::GitalyClient.feature_enabled?(:post_receive_pack)
+ Gitlab::GitalyClient.feature_enabled?(
+ :post_receive_pack,
+ status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT
+ )
when 'git_upload_pack'
true
when 'info_refs'
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 31cd6bfe6e1..a584eb97cf5 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -427,7 +427,7 @@ namespace :gitlab do
namespace :user do
desc "GitLab | Check the integrity of a specific user's repositories"
task :check_repos, [:username] => :environment do |t, args|
- username = args[:username] || prompt("Check repository integrity for fsername? ".color(:blue))
+ username = args[:username] || prompt("Check repository integrity for username? ".color(:blue))
user = User.find_by(username: username)
if user
repo_dirs = user.authorized_projects.map do |p|
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 04d56509ac6..ab601b0d66b 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -1,3 +1,5 @@
+# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/954
+#
namespace :gitlab do
namespace :cleanup do
HASHED_REPOSITORY_NAME = '@hashed'.freeze
diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake
index c9e3eed82f2..c996537cfbe 100644
--- a/lib/tasks/migrate/setup_postgresql.rake
+++ b/lib/tasks/migrate/setup_postgresql.rake
@@ -7,6 +7,7 @@ require Rails.root.join('db/migrate/20170317203554_index_routes_path_for_like')
require Rails.root.join('db/migrate/20170724214302_add_lower_path_index_to_redirect_routes')
require Rails.root.join('db/migrate/20170503185032_index_redirect_routes_path_for_like')
require Rails.root.join('db/migrate/20171220191323_add_index_on_namespaces_lower_name.rb')
+require Rails.root.join('db/migrate/20180113220114_rework_redirect_routes_indexes.rb')
desc 'GitLab | Sets up PostgreSQL'
task setup_postgresql: :environment do
@@ -17,4 +18,5 @@ task setup_postgresql: :environment do
AddLowerPathIndexToRedirectRoutes.new.up
IndexRedirectRoutesPathForLike.new.up
AddIndexOnNamespacesLowerName.new.up
+ ReworkRedirectRoutesIndexes.new.up
end