diff options
| author | Dennis Tang <dtang@gitlab.com> | 2018-05-11 18:42:20 +0200 |
|---|---|---|
| committer | Dennis Tang <dtang@gitlab.com> | 2018-05-11 18:42:20 +0200 |
| commit | 2205ed4f07265a43d2561ab2657557e317e7b9c0 (patch) | |
| tree | 08e8373f874b59e78d97dd87b1c687d3458823d9 /lib | |
| parent | 3a3f4a348be936abde6881fc3909026932bf97ab (diff) | |
| parent | f4e234d92a2ff31dc681d56b52e9fbbbe3f931b1 (diff) | |
| download | gitlab-ce-2205ed4f07265a43d2561ab2657557e317e7b9c0.tar.gz | |
Merge remote-tracking branch 'origin/master' into 38759-fetch-available-parameters-directly-from-gke-when-creating-a-cluster
Diffstat (limited to 'lib')
37 files changed, 600 insertions, 84 deletions
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index c2113551207..c17089759de 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -45,7 +45,9 @@ module API user = find_user_from_sources return unless user - forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api) + unless api_access_allowed?(user) + forbidden!(api_access_denied_message(user)) + end user end @@ -72,6 +74,14 @@ module API end end end + + def api_access_allowed?(user) + Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api) + end + + def api_access_denied_message(user) + Gitlab::Auth::UserAccessDeniedReason.new(user).rejection_message + end end module ClassMethods diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 75d56b82424..25d78fc761d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -136,6 +136,7 @@ module API def self.preload_relation(projects_relation, options = {}) projects_relation.preload(:project_feature, :route) + .preload(:import_state) .preload(namespace: [:route, :owner], tags: :taggings) end @@ -149,11 +150,11 @@ module API expose_url(api_v4_projects_path(id: project.id)) end - expose :issues, if: -> (*args) { issues_available?(*args) } do |project| + expose :issues, if: -> (project, options) { issues_available?(project, options) } do |project| expose_url(api_v4_projects_issues_path(id: project.id)) end - expose :merge_requests, if: -> (*args) { mrs_available?(*args) } do |project| + expose :merge_requests, if: -> (project, options) { mrs_available?(project, options) } do |project| expose_url(api_v4_projects_merge_requests_path(id: project.id)) end @@ -242,13 +243,18 @@ module API expose :requested_at end - class Group < Grape::Entity - expose :id, :name, :path, :description, :visibility + class BasicGroupDetails < Grape::Entity + expose :id + expose :web_url + expose :name + end + + class Group < BasicGroupDetails + expose :path, :description, :visibility expose :lfs_enabled?, as: :lfs_enabled expose :avatar_url do |group, options| group.avatar_url(only_path: false) end - expose :web_url expose :request_access_enabled expose :full_name, :full_path @@ -961,6 +967,7 @@ module API class Runner < Grape::Entity expose :id expose :description + expose :ip_address expose :active expose :is_shared expose :name @@ -984,6 +991,13 @@ module API options[:current_user].authorized_projects.where(id: runner.projects) end end + expose :groups, with: Entities::BasicGroupDetails do |runner, options| + if options[:current_user].admin? + runner.groups + else + options[:current_user].authorized_groups.where(id: runner.groups) + end + end end class RunnerRegistrationDetails < Grape::Entity diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 4d4fbe50f9f..649feba1036 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -11,22 +11,26 @@ module API requires :token, type: String, desc: 'Registration token' optional :description, type: String, desc: %q(Runner's description) optional :info, type: Hash, desc: %q(Runner's metadata) + optional :active, type: Boolean, desc: 'Should Runner be active' optional :locked, type: Boolean, desc: 'Should Runner be locked for current project' optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs' optional :tag_list, type: Array[String], desc: %q(List of Runner's tags) optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' end post '/' do - attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list, :maximum_timeout]) + attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :maximum_timeout]) .merge(get_runner_details_from_request) runner = if runner_registration_token_valid? # Create shared runner. Requires admin access - Ci::Runner.create(attributes.merge(is_shared: true)) + Ci::Runner.create(attributes.merge(is_shared: true, runner_type: :instance_type)) elsif project = Project.find_by(runners_token: params[:token]) - # Create a specific runner for project. - project.runners.create(attributes) + # Create a specific runner for the project + project.runners.create(attributes.merge(runner_type: :project_type)) + elsif group = Group.find_by(runners_token: params[:token]) + # Create a specific runner for the group + group.runners.create(attributes.merge(runner_type: :group_type)) end break forbidden! unless runner @@ -150,9 +154,20 @@ module API content_range = request.headers['Content-Range'] content_range = content_range.split('-') - stream_size = job.trace.append(request.body.read, content_range[0].to_i) - if stream_size < 0 - break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{-stream_size}" }) + # TODO: + # it seems that `Content-Range` as formatted by runner is wrong, + # the `byte_end` should point to final byte, but it points byte+1 + # that means that we have to calculate end of body, + # as we cannot use `content_length[1]` + # Issue: https://gitlab.com/gitlab-org/gitlab-runner/issues/3275 + + body_data = request.body.read + body_start = content_range[0].to_i + body_end = body_start + body_data.bytesize + + stream_size = job.trace.append(body_data, body_start) + unless stream_size == body_end + break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{stream_size}" }) end status 202 diff --git a/lib/gitlab/auth/omniauth_identity_linker_base.rb b/lib/gitlab/auth/omniauth_identity_linker_base.rb index ae365fcdfaa..f79ce6bb809 100644 --- a/lib/gitlab/auth/omniauth_identity_linker_base.rb +++ b/lib/gitlab/auth/omniauth_identity_linker_base.rb @@ -17,6 +17,10 @@ module Gitlab @changed end + def failed? + error_message.present? + end + def error_message identity.validate diff --git a/lib/gitlab/auth/user_access_denied_reason.rb b/lib/gitlab/auth/user_access_denied_reason.rb new file mode 100644 index 00000000000..af310aa12fc --- /dev/null +++ b/lib/gitlab/auth/user_access_denied_reason.rb @@ -0,0 +1,33 @@ +module Gitlab + module Auth + class UserAccessDeniedReason + def initialize(user) + @user = user + end + + def rejection_message + case rejection_type + when :internal + 'This action cannot be performed by internal users' + when :terms_not_accepted + 'You must accept the Terms of Service in order to perform this action. '\ + 'Please access GitLab from a web browser to accept these terms.' + else + 'Your account has been blocked.' + end + end + + private + + def rejection_type + if @user.internal? + :internal + elsif @user.required_terms_not_accepted? + :terms_not_accepted + else + :blocked + end + end + end + end +end diff --git a/lib/gitlab/background_migration/populate_import_state.rb b/lib/gitlab/background_migration/populate_import_state.rb new file mode 100644 index 00000000000..695a2a713c5 --- /dev/null +++ b/lib/gitlab/background_migration/populate_import_state.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # This background migration creates all the records on the + # import state table for projects that are considered imports or forks + class PopulateImportState + def perform(start_id, end_id) + move_attributes_data_to_import_state(start_id, end_id) + rescue ActiveRecord::RecordNotUnique + retry + end + + def move_attributes_data_to_import_state(start_id, end_id) + Rails.logger.info("#{self.class.name} - Moving import attributes data to project mirror data table: #{start_id} - #{end_id}") + + ActiveRecord::Base.connection.execute <<~SQL + INSERT INTO project_mirror_data (project_id, status, jid, last_error) + SELECT id, import_status, import_jid, import_error + FROM projects + WHERE projects.import_status != 'none' + AND projects.id BETWEEN #{start_id} AND #{end_id} + AND NOT EXISTS ( + SELECT id + FROM project_mirror_data + WHERE project_id = projects.id + ) + SQL + + ActiveRecord::Base.connection.execute <<~SQL + UPDATE projects + SET import_status = 'none' + WHERE import_status != 'none' + AND id BETWEEN #{start_id} AND #{end_id} + SQL + end + end + end +end diff --git a/lib/gitlab/background_migration/rollback_import_state_data.rb b/lib/gitlab/background_migration/rollback_import_state_data.rb new file mode 100644 index 00000000000..a7c986747d8 --- /dev/null +++ b/lib/gitlab/background_migration/rollback_import_state_data.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # This background migration migrates all the data of import_state + # back to the projects table for projects that are considered imports or forks + class RollbackImportStateData + def perform(start_id, end_id) + move_attributes_data_to_project(start_id, end_id) + end + + def move_attributes_data_to_project(start_id, end_id) + Rails.logger.info("#{self.class.name} - Moving import attributes data to projects table: #{start_id} - #{end_id}") + + if Gitlab::Database.mysql? + ActiveRecord::Base.connection.execute <<~SQL + UPDATE projects, project_mirror_data + SET + projects.import_status = project_mirror_data.status, + projects.import_jid = project_mirror_data.jid, + projects.import_error = project_mirror_data.last_error + WHERE project_mirror_data.project_id = projects.id + AND project_mirror_data.id BETWEEN #{start_id} AND #{end_id} + SQL + else + ActiveRecord::Base.connection.execute <<~SQL + UPDATE projects + SET + import_status = project_mirror_data.status, + import_jid = project_mirror_data.jid, + import_error = project_mirror_data.last_error + FROM project_mirror_data + WHERE project_mirror_data.project_id = projects.id + AND project_mirror_data.id BETWEEN #{start_id} AND #{end_id} + SQL + end + end + end + end +end diff --git a/lib/gitlab/build_access.rb b/lib/gitlab/build_access.rb new file mode 100644 index 00000000000..08a8f846ca5 --- /dev/null +++ b/lib/gitlab/build_access.rb @@ -0,0 +1,12 @@ +module Gitlab + class BuildAccess < UserAccess + attr_accessor :user, :project + + # This bypasses the `can?(:access_git)`-check we normally do in `UserAccess` + # for CI. That way if a user was able to trigger a pipeline, then the + # build is allowed to clone the project. + def can_access_git? + true + end + end +end diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb index 70732d26bbd..b5eb0cfa2f0 100644 --- a/lib/gitlab/ci/pipeline/chain/build.rb +++ b/lib/gitlab/ci/pipeline/chain/build.rb @@ -14,7 +14,8 @@ module Gitlab trigger_requests: Array(@command.trigger_request), user: @command.current_user, pipeline_schedule: @command.schedule, - protected: @command.protected_ref? + protected: @command.protected_ref?, + variables_attributes: Array(@command.variables_attributes) ) @pipeline.set_config_source diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb index a1849b01c5d..a53c80d34f7 100644 --- a/lib/gitlab/ci/pipeline/chain/command.rb +++ b/lib/gitlab/ci/pipeline/chain/command.rb @@ -7,7 +7,7 @@ module Gitlab # rubocop:disable Naming/FileName :origin_ref, :checkout_sha, :after_sha, :before_sha, :trigger_request, :schedule, :ignore_skip_ci, :save_incompleted, - :seeds_block + :seeds_block, :variables_attributes ) do include Gitlab::Utils::StrongMemoize diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb index 47b67930c6d..fe15fabc2e8 100644 --- a/lib/gitlab/ci/trace.rb +++ b/lib/gitlab/ci/trace.rb @@ -36,16 +36,16 @@ module Gitlab end def set(data) - write do |stream| + write('w+b') do |stream| data = job.hide_secrets(data) stream.set(data) end end def append(data, offset) - write do |stream| + write('a+b') do |stream| current_length = stream.size - break -current_length unless current_length == offset + break current_length unless current_length == offset data = job.hide_secrets(data) stream.append(data, offset) @@ -54,13 +54,15 @@ module Gitlab end def exist? - trace_artifact&.exists? || current_path.present? || old_trace.present? + trace_artifact&.exists? || job.trace_chunks.any? || current_path.present? || old_trace.present? end def read stream = Gitlab::Ci::Trace::Stream.new do if trace_artifact trace_artifact.open + elsif job.trace_chunks.any? + Gitlab::Ci::Trace::ChunkedIO.new(job) elsif current_path File.open(current_path, "rb") elsif old_trace @@ -73,9 +75,15 @@ module Gitlab stream&.close end - def write + def write(mode) stream = Gitlab::Ci::Trace::Stream.new do - File.open(ensure_path, "a+b") + if current_path + File.open(current_path, mode) + elsif Feature.enabled?('ci_enable_live_trace') + Gitlab::Ci::Trace::ChunkedIO.new(job) + else + File.open(ensure_path, mode) + end end yield(stream).tap do @@ -92,6 +100,7 @@ module Gitlab FileUtils.rm(trace_path, force: true) end + job.trace_chunks.fast_destroy_all job.erase_old_trace! end @@ -99,7 +108,12 @@ module Gitlab raise ArchiveError, 'Already archived' if trace_artifact raise ArchiveError, 'Job is not finished yet' unless job.complete? - if current_path + if job.trace_chunks.any? + Gitlab::Ci::Trace::ChunkedIO.new(job) do |stream| + archive_stream!(stream) + stream.destroy! + end + elsif current_path File.open(current_path) do |stream| archive_stream!(stream) FileUtils.rm(current_path) @@ -116,7 +130,7 @@ module Gitlab def archive_stream!(stream) clone_file!(stream, JobArtifactUploader.workhorse_upload_path) do |clone_path| - create_job_trace!(job, clone_path) + create_build_trace!(job, clone_path) end end @@ -132,7 +146,7 @@ module Gitlab end end - def create_job_trace!(job, path) + def create_build_trace!(job, path) File.open(path) do |stream| job.create_job_artifacts_trace!( project: job.project, diff --git a/lib/gitlab/ci/trace/chunked_io.rb b/lib/gitlab/ci/trace/chunked_io.rb new file mode 100644 index 00000000000..bfe0c2a2c26 --- /dev/null +++ b/lib/gitlab/ci/trace/chunked_io.rb @@ -0,0 +1,231 @@ +## +# This class is compatible with IO class (https://ruby-doc.org/core-2.3.1/IO.html) +# source: https://gitlab.com/snippets/1685610 +module Gitlab + module Ci + class Trace + class ChunkedIO + CHUNK_SIZE = ::Ci::BuildTraceChunk::CHUNK_SIZE + + FailedToGetChunkError = Class.new(StandardError) + + attr_reader :build + attr_reader :tell, :size + attr_reader :chunk, :chunk_range + + alias_method :pos, :tell + + def initialize(build, &block) + @build = build + @chunks_cache = [] + @tell = 0 + @size = calculate_size + yield self if block_given? + end + + def close + # no-op + end + + def binmode + # no-op + end + + def binmode? + true + end + + def seek(pos, where = IO::SEEK_SET) + new_pos = + case where + when IO::SEEK_END + size + pos + when IO::SEEK_SET + pos + when IO::SEEK_CUR + tell + pos + else + -1 + end + + raise ArgumentError, 'new position is outside of file' if new_pos < 0 || new_pos > size + + @tell = new_pos + end + + def eof? + tell == size + end + + def each_line + until eof? + line = readline + break if line.nil? + + yield(line) + end + end + + def read(length = nil, outbuf = "") + out = "" + + length ||= size - tell + + until length <= 0 || eof? + data = chunk_slice_from_offset + break if data.empty? + + chunk_bytes = [CHUNK_SIZE - chunk_offset, length].min + chunk_data = data.byteslice(0, chunk_bytes) + + out << chunk_data + @tell += chunk_data.bytesize + length -= chunk_data.bytesize + end + + # If outbuf is passed, we put the output into the buffer. This supports IO.copy_stream functionality + if outbuf + outbuf.slice!(0, outbuf.bytesize) + outbuf << out + end + + out + end + + def readline + out = "" + + until eof? + data = chunk_slice_from_offset + new_line = data.index("\n") + + if !new_line.nil? + out << data[0..new_line] + @tell += new_line + 1 + break + else + out << data + @tell += data.bytesize + end + end + + out + end + + def write(data) + start_pos = tell + + while tell < start_pos + data.bytesize + # get slice from current offset till the end where it falls into chunk + chunk_bytes = CHUNK_SIZE - chunk_offset + chunk_data = data.byteslice(tell - start_pos, chunk_bytes) + + # append data to chunk, overwriting from that point + ensure_chunk.append(chunk_data, chunk_offset) + + # move offsets within buffer + @tell += chunk_data.bytesize + @size = [size, tell].max + end + + tell - start_pos + ensure + invalidate_chunk_cache + end + + def truncate(offset) + raise ArgumentError, 'Outside of file' if offset > size || offset < 0 + return if offset == size # Skip the following process as it doesn't affect anything + + @tell = offset + @size = offset + + # remove all next chunks + trace_chunks.where('chunk_index > ?', chunk_index).fast_destroy_all + + # truncate current chunk + current_chunk.truncate(chunk_offset) + ensure + invalidate_chunk_cache + end + + def flush + # no-op + end + + def present? + true + end + + def destroy! + trace_chunks.fast_destroy_all + @tell = @size = 0 + ensure + invalidate_chunk_cache + end + + private + + ## + # The below methods are not implemented in IO class + # + def in_range? + @chunk_range&.include?(tell) + end + + def chunk_slice_from_offset + unless in_range? + current_chunk.tap do |chunk| + raise FailedToGetChunkError unless chunk + + @chunk = chunk.data + @chunk_range = chunk.range + end + end + + @chunk[chunk_offset..CHUNK_SIZE] + end + + def chunk_offset + tell % CHUNK_SIZE + end + + def chunk_index + tell / CHUNK_SIZE + end + + def chunk_start + chunk_index * CHUNK_SIZE + end + + def chunk_end + [chunk_start + CHUNK_SIZE, size].min + end + + def invalidate_chunk_cache + @chunks_cache = [] + end + + def current_chunk + @chunks_cache[chunk_index] ||= trace_chunks.find_by(chunk_index: chunk_index) + end + + def build_chunk + @chunks_cache[chunk_index] = ::Ci::BuildTraceChunk.new(build: build, chunk_index: chunk_index) + end + + def ensure_chunk + current_chunk || build_chunk + end + + def trace_chunks + ::Ci::BuildTraceChunk.where(build: build) + end + + def calculate_size + trace_chunks.order(chunk_index: :desc).first.try(&:end_offset).to_i + end + end + end + end +end diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb index 187ad8b833a..a71040e5e56 100644 --- a/lib/gitlab/ci/trace/stream.rb +++ b/lib/gitlab/ci/trace/stream.rb @@ -39,6 +39,8 @@ module Gitlab end def append(data, offset) + data = data.force_encoding(Encoding::BINARY) + stream.truncate(offset) stream.seek(0, IO::SEEK_END) stream.write(data) @@ -46,8 +48,11 @@ module Gitlab end def set(data) - truncate(0) + data = data.force_encoding(Encoding::BINARY) + + stream.seek(0, IO::SEEK_SET) stream.write(data) + stream.truncate(data.bytesize) stream.flush() end @@ -127,11 +132,11 @@ module Gitlab buf += debris debris, *lines = buf.each_line.to_a lines.reverse_each do |line| - yield(line.force_encoding('UTF-8')) + yield(line.force_encoding(Encoding.default_external)) end end - yield(debris.force_encoding('UTF-8')) unless debris.empty? + yield(debris.force_encoding(Encoding.default_external)) unless debris.empty? end def read_backward(length) diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb index 8eea33b9ab5..5791dbd0484 100644 --- a/lib/gitlab/email/handler/create_note_handler.rb +++ b/lib/gitlab/email/handler/create_note_handler.rb @@ -8,6 +8,7 @@ module Gitlab include ReplyProcessing delegate :project, to: :sent_notification, allow_nil: true + delegate :noteable, to: :sent_notification def can_handle? mail_key =~ /\A\w+\z/ @@ -18,7 +19,7 @@ module Gitlab validate_permission!(:create_note) - raise NoteableNotFoundError unless sent_notification.noteable + raise NoteableNotFoundError unless noteable raise EmptyEmailError if message.blank? verify_record!( diff --git a/lib/gitlab/email/handler/reply_processing.rb b/lib/gitlab/email/handler/reply_processing.rb index 32c5caf93e8..da5ff350549 100644 --- a/lib/gitlab/email/handler/reply_processing.rb +++ b/lib/gitlab/email/handler/reply_processing.rb @@ -32,8 +32,12 @@ module Gitlab def validate_permission!(permission) raise UserNotFoundError unless author raise UserBlockedError if author.blocked? - raise ProjectNotFound unless author.can?(:read_project, project) - raise UserNotAuthorizedError unless author.can?(permission, project) + + if project + raise ProjectNotFound unless author.can?(:read_project, project) + end + + raise UserNotAuthorizedError unless author.can?(permission, project || noteable) end def verify_record!(record:, invalid_exception:, record_name:) diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb index cc2638172ec..77af7a868d0 100644 --- a/lib/gitlab/file_detector.rb +++ b/lib/gitlab/file_detector.rb @@ -14,6 +14,7 @@ module Gitlab avatar: /\Alogo\.(png|jpg|gif)\z/, issue_template: %r{\A\.gitlab/issue_templates/[^/]+\.md\z}, merge_request_template: %r{\A\.gitlab/merge_request_templates/[^/]+\.md\z}, + xcode_config: %r{\A[^/]*\.(xcodeproj|xcworkspace)\z}, # Configuration files gitignore: '.gitignore', diff --git a/lib/gitlab/git/blame.rb b/lib/gitlab/git/blame.rb index 6d6ed065f79..4158d50cd9e 100644 --- a/lib/gitlab/git/blame.rb +++ b/lib/gitlab/git/blame.rb @@ -15,10 +15,7 @@ module Gitlab def each @blames.each do |blame| - yield( - Gitlab::Git::Commit.new(@repo, blame.commit), - blame.line - ) + yield(blame.commit, blame.line) end end @@ -60,9 +57,8 @@ module Gitlab end end - # load all commits in single call - commits.keys.each do |key| - commits[key] = @repo.lookup(key) + Gitlab::Git::Commit.batch_by_oid(@repo, commits.keys).each do |commit| + commits[commit.sha] = commit end # get it together diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index eabcf46cf58..d78d29b7ac6 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -62,6 +62,12 @@ module Gitlab end end + # Returns an array of Blob instances just with the metadata, that means + # the data attribute has no content. + def batch_metadata(repository, blob_references) + batch(repository, blob_references, blob_size_limit: 0) + end + # Find LFS blobs given an array of sha ids # Returns array of Gitlab::Git::Blob # Does not guarantee blob data will be set diff --git a/lib/gitlab/git/gitlab_projects.rb b/lib/gitlab/git/gitlab_projects.rb index 099709620b3..68373460d23 100644 --- a/lib/gitlab/git/gitlab_projects.rb +++ b/lib/gitlab/git/gitlab_projects.rb @@ -63,7 +63,8 @@ module Gitlab end def fork_repository(new_shard_name, new_repository_relative_path) - Gitlab::GitalyClient.migrate(:fork_repository) do |is_enabled| + Gitlab::GitalyClient.migrate(:fork_repository, + status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_fork_repository(new_shard_name, new_repository_relative_path) else diff --git a/lib/gitlab/git/raw_diff_change.rb b/lib/gitlab/git/raw_diff_change.rb index 92f6c45ce25..98de9328071 100644 --- a/lib/gitlab/git/raw_diff_change.rb +++ b/lib/gitlab/git/raw_diff_change.rb @@ -6,7 +6,15 @@ module Gitlab attr_reader :blob_id, :blob_size, :old_path, :new_path, :operation def initialize(raw_change) - parse(raw_change) + if raw_change.is_a?(Gitaly::GetRawChangesResponse::RawChange) + @blob_id = raw_change.blob_id + @blob_size = raw_change.size + @old_path = raw_change.old_path.presence + @new_path = raw_change.new_path.presence + @operation = raw_change.operation&.downcase || :unknown + else + parse(raw_change) + end end private @@ -20,13 +28,14 @@ module Gitlab # 85bc2f9753afd5f4fc5d7c75f74f8d526f26b4f3 107 R060\tfiles/js/commit.js.coffee\tfiles/js/commit.coffee def parse(raw_change) @blob_id, @blob_size, @raw_operation, raw_paths = raw_change.split(' ', 4) + @blob_size = @blob_size.to_i @operation = extract_operation @old_path, @new_path = extract_paths(raw_paths) end def extract_paths(file_path) case operation - when :renamed + when :copied, :renamed file_path.split(/\t/) when :deleted [file_path, nil] diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index de0044fc149..5d47f8b2075 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -20,6 +20,9 @@ module Gitlab GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE ].freeze SEARCH_CONTEXT_LINES = 3 + # In https://gitlab.com/gitlab-org/gitaly/merge_requests/698 + # We copied these two prefixes into gitaly-go, so don't change these + # or things will break! (REBASE_WORKTREE_PREFIX and SQUASH_WORKTREE_PREFIX) REBASE_WORKTREE_PREFIX = 'rebase'.freeze SQUASH_WORKTREE_PREFIX = 'squash'.freeze GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'.freeze @@ -27,6 +30,7 @@ module Gitlab EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000'.freeze NoRepository = Class.new(StandardError) + InvalidRepository = Class.new(StandardError) InvalidBlobName = Class.new(StandardError) InvalidRef = Class.new(StandardError) GitError = Class.new(StandardError) @@ -575,22 +579,44 @@ module Gitlab count_commits(from: from, to: to, **options) end + # Counts the amount of commits between `from` and `to`. + def count_commits_between(from, to, options = {}) + count_commits(from: from, to: to, **options) + end + # old_rev and new_rev are commit ID's # the result of this method is an array of Gitlab::Git::RawDiffChange def raw_changes_between(old_rev, new_rev) - result = [] + @raw_changes_between ||= {} - circuit_breaker.perform do - Open3.pipeline_r(git_diff_cmd(old_rev, new_rev), format_git_cat_file_script, git_cat_file_cmd) do |last_stdout, wait_threads| - last_stdout.each_line { |line| result << ::Gitlab::Git::RawDiffChange.new(line.chomp!) } + @raw_changes_between[[old_rev, new_rev]] ||= begin + return [] if new_rev.blank? || new_rev == Gitlab::Git::BLANK_SHA - if wait_threads.any? { |waiter| !waiter.value&.success? } - raise ::Gitlab::Git::Repository::GitError, "Unabled to obtain changes between #{old_rev} and #{new_rev}" + gitaly_migrate(:raw_changes_between) do |is_enabled| + if is_enabled + gitaly_repository_client.raw_changes_between(old_rev, new_rev) + .each_with_object([]) do |msg, arr| + msg.raw_changes.each { |change| arr << ::Gitlab::Git::RawDiffChange.new(change) } + end + else + result = [] + + circuit_breaker.perform do + Open3.pipeline_r(git_diff_cmd(old_rev, new_rev), format_git_cat_file_script, git_cat_file_cmd) do |last_stdout, wait_threads| + last_stdout.each_line { |line| result << ::Gitlab::Git::RawDiffChange.new(line.chomp!) } + + if wait_threads.any? { |waiter| !waiter.value&.success? } + raise ::Gitlab::Git::Repository::GitError, "Unabled to obtain changes between #{old_rev} and #{new_rev}" + end + end + end + + result end end end - - result + rescue ArgumentError => e + raise Gitlab::Git::Repository::GitError.new(e) end # Returns the SHA of the most recent common ancestor of +from+ and +to+ @@ -1569,7 +1595,8 @@ module Gitlab end def checksum - gitaly_migrate(:calculate_checksum) do |is_enabled| + gitaly_migrate(:calculate_checksum, + status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_repository_client.calculate_checksum else @@ -1670,10 +1697,14 @@ module Gitlab end end + # This function is duplicated in Gitaly-Go, don't change it! + # https://gitlab.com/gitlab-org/gitaly/merge_requests/698 def fresh_worktree?(path) File.exist?(path) && !clean_stuck_worktree(path) end + # This function is duplicated in Gitaly-Go, don't change it! + # https://gitlab.com/gitlab-org/gitaly/merge_requests/698 def clean_stuck_worktree(path) return false unless File.mtime(path) < 15.minutes.ago @@ -2514,10 +2545,12 @@ module Gitlab output, status = run_git(args) if status.nil? || !status.zero? - # Empty repositories return with a non-zero status and an empty output. - return EMPTY_REPOSITORY_CHECKSUM if output&.empty? + # Non-valid git repositories return 128 as the status code and an error output + raise InvalidRepository if status == 128 && output.to_s.downcase =~ /not a git repository/ + # Empty repositories returns with a non-zero status and an empty output. + raise ChecksumError, output unless output.blank? - raise ChecksumError, output + return EMPTY_REPOSITORY_CHECKSUM end refs = output.split("\n") diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb index 84a26fe4a6f..d75a5f15c29 100644 --- a/lib/gitlab/git/wiki.rb +++ b/lib/gitlab/git/wiki.rb @@ -67,7 +67,8 @@ module Gitlab end def page(title:, version: nil, dir: nil) - @repository.gitaly_migrate(:wiki_find_page) do |is_enabled| + @repository.gitaly_migrate(:wiki_find_page, + status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_find_page(title: title, version: version, dir: dir) else diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 0d1ee73ca1a..db7c29be94b 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -2,8 +2,6 @@ # class return an instance of `GitlabAccessStatus` module Gitlab class GitAccess - include Gitlab::Utils::StrongMemoize - UnauthorizedError = Class.new(StandardError) NotFoundError = Class.new(StandardError) ProjectCreationError = Class.new(StandardError) @@ -17,7 +15,6 @@ module Gitlab deploy_key_upload: 'This deploy key does not have write access to this project.', no_repo: 'A repository for this project does not exist yet.', project_not_found: 'The project you were looking for could not be found.', - account_blocked: 'Your account has been blocked.', command_not_allowed: "The command you're trying to execute is not allowed.", upload_pack_disabled_over_http: 'Pulling over HTTP is not allowed.', receive_pack_disabled_over_http: 'Pushing over HTTP is not allowed.', @@ -108,8 +105,11 @@ module Gitlab end def check_active_user! - if user && !user_access.allowed? - raise UnauthorizedError, ERROR_MESSAGES[:account_blocked] + return unless user + + unless user_access.allowed? + message = Gitlab::Auth::UserAccessDeniedReason.new(user).rejection_message + raise UnauthorizedError, message end end @@ -340,6 +340,8 @@ module Gitlab def user_access @user_access ||= if ci? CiAccess.new + elsif user && request_from_ci_build? + BuildAccess.new(user, project: project) else UserAccess.new(user, project: project) end diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index 498187997e1..132a5947f17 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -292,6 +292,14 @@ module Gitlab request = Gitaly::CalculateChecksumRequest.new(repository: @gitaly_repo) response = GitalyClient.call(@storage, :repository_service, :calculate_checksum, request) response.checksum.presence + rescue GRPC::DataLoss => e + raise Gitlab::Git::Repository::InvalidRepository.new(e) + end + + def raw_changes_between(from, to) + request = Gitaly::GetRawChangesRequest.new(repository: @gitaly_repo, from_revision: from, to_revision: to) + + GitalyClient.call(@storage, :repository_service, :get_raw_changes, request) end end end diff --git a/lib/gitlab/gitaly_client/storage_settings.rb b/lib/gitlab/gitaly_client/storage_settings.rb index 8668caf0c55..9a576e463e3 100644 --- a/lib/gitlab/gitaly_client/storage_settings.rb +++ b/lib/gitlab/gitaly_client/storage_settings.rb @@ -5,6 +5,14 @@ module Gitlab # directly. class StorageSettings DirectPathAccessError = Class.new(StandardError) + InvalidConfigurationError = Class.new(StandardError) + + INVALID_STORAGE_MESSAGE = <<~MSG.freeze + Storage is invalid because it has no `path` key. + + For source installations, update your config/gitlab.yml Refer to gitlab.yml.example for an updated example. + If you're using the Gitlab Development Kit, you can update your configuration running `gdk reconfigure`. + MSG # This class will give easily recognizable NoMethodErrors Deprecated = Class.new @@ -12,7 +20,8 @@ module Gitlab attr_reader :legacy_disk_path def initialize(storage) - raise "expected a Hash, got a #{storage.class.name}" unless storage.is_a?(Hash) + raise InvalidConfigurationError, "expected a Hash, got a #{storage.class.name}" unless storage.is_a?(Hash) + raise InvalidConfigurationError, INVALID_STORAGE_MESSAGE unless storage.has_key?('path') # Support a nil 'path' field because some of the circuit breaker tests use it. @legacy_disk_path = File.expand_path(storage['path'], Rails.root) if storage['path'] diff --git a/lib/gitlab/github_import/parallel_importer.rb b/lib/gitlab/github_import/parallel_importer.rb index 6da11e6ef08..b02b123c98e 100644 --- a/lib/gitlab/github_import/parallel_importer.rb +++ b/lib/gitlab/github_import/parallel_importer.rb @@ -32,7 +32,8 @@ module Gitlab Gitlab::SidekiqStatus .set(jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION) - project.update_column(:import_jid, jid) + project.ensure_import_state + project.import_state&.update_column(:jid, jid) Stage::ImportRepositoryWorker .perform_async(project.id) diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index a7e055ac444..c741dabe168 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -19,6 +19,7 @@ module Gitlab gon.gitlab_logo = ActionController::Base.helpers.asset_path('gitlab_logo.png') gon.sprite_icons = IconsHelper.sprite_icon_path gon.sprite_file_icons = IconsHelper.sprite_file_icons_path + gon.emoji_sprites_css_path = ActionController::Base.helpers.stylesheet_path('emoji_sprites') gon.test_env = Rails.env.test? gon.suggested_label_colors = LabelsHelper.suggested_colors diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 0d1c4f73c6e..21ac7f7e0b6 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -106,6 +106,7 @@ excluded_attributes: - :last_repository_updated_at - :last_repository_check_at - :storage_version + - :remote_mirror_available_overridden - :description_html snippets: - :expired_at diff --git a/lib/gitlab/legacy_github_import/importer.rb b/lib/gitlab/legacy_github_import/importer.rb index 7edd0ad2033..b04d678cf98 100644 --- a/lib/gitlab/legacy_github_import/importer.rb +++ b/lib/gitlab/legacy_github_import/importer.rb @@ -78,7 +78,8 @@ module Gitlab def handle_errors return unless errors.any? - project.update_column(:import_error, { + project.ensure_import_state + project.import_state&.update_column(:last_error, { message: 'The remote data could not be fully imported.', errors: errors }.to_json) diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb index d12ba0ec176..d41a855bff1 100644 --- a/lib/gitlab/metrics/prometheus.rb +++ b/lib/gitlab/metrics/prometheus.rb @@ -25,6 +25,14 @@ module Gitlab end end + def reset_registry! + clear_memoization(:registry) + + REGISTRY_MUTEX.synchronize do + ::Prometheus::Client.reset! + end + end + def registry strong_memoize(:registry) do REGISTRY_MUTEX.synchronize do diff --git a/lib/gitlab/multi_collection_paginator.rb b/lib/gitlab/multi_collection_paginator.rb index 43921a8c1c0..fd5de73c526 100644 --- a/lib/gitlab/multi_collection_paginator.rb +++ b/lib/gitlab/multi_collection_paginator.rb @@ -5,7 +5,7 @@ module Gitlab def initialize(*collections, per_page: nil) raise ArgumentError.new('Only 2 collections are supported') if collections.size != 2 - @per_page = per_page || Kaminari.config.default_per_page + @per_page = (per_page || Kaminari.config.default_per_page).to_i @first_collection, @second_collection = collections end diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb index ae136202f0c..08f6a54776f 100644 --- a/lib/gitlab/project_template.rb +++ b/lib/gitlab/project_template.rb @@ -25,9 +25,9 @@ module Gitlab end TEMPLATES_TABLE = [ - ProjectTemplate.new('rails', 'Ruby on Rails', 'Includes an MVC structure, gemfile, rakefile, and .gitlab-ci.yml file, along with many others, to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/rails'), - ProjectTemplate.new('spring', 'Spring', 'Includes an MVC structure, mvnw, pom.xml, and .gitlab-ci.yml file to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/spring'), - ProjectTemplate.new('express', 'NodeJS Express', 'Includes an MVC structure and .gitlab-ci.yml file to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/express') + ProjectTemplate.new('rails', 'Ruby on Rails', 'Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/rails'), + ProjectTemplate.new('spring', 'Spring', 'Includes an MVC structure, mvnw and pom.xml to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/spring'), + ProjectTemplate.new('express', 'NodeJS Express', 'Includes an MVC structure to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/express') ].freeze class << self diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb index 1fa2a19b0af..4888184403c 100644 --- a/lib/gitlab/repo_path.rb +++ b/lib/gitlab/repo_path.rb @@ -4,7 +4,8 @@ module Gitlab def self.parse(repo_path) wiki = false - project_path = strip_storage_path(repo_path.sub(/\.git\z/, ''), fail_on_not_found: false) + project_path = repo_path.sub(/\.git\z/, '').sub(%r{\A/}, '') + project, was_redirected = find_project(project_path) if project_path.end_with?('.wiki') && project.nil? @@ -17,22 +18,6 @@ module Gitlab [project, wiki, redirected_path] end - def self.strip_storage_path(repo_path, fail_on_not_found: true) - result = repo_path - - storage = Gitlab.config.repositories.storages.values.find do |params| - repo_path.start_with?(params.legacy_disk_path) - end - - if storage - result = result.sub(storage.legacy_disk_path, '') - elsif fail_on_not_found - raise NotFoundError.new("No known storage path matches #{repo_path.inspect}") - end - - result.sub(%r{\A/*}, '') - end - def self.find_project(project_path) project = Project.find_by_full_path(project_path, follow_redirects: true) was_redirected = project && project.full_path.casecmp(project_path) != 0 diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 8c0a4d55ea2..e294f3c4ebc 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -71,6 +71,7 @@ module Gitlab projects_imported_from_github: Project.where(import_type: 'github').count, protected_branches: ProtectedBranch.count, releases: Release.count, + remote_mirrors: RemoteMirror.count, snippets: Snippet.count, todos: Todo.count, uploads: Upload.count, diff --git a/lib/gitlab/middleware/webpack_proxy.rb b/lib/gitlab/webpack/dev_server_middleware.rb index 6aecf63231f..b9a75eaac63 100644 --- a/lib/gitlab/middleware/webpack_proxy.rb +++ b/lib/gitlab/webpack/dev_server_middleware.rb @@ -3,8 +3,8 @@ # :nocov: module Gitlab - module Middleware - class WebpackProxy < Rack::Proxy + module Webpack + class DevServerMiddleware < Rack::Proxy def initialize(app = nil, opts = {}) @proxy_host = opts.fetch(:proxy_host, 'localhost') @proxy_port = opts.fetch(:proxy_port, 3808) diff --git a/lib/gitlab/webpack/manifest.rb b/lib/gitlab/webpack/manifest.rb new file mode 100644 index 00000000000..0c343e5bc1d --- /dev/null +++ b/lib/gitlab/webpack/manifest.rb @@ -0,0 +1,27 @@ +require 'webpack/rails/manifest' + +module Gitlab + module Webpack + class Manifest < ::Webpack::Rails::Manifest + # Raised if a supplied asset does not exist in the webpack manifest + AssetMissingError = Class.new(StandardError) + + class << self + def entrypoint_paths(source) + raise ::Webpack::Rails::Manifest::WebpackError, manifest["errors"] unless manifest_bundled? + + entrypoint = manifest["entrypoints"][source] + if entrypoint && entrypoint["assets"] + # Can be either a string or an array of strings. + # Do not include source maps as they are not javascript + [entrypoint["assets"]].flatten.reject { |p| p =~ /.*\.map$/ }.map do |p| + "/#{::Rails.configuration.webpack.public_path}/#{p}" + end + else + raise AssetMissingError, "Can't find entry point '#{source}' in webpack manifest" + end + end + end + end + end +end diff --git a/lib/tasks/migrate/add_limits_mysql.rake b/lib/tasks/migrate/add_limits_mysql.rake index 151f42a2222..c6204f89de4 100644 --- a/lib/tasks/migrate/add_limits_mysql.rake +++ b/lib/tasks/migrate/add_limits_mysql.rake @@ -1,6 +1,7 @@ require Rails.root.join('db/migrate/limits_to_mysql') require Rails.root.join('db/migrate/markdown_cache_limits_to_mysql') require Rails.root.join('db/migrate/merge_request_diff_file_limits_to_mysql') +require Rails.root.join('db/migrate/limits_ci_build_trace_chunks_raw_data_for_mysql') desc "GitLab | Add limits to strings in mysql database" task add_limits_mysql: :environment do @@ -8,4 +9,5 @@ task add_limits_mysql: :environment do LimitsToMysql.new.up MarkdownCacheLimitsToMysql.new.up MergeRequestDiffFileLimitsToMysql.new.up + LimitsCiBuildTraceChunksRawDataForMysql.new.up end |
