summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorDouglas Barbosa Alexandre <dbalexandre@gmail.com>2019-07-09 14:45:46 -0300
committerDouglas Barbosa Alexandre <dbalexandre@gmail.com>2019-07-09 14:45:46 -0300
commit2615265ef82e07ea36df67f6d1a11ad4a731ad63 (patch)
tree92c1d3ab69f88e1dceed11ae886e1acb6ae65f1e /lib
parent203ef336392befd907c335a4a851ec0794d5e91a (diff)
parent48c19d9e296b2bec3663f46b8d936da2082171b1 (diff)
downloadgitlab-ce-2615265ef82e07ea36df67f6d1a11ad4a731ad63.tar.gz
Merge branch 'master' into sathieu/gitlab-ce-project_api
Diffstat (limited to 'lib')
-rw-r--r--lib/api/api.rb6
-rw-r--r--lib/api/helpers/graphql_helpers.rb2
-rw-r--r--lib/api/merge_requests.rb3
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/runners.rb2
-rw-r--r--lib/api/settings.rb2
-rw-r--r--lib/api/user_counts.rb18
-rw-r--r--lib/feature.rb23
-rw-r--r--lib/feature/gitaly.rb7
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/PHP.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml1
-rw-r--r--lib/gitlab/danger/helper.rb6
-rw-r--r--lib/gitlab/database/median.rb2
-rw-r--r--lib/gitlab/diff/position.rb4
-rw-r--r--lib/gitlab/diff/position_tracer.rb192
-rw-r--r--lib/gitlab/diff/position_tracer/base_strategy.rb26
-rw-r--r--lib/gitlab/diff/position_tracer/image_strategy.rb50
-rw-r--r--lib/gitlab/diff/position_tracer/line_strategy.rb201
-rw-r--r--lib/gitlab/git/repository.rb4
-rw-r--r--lib/gitlab/git/rugged_impl/blob.rb3
-rw-r--r--lib/gitlab/git/rugged_impl/commit.rb8
-rw-r--r--lib/gitlab/git/rugged_impl/repository.rb3
-rw-r--r--lib/gitlab/git/rugged_impl/tree.rb3
-rw-r--r--lib/gitlab/git/rugged_impl/use_rugged.rb16
-rw-r--r--lib/gitlab/gitaly_client.rb49
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb5
-rw-r--r--lib/gitlab/gon_helper.rb5
-rw-r--r--lib/gitlab/graphql.rb4
-rw-r--r--lib/gitlab/graphql/authorize.rb2
-rw-r--r--lib/gitlab/graphql/calls_gitaly.rb15
-rw-r--r--lib/gitlab/graphql/calls_gitaly/instrumentation.rb40
-rw-r--r--lib/gitlab/graphql/mount_mutation.rb5
-rw-r--r--lib/gitlab/http.rb6
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb7
-rw-r--r--lib/gitlab/metrics/system.rb12
-rw-r--r--lib/gitlab/namespaced_session_store.rb15
-rw-r--r--lib/gitlab/performance_bar.rb28
-rw-r--r--lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb12
-rw-r--r--lib/gitlab/quick_actions/issuable_actions.rb4
-rw-r--r--lib/gitlab/sidekiq_status.rb6
-rw-r--r--lib/gitlab/sql/pattern.rb24
-rw-r--r--lib/gitlab/user_extractor.rb56
-rw-r--r--lib/peek/views/redis.rb8
45 files changed, 564 insertions, 327 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 20f8c637274..7016a66593d 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -52,7 +52,10 @@ module API
rack_response({ 'message' => '404 Not found' }.to_json, 404)
end
- rescue_from ::Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError do
+ rescue_from(
+ ::ActiveRecord::StaleObjectError,
+ ::Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
+ ) do
rack_response({ 'message' => '409 Conflict: Resource lock' }.to_json, 409)
end
@@ -163,6 +166,7 @@ module API
mount ::API::Templates
mount ::API::Todos
mount ::API::Triggers
+ mount ::API::UserCounts
mount ::API::Users
mount ::API::Variables
mount ::API::Version
diff --git a/lib/api/helpers/graphql_helpers.rb b/lib/api/helpers/graphql_helpers.rb
index 94010ab1bc2..bd60470fbd6 100644
--- a/lib/api/helpers/graphql_helpers.rb
+++ b/lib/api/helpers/graphql_helpers.rb
@@ -7,8 +7,6 @@ module API
# should be in app/graphql/ or lib/gitlab/graphql/
module GraphqlHelpers
def conditionally_graphql!(fallback:, query:, context: {}, transform: nil)
- return fallback.call unless Feature.enabled?(:graphql)
-
result = GitlabSchema.execute(query, context: context)
if transform
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 6b8c1a2c0e8..64ee82cd775 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -429,9 +429,10 @@ module API
authorize_push_to_merge_request!(merge_request)
- RebaseWorker.perform_async(merge_request.id, current_user.id)
+ merge_request.rebase_async(current_user.id)
status :accepted
+ present rebase_in_progress: merge_request.rebase_in_progress?
end
desc 'List issues that will be closed on merge' do
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 1e14c77b5be..a7d62014509 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -474,7 +474,7 @@ module API
authorize_admin_project
begin
- ::Projects::HousekeepingService.new(user_project).execute
+ ::Projects::HousekeepingService.new(user_project, :gc).execute
rescue ::Projects::HousekeepingService::LeaseTaken => error
conflict!(error.message)
end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index f3fea463e7f..c2d371b6867 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -115,6 +115,8 @@ module API
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
+ optional :order_by, type: String, desc: 'Order by `id` or not', values: RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS
+ optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)'
use :pagination
end
get ':id/jobs' do
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 3c5c1a9fd5f..4275d911708 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -55,6 +55,8 @@ module API
optional :gitaly_timeout_default, type: Integer, desc: 'Default 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.'
optional :gitaly_timeout_medium, type: Integer, desc: 'Medium Gitaly timeout, in seconds. Set to 0 to disable timeouts.'
+ optional :grafana_enabled, type: Boolean, desc: 'Enable Grafana'
+ optional :grafana_url, type: String, desc: 'Grafana URL'
optional :gravatar_enabled, type: Boolean, desc: 'Flag indicating if the Gravatar service is enabled'
optional :help_page_hide_commercial_content, type: Boolean, desc: 'Hide marketing-related entries from help'
optional :help_page_support_url, type: String, desc: 'Alternate support URL for help page'
diff --git a/lib/api/user_counts.rb b/lib/api/user_counts.rb
new file mode 100644
index 00000000000..8df4b381bbf
--- /dev/null
+++ b/lib/api/user_counts.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module API
+ class UserCounts < Grape::API
+ resource :user_counts do
+ desc 'Return the user specific counts' do
+ detail 'Open MR Count'
+ end
+ get do
+ unauthorized! unless current_user
+
+ {
+ merge_requests: current_user.assigned_open_merge_requests_count
+ }
+ end
+ end
+ end
+end
diff --git a/lib/feature.rb b/lib/feature.rb
index 22420e95ea2..e28333aa58e 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -103,10 +103,27 @@ class Feature
feature_class: FlipperFeature,
gate_class: FlipperGate)
+ # Redis L2 cache
+ redis_cache_adapter =
+ Flipper::Adapters::ActiveSupportCacheStore.new(
+ active_record_adapter,
+ l2_cache_backend,
+ expires_in: 1.hour)
+
+ # Thread-local L1 cache: use a short timeout since we don't have a
+ # way to expire this cache all at once
Flipper::Adapters::ActiveSupportCacheStore.new(
- active_record_adapter,
- Rails.cache,
- expires_in: 1.hour)
+ redis_cache_adapter,
+ l1_cache_backend,
+ expires_in: 1.minute)
+ end
+
+ def l1_cache_backend
+ Gitlab::ThreadMemoryCache.cache_backend
+ end
+
+ def l2_cache_backend
+ Rails.cache
end
end
diff --git a/lib/feature/gitaly.rb b/lib/feature/gitaly.rb
index d7a8f8a0b9e..67c0b902c0c 100644
--- a/lib/feature/gitaly.rb
+++ b/lib/feature/gitaly.rb
@@ -8,7 +8,12 @@ class Feature
# CATFILE_CACHE sets an incorrect example
CATFILE_CACHE = 'catfile-cache'.freeze
- SERVER_FEATURE_FLAGS = [CATFILE_CACHE].freeze
+ SERVER_FEATURE_FLAGS =
+ [
+ CATFILE_CACHE,
+ 'get_commit_signatures'.freeze
+ ].freeze
+
DEFAULT_ON_FLAGS = Set.new([CATFILE_CACHE]).freeze
class << self
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
index dcf8254ef94..108f0119ae1 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
@@ -246,7 +246,6 @@ rollout 100%:
auto_database_url=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${CI_ENVIRONMENT_SLUG}-postgres:5432/${POSTGRES_DB}
export DATABASE_URL=${DATABASE_URL-$auto_database_url}
export TILLER_NAMESPACE=$KUBE_NAMESPACE
- # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
function get_replicas() {
track="${1:-stable}"
diff --git a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
index b9fee2d5731..25ea20e454f 100644
--- a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
@@ -1,5 +1,5 @@
# Select image from https://hub.docker.com/_/php/
-image: php:7.1.1
+image: php:latest
# Select what we should cache between builds
cache:
diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
index 8713b833011..0a97a16b83c 100644
--- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
@@ -54,6 +54,7 @@ sast:
MAVEN_PATH \
MAVEN_REPO_PATH \
SBT_PATH \
+ FAIL_NEVER \
) \
--volume "$PWD:/code" \
--volume /var/run/docker.sock:/var/run/docker.sock \
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
index 1ecf4a12db7..0fc145534bf 100644
--- a/lib/gitlab/danger/helper.rb
+++ b/lib/gitlab/danger/helper.rb
@@ -103,6 +103,11 @@ module Gitlab
yarn\.lock
)\z}x => :frontend,
+ %r{\A(ee/)?db/} => :database,
+ %r{\A(ee/)?lib/gitlab/(database|background_migration|sql|github_import)(/|\.rb)} => :database,
+ %r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database,
+ %r{\Arubocop/cop/migration(/|\.rb)} => :database,
+
%r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
%r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend,
%r{\A(ee/)?spec/features/} => :test,
@@ -112,7 +117,6 @@ module Gitlab
%r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile|\.gitlab-ci\.yml)\z} => :backend,
%r{\A[A-Z_]+_VERSION\z} => :backend,
- %r{\A(ee/)?db/} => :database,
%r{\A(ee/)?qa/} => :qa,
# Files that don't fit into any category are marked with :none
diff --git a/lib/gitlab/database/median.rb b/lib/gitlab/database/median.rb
index 1455e410d4b..b8d895dee7d 100644
--- a/lib/gitlab/database/median.rb
+++ b/lib/gitlab/database/median.rb
@@ -158,7 +158,7 @@ module Gitlab
Arel::Nodes::Window.new.order(arel_table[column_sym])
).as('row_id')
- count = arel_table.project("COUNT(1)").as('ct')
+ count = arel_table.where(arel_table[column_sym].gteq(zero_interval)).project("COUNT(1)").as('ct')
[column_row, row_id, count]
end
diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb
index d349c378e53..dfa80eb4a64 100644
--- a/lib/gitlab/diff/position.rb
+++ b/lib/gitlab/diff/position.rb
@@ -134,6 +134,10 @@ module Gitlab
@line_code ||= diff_file(repository)&.line_code_for_position(self)
end
+ def file_hash
+ @file_hash ||= Digest::SHA1.hexdigest(file_path)
+ end
+
def on_image?
position_type == 'image'
end
diff --git a/lib/gitlab/diff/position_tracer.rb b/lib/gitlab/diff/position_tracer.rb
index af3df820422..a1c82ce9afc 100644
--- a/lib/gitlab/diff/position_tracer.rb
+++ b/lib/gitlab/diff/position_tracer.rb
@@ -17,187 +17,13 @@ module Gitlab
@paths = paths
end
- def trace(ab_position)
+ def trace(old_position)
return unless old_diff_refs&.complete? && new_diff_refs&.complete?
- return unless ab_position.diff_refs == old_diff_refs
+ return unless old_position.diff_refs == old_diff_refs
- # Suppose we have an MR with source branch `feature` and target branch `master`.
- # When the MR was created, the head of `master` was commit A, and the
- # head of `feature` was commit B, resulting in the original diff A->B.
- # Since creation, `master` was updated to C.
- # Now `feature` is being updated to D, and the newly generated MR diff is C->D.
- # It is possible that C and D are direct descendants of A and B respectively,
- # but this isn't necessarily the case as rebases and merges come into play.
- #
- # Suppose we have a diff note on the original diff A->B. Now that the MR
- # is updated, we need to find out what line in C->D corresponds to the
- # line the note was originally created on, so that we can update the diff note's
- # records and continue to display it in the right place in the diffs.
- # If we cannot find this line in the new diff, this means the diff note is now
- # outdated, and we will display that fact to the user.
- #
- # In the new diff, the file the diff note was originally created on may
- # have been renamed, deleted or even created, if the file existed in A and B,
- # but was removed in C, and restored in D.
- #
- # Every diff note stores a Position object that defines a specific location,
- # identified by paths and line numbers, within a specific diff, identified
- # by start, head and base commit ids.
- #
- # For diff notes for diff A->B, the position looks like this:
- # Position
- # start_sha - ID of commit A
- # head_sha - ID of commit B
- # base_sha - ID of base commit of A and B
- # old_path - path as of A (nil if file was newly created)
- # new_path - path as of B (nil if file was deleted)
- # old_line - line number as of A (nil if file was newly created)
- # new_line - line number as of B (nil if file was deleted)
- #
- # We can easily update `start_sha` and `head_sha` to hold the IDs of
- # commits C and D, and can trivially determine `base_sha` based on those,
- # but need to find the paths and line numbers as of C and D.
- #
- # If the file was unchanged or newly created in A->B, the path as of D can be found
- # by generating diff B->D ("head to head"), finding the diff file with
- # `diff_file.old_path == position.new_path`, and taking `diff_file.new_path`.
- # The path as of C can be found by taking diff C->D, finding the diff file
- # with that same `new_path` and taking `diff_file.old_path`.
- # The line number as of D can be found by using the LineMapper on diff B->D
- # and providing the line number as of B.
- # The line number as of C can be found by using the LineMapper on diff C->D
- # and providing the line number as of D.
- #
- # If the file was deleted in A->B, the path as of C can be found
- # by generating diff A->C ("base to base"), finding the diff file with
- # `diff_file.old_path == position.old_path`, and taking `diff_file.new_path`.
- # The path as of D can be found by taking diff C->D, finding the diff file
- # with `old_path` set to that `diff_file.new_path` and taking `diff_file.new_path`.
- # The line number as of C can be found by using the LineMapper on diff A->C
- # and providing the line number as of A.
- # The line number as of D can be found by using the LineMapper on diff C->D
- # and providing the line number as of C.
+ strategy = old_position.on_text? ? LineStrategy : ImageStrategy
- if ab_position.added?
- trace_added_line(ab_position)
- elsif ab_position.removed?
- trace_removed_line(ab_position)
- else # unchanged
- trace_unchanged_line(ab_position)
- end
- end
-
- private
-
- def trace_added_line(ab_position)
- b_path = ab_position.new_path
- b_line = ab_position.new_line
-
- bd_diff = bd_diffs.diff_file_with_old_path(b_path)
-
- d_path = bd_diff&.new_path || b_path
- d_line = LineMapper.new(bd_diff).old_to_new(b_line)
-
- if d_line
- cd_diff = cd_diffs.diff_file_with_new_path(d_path)
-
- c_path = cd_diff&.old_path || d_path
- c_line = LineMapper.new(cd_diff).new_to_old(d_line)
-
- if c_line
- # If the line is still in D but also in C, it has turned from an
- # added line into an unchanged one.
- new_position = position(cd_diff, c_line, d_line)
- if valid_position?(new_position)
- # If the line is still in the MR, we don't treat this as outdated.
- { position: new_position, outdated: false }
- else
- # If the line is no longer in the MR, we unfortunately cannot show
- # the current state on the CD diff, so we treat it as outdated.
- ac_diff = ac_diffs.diff_file_with_new_path(c_path)
-
- { position: position(ac_diff, nil, c_line), outdated: true }
- end
- else
- # If the line is still in D and not in C, it is still added.
- { position: position(cd_diff, nil, d_line), outdated: false }
- end
- else
- # If the line is no longer in D, it has been removed from the MR.
- { position: position(bd_diff, b_line, nil), outdated: true }
- end
- end
-
- def trace_removed_line(ab_position)
- a_path = ab_position.old_path
- a_line = ab_position.old_line
-
- ac_diff = ac_diffs.diff_file_with_old_path(a_path)
-
- c_path = ac_diff&.new_path || a_path
- c_line = LineMapper.new(ac_diff).old_to_new(a_line)
-
- if c_line
- cd_diff = cd_diffs.diff_file_with_old_path(c_path)
-
- d_path = cd_diff&.new_path || c_path
- d_line = LineMapper.new(cd_diff).old_to_new(c_line)
-
- if d_line
- # If the line is still in C but also in D, it has turned from a
- # removed line into an unchanged one.
- bd_diff = bd_diffs.diff_file_with_new_path(d_path)
-
- { position: position(bd_diff, nil, d_line), outdated: true }
- else
- # If the line is still in C and not in D, it is still removed.
- { position: position(cd_diff, c_line, nil), outdated: false }
- end
- else
- # If the line is no longer in C, it has been removed outside of the MR.
- { position: position(ac_diff, a_line, nil), outdated: true }
- end
- end
-
- def trace_unchanged_line(ab_position)
- a_path = ab_position.old_path
- a_line = ab_position.old_line
- b_path = ab_position.new_path
- b_line = ab_position.new_line
-
- ac_diff = ac_diffs.diff_file_with_old_path(a_path)
-
- c_path = ac_diff&.new_path || a_path
- c_line = LineMapper.new(ac_diff).old_to_new(a_line)
-
- bd_diff = bd_diffs.diff_file_with_old_path(b_path)
-
- d_line = LineMapper.new(bd_diff).old_to_new(b_line)
-
- cd_diff = cd_diffs.diff_file_with_old_path(c_path)
-
- if c_line && d_line
- # If the line is still in C and D, it is still unchanged.
- new_position = position(cd_diff, c_line, d_line)
- if valid_position?(new_position)
- # If the line is still in the MR, we don't treat this as outdated.
- { position: new_position, outdated: false }
- else
- # If the line is no longer in the MR, we unfortunately cannot show
- # the current state on the CD diff or any change on the BD diff,
- # so we treat it as outdated.
- { position: nil, outdated: true }
- end
- elsif d_line # && !c_line
- # If the line is still in D but no longer in C, it has turned from
- # an unchanged line into an added one.
- # We don't treat this as outdated since the line is still in the MR.
- { position: position(cd_diff, nil, d_line), outdated: false }
- else # !d_line && (c_line || !c_line)
- # If the line is no longer in D, it has turned from an unchanged line
- # into a removed one.
- { position: position(bd_diff, b_line, nil), outdated: true }
- end
+ strategy.new(self).trace(old_position)
end
def ac_diffs
@@ -216,18 +42,12 @@ module Gitlab
@cd_diffs ||= compare(new_diff_refs.start_sha, new_diff_refs.head_sha)
end
+ private
+
def compare(start_sha, head_sha, straight: false)
compare = CompareService.new(project, head_sha).execute(project, start_sha, straight: straight)
compare.diffs(paths: paths, expanded: true)
end
-
- def position(diff_file, old_line, new_line)
- Position.new(diff_file: diff_file, old_line: old_line, new_line: new_line)
- end
-
- def valid_position?(position)
- !!position.diff_line(project.repository)
- end
end
end
end
diff --git a/lib/gitlab/diff/position_tracer/base_strategy.rb b/lib/gitlab/diff/position_tracer/base_strategy.rb
new file mode 100644
index 00000000000..65049daabf4
--- /dev/null
+++ b/lib/gitlab/diff/position_tracer/base_strategy.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ class PositionTracer
+ class BaseStrategy
+ attr_reader :tracer
+
+ delegate \
+ :project,
+ :ac_diffs,
+ :bd_diffs,
+ :cd_diffs,
+ to: :tracer
+
+ def initialize(tracer)
+ @tracer = tracer
+ end
+
+ def trace(position)
+ raise NotImplementedError
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/position_tracer/image_strategy.rb b/lib/gitlab/diff/position_tracer/image_strategy.rb
new file mode 100644
index 00000000000..79244a17951
--- /dev/null
+++ b/lib/gitlab/diff/position_tracer/image_strategy.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ class PositionTracer
+ class ImageStrategy < BaseStrategy
+ def trace(position)
+ b_path = position.new_path
+
+ # If file exists in B->D (e.g. updated, renamed, removed), let the
+ # note become outdated.
+ bd_diff = bd_diffs.diff_file_with_old_path(b_path)
+
+ return { position: new_position(position, bd_diff), outdated: true } if bd_diff
+
+ # If file still exists in the new diff, update the position.
+ cd_diff = cd_diffs.diff_file_with_new_path(bd_diff&.new_path || b_path)
+
+ return { position: new_position(position, cd_diff), outdated: false } if cd_diff
+
+ # If file exists in A->C (e.g. rebased and same changes were present
+ # in target branch), let the note become outdated.
+ ac_diff = ac_diffs.diff_file_with_old_path(position.old_path)
+
+ return { position: new_position(position, ac_diff), outdated: true } if ac_diff
+
+ # If ever there's a case that the file no longer exists in any diff,
+ # don't set a change position and let the note become outdated.
+ #
+ # This should never happen given the file should exist in one of the
+ # diffs above.
+ { outdated: true }
+ end
+
+ private
+
+ def new_position(position, diff_file)
+ Position.new(
+ diff_file: diff_file,
+ x: position.x,
+ y: position.y,
+ width: position.width,
+ height: position.height,
+ position_type: position.position_type
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/position_tracer/line_strategy.rb b/lib/gitlab/diff/position_tracer/line_strategy.rb
new file mode 100644
index 00000000000..8db0fc6f963
--- /dev/null
+++ b/lib/gitlab/diff/position_tracer/line_strategy.rb
@@ -0,0 +1,201 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ class PositionTracer
+ class LineStrategy < BaseStrategy
+ def trace(position)
+ # Suppose we have an MR with source branch `feature` and target branch `master`.
+ # When the MR was created, the head of `master` was commit A, and the
+ # head of `feature` was commit B, resulting in the original diff A->B.
+ # Since creation, `master` was updated to C.
+ # Now `feature` is being updated to D, and the newly generated MR diff is C->D.
+ # It is possible that C and D are direct descendants of A and B respectively,
+ # but this isn't necessarily the case as rebases and merges come into play.
+ #
+ # Suppose we have a diff note on the original diff A->B. Now that the MR
+ # is updated, we need to find out what line in C->D corresponds to the
+ # line the note was originally created on, so that we can update the diff note's
+ # records and continue to display it in the right place in the diffs.
+ # If we cannot find this line in the new diff, this means the diff note is now
+ # outdated, and we will display that fact to the user.
+ #
+ # In the new diff, the file the diff note was originally created on may
+ # have been renamed, deleted or even created, if the file existed in A and B,
+ # but was removed in C, and restored in D.
+ #
+ # Every diff note stores a Position object that defines a specific location,
+ # identified by paths and line numbers, within a specific diff, identified
+ # by start, head and base commit ids.
+ #
+ # For diff notes for diff A->B, the position looks like this:
+ # Position
+ # start_sha - ID of commit A
+ # head_sha - ID of commit B
+ # base_sha - ID of base commit of A and B
+ # old_path - path as of A (nil if file was newly created)
+ # new_path - path as of B (nil if file was deleted)
+ # old_line - line number as of A (nil if file was newly created)
+ # new_line - line number as of B (nil if file was deleted)
+ #
+ # We can easily update `start_sha` and `head_sha` to hold the IDs of
+ # commits C and D, and can trivially determine `base_sha` based on those,
+ # but need to find the paths and line numbers as of C and D.
+ #
+ # If the file was unchanged or newly created in A->B, the path as of D can be found
+ # by generating diff B->D ("head to head"), finding the diff file with
+ # `diff_file.old_path == position.new_path`, and taking `diff_file.new_path`.
+ # The path as of C can be found by taking diff C->D, finding the diff file
+ # with that same `new_path` and taking `diff_file.old_path`.
+ # The line number as of D can be found by using the LineMapper on diff B->D
+ # and providing the line number as of B.
+ # The line number as of C can be found by using the LineMapper on diff C->D
+ # and providing the line number as of D.
+ #
+ # If the file was deleted in A->B, the path as of C can be found
+ # by generating diff A->C ("base to base"), finding the diff file with
+ # `diff_file.old_path == position.old_path`, and taking `diff_file.new_path`.
+ # The path as of D can be found by taking diff C->D, finding the diff file
+ # with `old_path` set to that `diff_file.new_path` and taking `diff_file.new_path`.
+ # The line number as of C can be found by using the LineMapper on diff A->C
+ # and providing the line number as of A.
+ # The line number as of D can be found by using the LineMapper on diff C->D
+ # and providing the line number as of C.
+
+ if position.added?
+ trace_added_line(position)
+ elsif position.removed?
+ trace_removed_line(position)
+ else # unchanged
+ trace_unchanged_line(position)
+ end
+ end
+
+ private
+
+ def trace_added_line(position)
+ b_path = position.new_path
+ b_line = position.new_line
+
+ bd_diff = bd_diffs.diff_file_with_old_path(b_path)
+
+ d_path = bd_diff&.new_path || b_path
+ d_line = LineMapper.new(bd_diff).old_to_new(b_line)
+
+ if d_line
+ cd_diff = cd_diffs.diff_file_with_new_path(d_path)
+
+ c_path = cd_diff&.old_path || d_path
+ c_line = LineMapper.new(cd_diff).new_to_old(d_line)
+
+ if c_line
+ # If the line is still in D but also in C, it has turned from an
+ # added line into an unchanged one.
+ new_position = new_position(cd_diff, c_line, d_line)
+ if valid_position?(new_position)
+ # If the line is still in the MR, we don't treat this as outdated.
+ { position: new_position, outdated: false }
+ else
+ # If the line is no longer in the MR, we unfortunately cannot show
+ # the current state on the CD diff, so we treat it as outdated.
+ ac_diff = ac_diffs.diff_file_with_new_path(c_path)
+
+ { position: new_position(ac_diff, nil, c_line), outdated: true }
+ end
+ else
+ # If the line is still in D and not in C, it is still added.
+ { position: new_position(cd_diff, nil, d_line), outdated: false }
+ end
+ else
+ # If the line is no longer in D, it has been removed from the MR.
+ { position: new_position(bd_diff, b_line, nil), outdated: true }
+ end
+ end
+
+ def trace_removed_line(position)
+ a_path = position.old_path
+ a_line = position.old_line
+
+ ac_diff = ac_diffs.diff_file_with_old_path(a_path)
+
+ c_path = ac_diff&.new_path || a_path
+ c_line = LineMapper.new(ac_diff).old_to_new(a_line)
+
+ if c_line
+ cd_diff = cd_diffs.diff_file_with_old_path(c_path)
+
+ d_path = cd_diff&.new_path || c_path
+ d_line = LineMapper.new(cd_diff).old_to_new(c_line)
+
+ if d_line
+ # If the line is still in C but also in D, it has turned from a
+ # removed line into an unchanged one.
+ bd_diff = bd_diffs.diff_file_with_new_path(d_path)
+
+ { position: new_position(bd_diff, nil, d_line), outdated: true }
+ else
+ # If the line is still in C and not in D, it is still removed.
+ { position: new_position(cd_diff, c_line, nil), outdated: false }
+ end
+ else
+ # If the line is no longer in C, it has been removed outside of the MR.
+ { position: new_position(ac_diff, a_line, nil), outdated: true }
+ end
+ end
+
+ def trace_unchanged_line(position)
+ a_path = position.old_path
+ a_line = position.old_line
+ b_path = position.new_path
+ b_line = position.new_line
+
+ ac_diff = ac_diffs.diff_file_with_old_path(a_path)
+
+ c_path = ac_diff&.new_path || a_path
+ c_line = LineMapper.new(ac_diff).old_to_new(a_line)
+
+ bd_diff = bd_diffs.diff_file_with_old_path(b_path)
+
+ d_line = LineMapper.new(bd_diff).old_to_new(b_line)
+
+ cd_diff = cd_diffs.diff_file_with_old_path(c_path)
+
+ if c_line && d_line
+ # If the line is still in C and D, it is still unchanged.
+ new_position = new_position(cd_diff, c_line, d_line)
+ if valid_position?(new_position)
+ # If the line is still in the MR, we don't treat this as outdated.
+ { position: new_position, outdated: false }
+ else
+ # If the line is no longer in the MR, we unfortunately cannot show
+ # the current state on the CD diff or any change on the BD diff,
+ # so we treat it as outdated.
+ { position: nil, outdated: true }
+ end
+ elsif d_line # && !c_line
+ # If the line is still in D but no longer in C, it has turned from
+ # an unchanged line into an added one.
+ # We don't treat this as outdated since the line is still in the MR.
+ { position: new_position(cd_diff, nil, d_line), outdated: false }
+ else # !d_line && (c_line || !c_line)
+ # If the line is no longer in D, it has turned from an unchanged line
+ # into a removed one.
+ { position: new_position(bd_diff, b_line, nil), outdated: true }
+ end
+ end
+
+ def new_position(diff_file, old_line, new_line)
+ Position.new(
+ diff_file: diff_file,
+ old_line: old_line,
+ new_line: new_line
+ )
+ end
+
+ def valid_position?(position)
+ !!position.diff_line(project.repository)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 19b6aab1c4f..060a29be782 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -536,9 +536,9 @@ module Gitlab
tags.find { |tag| tag.name == name }
end
- def merge_to_ref(user, source_sha, branch, target_ref, message)
+ def merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref)
wrapped_gitaly_errors do
- gitaly_operation_client.user_merge_to_ref(user, source_sha, branch, target_ref, message)
+ gitaly_operation_client.user_merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref)
end
end
diff --git a/lib/gitlab/git/rugged_impl/blob.rb b/lib/gitlab/git/rugged_impl/blob.rb
index 11ee4ebda4b..86c9f33d82a 100644
--- a/lib/gitlab/git/rugged_impl/blob.rb
+++ b/lib/gitlab/git/rugged_impl/blob.rb
@@ -11,10 +11,11 @@ module Gitlab
module Blob
module ClassMethods
extend ::Gitlab::Utils::Override
+ include Gitlab::Git::RuggedImpl::UseRugged
override :tree_entry
def tree_entry(repository, sha, path, limit)
- if Feature.enabled?(:rugged_tree_entry)
+ if use_rugged?(repository, :rugged_tree_entry)
rugged_tree_entry(repository, sha, path, limit)
else
super
diff --git a/lib/gitlab/git/rugged_impl/commit.rb b/lib/gitlab/git/rugged_impl/commit.rb
index bce4fa14fb4..971a33b2e99 100644
--- a/lib/gitlab/git/rugged_impl/commit.rb
+++ b/lib/gitlab/git/rugged_impl/commit.rb
@@ -12,6 +12,7 @@ module Gitlab
module Commit
module ClassMethods
extend ::Gitlab::Utils::Override
+ include Gitlab::Git::RuggedImpl::UseRugged
def rugged_find(repo, commit_id)
obj = repo.rev_parse_target(commit_id)
@@ -34,7 +35,7 @@ module Gitlab
override :find_commit
def find_commit(repo, commit_id)
- if Feature.enabled?(:rugged_find_commit)
+ if use_rugged?(repo, :rugged_find_commit)
rugged_find(repo, commit_id)
else
super
@@ -43,7 +44,7 @@ module Gitlab
override :batch_by_oid
def batch_by_oid(repo, oids)
- if Feature.enabled?(:rugged_list_commits_by_oid)
+ if use_rugged?(repo, :rugged_list_commits_by_oid)
rugged_batch_by_oid(repo, oids)
else
super
@@ -52,6 +53,7 @@ module Gitlab
end
extend ::Gitlab::Utils::Override
+ include Gitlab::Git::RuggedImpl::UseRugged
override :init_commit
def init_commit(raw_commit)
@@ -65,7 +67,7 @@ module Gitlab
override :commit_tree_entry
def commit_tree_entry(path)
- if Feature.enabled?(:rugged_commit_tree_entry)
+ if use_rugged?(@repository, :rugged_commit_tree_entry)
rugged_tree_entry(path)
else
super
diff --git a/lib/gitlab/git/rugged_impl/repository.rb b/lib/gitlab/git/rugged_impl/repository.rb
index e91b0ddcd31..9268abdfed9 100644
--- a/lib/gitlab/git/rugged_impl/repository.rb
+++ b/lib/gitlab/git/rugged_impl/repository.rb
@@ -11,6 +11,7 @@ module Gitlab
module RuggedImpl
module Repository
extend ::Gitlab::Utils::Override
+ include Gitlab::Git::RuggedImpl::UseRugged
FEATURE_FLAGS = %i(rugged_find_commit rugged_tree_entries rugged_tree_entry rugged_commit_is_ancestor rugged_commit_tree_entry rugged_list_commits_by_oid).freeze
@@ -46,7 +47,7 @@ module Gitlab
override :ancestor?
def ancestor?(from, to)
- if Feature.enabled?(:rugged_commit_is_ancestor)
+ if use_rugged?(self, :rugged_commit_is_ancestor)
rugged_is_ancestor?(from, to)
else
super
diff --git a/lib/gitlab/git/rugged_impl/tree.rb b/lib/gitlab/git/rugged_impl/tree.rb
index 9c37bb01961..f3721a3f1b7 100644
--- a/lib/gitlab/git/rugged_impl/tree.rb
+++ b/lib/gitlab/git/rugged_impl/tree.rb
@@ -11,10 +11,11 @@ module Gitlab
module Tree
module ClassMethods
extend ::Gitlab::Utils::Override
+ include Gitlab::Git::RuggedImpl::UseRugged
override :tree_entries
def tree_entries(repository, sha, path, recursive)
- if Feature.enabled?(:rugged_tree_entries)
+ if use_rugged?(repository, :rugged_tree_entries)
tree_entries_with_flat_path_from_rugged(repository, sha, path, recursive)
else
super
diff --git a/lib/gitlab/git/rugged_impl/use_rugged.rb b/lib/gitlab/git/rugged_impl/use_rugged.rb
new file mode 100644
index 00000000000..99091b03cd1
--- /dev/null
+++ b/lib/gitlab/git/rugged_impl/use_rugged.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Git
+ module RuggedImpl
+ module UseRugged
+ def use_rugged?(repo, feature_key)
+ feature = Feature.get(feature_key)
+ return feature.enabled? if Feature.persisted?(feature)
+
+ Gitlab::GitalyClient.can_use_disk?(repo.storage)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 47976389af6..cf0157269a8 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -30,14 +30,10 @@ module Gitlab
SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION'
MAXIMUM_GITALY_CALLS = 30
CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze
+ GITALY_METADATA_FILENAME = '.gitaly-metadata'
MUTEX = Mutex.new
- define_histogram :gitaly_controller_action_duration_seconds do
- docstring "Gitaly endpoint histogram by controller and action combination"
- base_labels Gitlab::Metrics::Transaction::BASE_LABELS.merge(gitaly_service: nil, rpc: nil)
- end
-
def self.stub(name, storage)
MUTEX.synchronize do
@stubs ||= {}
@@ -161,10 +157,6 @@ module Gitlab
# Keep track, separately, for the performance bar
self.query_time += duration
- gitaly_controller_action_duration_seconds.observe(
- current_transaction_labels.merge(gitaly_service: service.to_s, rpc: rpc.to_s),
- duration)
-
if peek_enabled?
add_call_details(feature: "#{service}##{rpc}", duration: duration, request: request_hash, rpc: rpc,
backtrace: Gitlab::Profiler.clean_backtrace(caller))
@@ -387,6 +379,45 @@ module Gitlab
0
end
+ def self.storage_metadata_file_path(storage)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ File.join(
+ Gitlab.config.repositories.storages[storage].legacy_disk_path, GITALY_METADATA_FILENAME
+ )
+ end
+ end
+
+ def self.can_use_disk?(storage)
+ cached_value = MUTEX.synchronize do
+ @can_use_disk ||= {}
+ @can_use_disk[storage]
+ end
+
+ return cached_value unless cached_value.nil?
+
+ gitaly_filesystem_id = filesystem_id(storage)
+ direct_filesystem_id = filesystem_id_from_disk(storage)
+
+ MUTEX.synchronize do
+ @can_use_disk[storage] = gitaly_filesystem_id.present? &&
+ gitaly_filesystem_id == direct_filesystem_id
+ end
+ end
+
+ def self.filesystem_id(storage)
+ response = Gitlab::GitalyClient::ServerService.new(storage).info
+ storage_status = response.storage_statuses.find { |status| status.storage_name == storage }
+ storage_status.filesystem_id
+ end
+
+ def self.filesystem_id_from_disk(storage)
+ metadata_file = File.read(storage_metadata_file_path(storage))
+ metadata_hash = JSON.parse(metadata_file)
+ metadata_hash['gitaly_filesystem_id']
+ rescue Errno::ENOENT, JSON::ParserError
+ nil
+ end
+
def self.timeout(timeout_name)
Gitlab::CurrentSettings.current_application_settings[timeout_name]
end
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index b42e6cbad8d..783c2ff0915 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -100,14 +100,15 @@ module Gitlab
end
end
- def user_merge_to_ref(user, source_sha, branch, target_ref, message)
+ def user_merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref)
request = Gitaly::UserMergeToRefRequest.new(
repository: @gitaly_repo,
source_sha: source_sha,
branch: encode_binary(branch),
target_ref: encode_binary(target_ref),
user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
- message: encode_binary(message)
+ message: encode_binary(message),
+ first_parent_ref: encode_binary(first_parent_ref)
)
response = GitalyClient.call(@repository.storage, :operation_service, :user_merge_to_ref, request)
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 92917028851..41ec8741eb1 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -38,6 +38,11 @@ module Gitlab
gon.current_user_fullname = current_user.name
gon.current_user_avatar_url = current_user.avatar_url
end
+
+ # Flag controls a GFM feature used across many routes.
+ # Pushing the flag from one place simplifies control
+ # and facilitates easy removal.
+ push_frontend_feature_flag(:gfm_embedded_metrics)
end
# Exposes the state of a feature flag to the frontend code.
diff --git a/lib/gitlab/graphql.rb b/lib/gitlab/graphql.rb
index 8a59e83974f..74c04e5380e 100644
--- a/lib/gitlab/graphql.rb
+++ b/lib/gitlab/graphql.rb
@@ -3,9 +3,5 @@
module Gitlab
module Graphql
StandardGraphqlError = Class.new(StandardError)
-
- def self.enabled?
- Feature.enabled?(:graphql, default_enabled: true)
- end
end
end
diff --git a/lib/gitlab/graphql/authorize.rb b/lib/gitlab/graphql/authorize.rb
index f8d0208e275..e83b567308b 100644
--- a/lib/gitlab/graphql/authorize.rb
+++ b/lib/gitlab/graphql/authorize.rb
@@ -8,7 +8,7 @@ module Gitlab
extend ActiveSupport::Concern
def self.use(schema_definition)
- schema_definition.instrument(:field, Instrumentation.new, after_built_ins: true)
+ schema_definition.instrument(:field, Gitlab::Graphql::Authorize::Instrumentation.new, after_built_ins: true)
end
end
end
diff --git a/lib/gitlab/graphql/calls_gitaly.rb b/lib/gitlab/graphql/calls_gitaly.rb
new file mode 100644
index 00000000000..40cd74a34f2
--- /dev/null
+++ b/lib/gitlab/graphql/calls_gitaly.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ # Wraps the field resolution to count Gitaly calls before and after.
+ # Raises an error if the field calls Gitaly but hadn't declared such.
+ module CallsGitaly
+ extend ActiveSupport::Concern
+
+ def self.use(schema_definition)
+ schema_definition.instrument(:field, Gitlab::Graphql::CallsGitaly::Instrumentation.new, after_built_ins: true)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/calls_gitaly/instrumentation.rb b/lib/gitlab/graphql/calls_gitaly/instrumentation.rb
new file mode 100644
index 00000000000..fbd5e348c7d
--- /dev/null
+++ b/lib/gitlab/graphql/calls_gitaly/instrumentation.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module CallsGitaly
+ class Instrumentation
+ # Check if any `calls_gitaly: true` declarations need to be added
+ # Do nothing if a constant complexity was provided
+ def instrument(_type, field)
+ type_object = field.metadata[:type_class]
+ return field unless type_object.respond_to?(:calls_gitaly?)
+ return field if type_object.constant_complexity? || type_object.calls_gitaly?
+
+ old_resolver_proc = field.resolve_proc
+
+ gitaly_wrapped_resolve = -> (typed_object, args, ctx) do
+ previous_gitaly_call_count = Gitlab::GitalyClient.get_request_count
+ result = old_resolver_proc.call(typed_object, args, ctx)
+ current_gitaly_call_count = Gitlab::GitalyClient.get_request_count
+ calls_gitaly_check(type_object, current_gitaly_call_count - previous_gitaly_call_count)
+ result
+ end
+
+ field.redefine do
+ resolve(gitaly_wrapped_resolve)
+ end
+ end
+
+ def calls_gitaly_check(type_object, calls)
+ return if calls < 1
+
+ # Will inform you if there needs to be `calls_gitaly: true` as a kwarg in the field declaration
+ # if there is at least 1 Gitaly call involved with the field resolution.
+ error = RuntimeError.new("Gitaly is called for field '#{type_object.name}' on #{type_object.owner.try(:name)} - please either specify a constant complexity or add `calls_gitaly: true` to the field declaration")
+ Gitlab::Sentry.track_exception(error)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/mount_mutation.rb b/lib/gitlab/graphql/mount_mutation.rb
index 9048967d4e1..b10e963170a 100644
--- a/lib/gitlab/graphql/mount_mutation.rb
+++ b/lib/gitlab/graphql/mount_mutation.rb
@@ -6,11 +6,12 @@ module Gitlab
extend ActiveSupport::Concern
class_methods do
- def mount_mutation(mutation_class)
+ def mount_mutation(mutation_class, **custom_kwargs)
# Using an underscored field name symbol will make `graphql-ruby`
# standardize the field name
field mutation_class.graphql_name.underscore.to_sym,
- mutation: mutation_class
+ mutation: mutation_class,
+ **custom_kwargs
end
end
end
diff --git a/lib/gitlab/http.rb b/lib/gitlab/http.rb
index db2b4dde244..58bce613a98 100644
--- a/lib/gitlab/http.rb
+++ b/lib/gitlab/http.rb
@@ -10,9 +10,9 @@ module Gitlab
RedirectionTooDeep = Class.new(StandardError)
HTTP_ERRORS = [
- SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET,
- Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout,
- Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError,
+ SocketError, OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError,
+ Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
+ Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError,
Gitlab::HTTP::RedirectionTooDeep
].freeze
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index a0fb051e806..01437c67fa9 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -160,6 +160,7 @@ excluded_attributes:
- :milestone_id
- :ref_fetched
- :merge_jid
+ - :rebase_jid
- :latest_merge_request_diff_id
award_emoji:
- :awardable_id
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index 17eacbd21d8..eef802caabb 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -6,6 +6,12 @@ module Gitlab
module Metrics
module Samplers
class RubySampler < BaseSampler
+ def initialize(interval)
+ metrics[:process_start_time_seconds].set(labels.merge(worker_label), Time.now.to_i)
+
+ super
+ end
+
def metrics
@metrics ||= init_metrics
end
@@ -47,7 +53,6 @@ module Gitlab
metrics[:file_descriptors].set(labels.merge(worker_label), System.file_descriptor_count)
metrics[:process_cpu_seconds_total].set(labels.merge(worker_label), ::Gitlab::Metrics::System.cpu_time)
metrics[:process_max_fds].set(labels.merge(worker_label), ::Gitlab::Metrics::System.max_open_file_descriptors)
- metrics[:process_start_time_seconds].set(labels.merge(worker_label), ::Gitlab::Metrics::System.process_start_time)
set_memory_usage_metrics
sample_gc
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 34de40ca72f..5c2f07b95e2 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -31,14 +31,6 @@ module Gitlab
match[1].to_i
end
-
- def self.process_start_time
- fields = File.read('/proc/self/stat').split
-
- # fields[21] is linux proc stat field "(22) starttime".
- # The value is expressed in clock ticks, divide by clock ticks for seconds.
- ( fields[21].to_i || 0 ) / clk_tck
- end
else
def self.memory_usage
0.0
@@ -51,10 +43,6 @@ module Gitlab
def self.max_open_file_descriptors
0
end
-
- def self.process_start_time
- 0
- end
end
def self.cpu_time
diff --git a/lib/gitlab/namespaced_session_store.rb b/lib/gitlab/namespaced_session_store.rb
index 34520078bfb..f0f24c081c3 100644
--- a/lib/gitlab/namespaced_session_store.rb
+++ b/lib/gitlab/namespaced_session_store.rb
@@ -4,19 +4,24 @@ module Gitlab
class NamespacedSessionStore
delegate :[], :[]=, to: :store
- def initialize(key)
+ def initialize(key, session = Session.current)
@key = key
+ @session = session
end
def initiated?
- !Session.current.nil?
+ !session.nil?
end
def store
- return unless Session.current
+ return unless session
- Session.current[@key] ||= {}
- Session.current[@key]
+ session[@key] ||= {}
+ session[@key]
end
+
+ private
+
+ attr_reader :session
end
end
diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb
index 4b0c7b5c7f8..07439d8e011 100644
--- a/lib/gitlab/performance_bar.rb
+++ b/lib/gitlab/performance_bar.rb
@@ -3,7 +3,8 @@
module Gitlab
module PerformanceBar
ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids:v2'.freeze
- EXPIRY_TIME = 5.minutes
+ EXPIRY_TIME_L1_CACHE = 1.minute
+ EXPIRY_TIME_L2_CACHE = 5.minutes
def self.enabled?(user = nil)
return true if Rails.env.development?
@@ -19,20 +20,31 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def self.allowed_user_ids
- Rails.cache.fetch(ALLOWED_USER_IDS_KEY, expires_in: EXPIRY_TIME) do
- group = Group.find_by_id(allowed_group_id)
+ l1_cache_backend.fetch(ALLOWED_USER_IDS_KEY, expires_in: EXPIRY_TIME_L1_CACHE) do
+ l2_cache_backend.fetch(ALLOWED_USER_IDS_KEY, expires_in: EXPIRY_TIME_L2_CACHE) do
+ group = Group.find_by_id(allowed_group_id)
- if group
- GroupMembersFinder.new(group).execute.pluck(:user_id)
- else
- []
+ if group
+ GroupMembersFinder.new(group).execute.pluck(:user_id)
+ else
+ []
+ end
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
def self.expire_allowed_user_ids_cache
- Rails.cache.delete(ALLOWED_USER_IDS_KEY)
+ l1_cache_backend.delete(ALLOWED_USER_IDS_KEY)
+ l2_cache_backend.delete(ALLOWED_USER_IDS_KEY)
+ end
+
+ def self.l1_cache_backend
+ Gitlab::ThreadMemoryCache.cache_backend
+ end
+
+ def self.l2_cache_backend
+ Rails.cache
end
end
end
diff --git a/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb b/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb
new file mode 100644
index 00000000000..2d997760c46
--- /dev/null
+++ b/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+# Adapted from https://github.com/peek/peek/blob/master/lib/peek/adapters/redis.rb
+module Gitlab
+ module PerformanceBar
+ module RedisAdapterWhenPeekEnabled
+ def save
+ super unless ::Peek.request_id.blank?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/issuable_actions.rb b/lib/gitlab/quick_actions/issuable_actions.rb
index 572c55efcc2..f7f89d4e897 100644
--- a/lib/gitlab/quick_actions/issuable_actions.rb
+++ b/lib/gitlab/quick_actions/issuable_actions.rb
@@ -146,8 +146,8 @@ module Gitlab
@updates[:todo_event] = 'add'
end
- desc _('Mark todo as done')
- explanation _('Marks todo as done.')
+ desc _('Mark to do as done')
+ explanation _('Marks to do as done.')
types Issuable
condition do
quick_action_target.persisted? &&
diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb
index 583a970bf4e..0f890a12134 100644
--- a/lib/gitlab/sidekiq_status.rb
+++ b/lib/gitlab/sidekiq_status.rb
@@ -53,14 +53,14 @@ module Gitlab
self.num_running(job_ids).zero?
end
- # Returns true if the given job is running
+ # Returns true if the given job is running or enqueued.
#
# job_id - The Sidekiq job ID to check.
def self.running?(job_id)
num_running([job_id]) > 0
end
- # Returns the number of jobs that are running.
+ # Returns the number of jobs that are running or enqueued.
#
# job_ids - The Sidekiq job IDs to check.
def self.num_running(job_ids)
@@ -81,7 +81,7 @@ module Gitlab
# job_ids - The Sidekiq job IDs to check.
#
# Returns an array of true or false indicating job completion.
- # true = job is still running
+ # true = job is still running or enqueued
# false = job completed
def self.job_status(job_ids)
keys = job_ids.map { |jid| key_for(jid) }
diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb
index fd108b4c124..f6edbfced7f 100644
--- a/lib/gitlab/sql/pattern.rb
+++ b/lib/gitlab/sql/pattern.rb
@@ -9,14 +9,16 @@ module Gitlab
REGEX_QUOTED_WORD = /(?<=\A| )"[^"]+"(?= |\z)/.freeze
class_methods do
- def fuzzy_search(query, columns)
- matches = columns.map { |col| fuzzy_arel_match(col, query) }.compact.reduce(:or)
+ def fuzzy_search(query, columns, use_minimum_char_limit: true)
+ matches = columns.map do |col|
+ fuzzy_arel_match(col, query, use_minimum_char_limit: use_minimum_char_limit)
+ end.compact.reduce(:or)
where(matches)
end
- def to_pattern(query)
- if partial_matching?(query)
+ def to_pattern(query, use_minimum_char_limit: true)
+ if partial_matching?(query, use_minimum_char_limit: use_minimum_char_limit)
"%#{sanitize_sql_like(query)}%"
else
sanitize_sql_like(query)
@@ -27,7 +29,9 @@ module Gitlab
MIN_CHARS_FOR_PARTIAL_MATCHING
end
- def partial_matching?(query)
+ def partial_matching?(query, use_minimum_char_limit: true)
+ return true unless use_minimum_char_limit
+
query.length >= min_chars_for_partial_matching
end
@@ -35,14 +39,14 @@ module Gitlab
# query - The text to search for.
# lower_exact_match - When set to `true` we'll fall back to using
# `LOWER(column) = query` instead of using `ILIKE`.
- def fuzzy_arel_match(column, query, lower_exact_match: false)
+ def fuzzy_arel_match(column, query, lower_exact_match: false, use_minimum_char_limit: true)
query = query.squish
return unless query.present?
- words = select_fuzzy_words(query)
+ words = select_fuzzy_words(query, use_minimum_char_limit: use_minimum_char_limit)
if words.any?
- words.map { |word| arel_table[column].matches(to_pattern(word)) }.reduce(:and)
+ words.map { |word| arel_table[column].matches(to_pattern(word, use_minimum_char_limit: use_minimum_char_limit)) }.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
@@ -56,7 +60,7 @@ module Gitlab
end
end
- def select_fuzzy_words(query)
+ def select_fuzzy_words(query, use_minimum_char_limit: true)
quoted_words = query.scan(REGEX_QUOTED_WORD)
query = quoted_words.reduce(query) { |q, quoted_word| q.sub(quoted_word, '') }
@@ -67,7 +71,7 @@ module Gitlab
words.concat(quoted_words)
- words.select { |word| partial_matching?(word) }
+ words.select { |word| partial_matching?(word, use_minimum_char_limit: use_minimum_char_limit) }
end
end
end
diff --git a/lib/gitlab/user_extractor.rb b/lib/gitlab/user_extractor.rb
deleted file mode 100644
index ede60c9ab1d..00000000000
--- a/lib/gitlab/user_extractor.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# frozen_string_literal: true
-
-# This class extracts all users found in a piece of text by the username or the
-# email address
-
-module Gitlab
- class UserExtractor
- # Not using `Devise.email_regexp` to filter out any chars that an email
- # does not end with and not pinning the email to a start of end of a string.
- EMAIL_REGEXP = /(?<email>([^@\s]+@[^@\s]+(?<!\W)))/.freeze
- USERNAME_REGEXP = User.reference_pattern
-
- def initialize(text)
- # EE passes an Array to `text` in a few places, so we want to support both
- # here.
- @text = Array(text).join(' ')
- end
-
- def users
- return User.none unless @text.present?
- return User.none if references.empty?
-
- @users ||= User.from_union(union_relations)
- end
-
- def usernames
- matches[:usernames]
- end
-
- def emails
- matches[:emails]
- end
-
- def references
- @references ||= matches.values.flatten
- end
-
- def matches
- @matches ||= {
- emails: @text.scan(EMAIL_REGEXP).flatten.uniq,
- usernames: @text.scan(USERNAME_REGEXP).flatten.uniq
- }
- end
-
- private
-
- def union_relations
- relations = []
-
- relations << User.by_any_email(emails) if emails.any?
- relations << User.by_username(usernames) if usernames.any?
-
- relations
- end
- end
-end
diff --git a/lib/peek/views/redis.rb b/lib/peek/views/redis.rb
index ad3c3c9fe01..73de8672fa4 100644
--- a/lib/peek/views/redis.rb
+++ b/lib/peek/views/redis.rb
@@ -37,6 +37,8 @@ end
module Peek
module Views
module RedisDetailed
+ REDACTED_MARKER = "<redacted>"
+
def results
super.merge(details: details)
end
@@ -57,10 +59,12 @@ module Peek
end
def format_command(cmd)
+ if cmd.length >= 2 && cmd.first =~ /^auth$/i
+ cmd[-1] = REDACTED_MARKER
# Scrub out the value of the SET calls to avoid binary
# data or large data from spilling into the view
- if cmd.length >= 2 && cmd.first =~ /set/i
- cmd[-1] = "<redacted>"
+ elsif cmd.length >= 3 && cmd.first =~ /set/i
+ cmd[2..-1] = REDACTED_MARKER
end
cmd.join(' ')