diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-29 15:08:59 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-29 15:08:59 +0000 |
commit | 23288f62da73fb0e30d8e7ce306665e8fda1b932 (patch) | |
tree | 2baf1339e4d7c7c35d6b8a52cfb90597a5d4cdf1 | |
parent | 7cc6872401eb487ed20dbb9d455f8bb9c97d9e39 (diff) | |
download | gitlab-ce-23288f62da73fb0e30d8e7ce306665e8fda1b932.tar.gz |
Add latest changes from gitlab-org/gitlab@master
68 files changed, 1258 insertions, 79 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 6b4de0a42b0..f288d11142d 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1.83.0 +1.85.0 @@ -456,7 +456,7 @@ group :ed25519 do end # Gitaly GRPC protocol definitions -gem 'gitaly', '~> 1.81.0' +gem 'gitaly', '~> 1.85.0' gem 'grpc', '~> 1.24.0' diff --git a/Gemfile.lock b/Gemfile.lock index b5aca8658b3..f7bc7441741 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -360,7 +360,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) git (1.5.0) - gitaly (1.81.0) + gitaly (1.85.0) grpc (~> 1.0) github-markup (1.7.0) gitlab-chronic (0.10.5) @@ -1209,7 +1209,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly (~> 1.81.0) + gitaly (~> 1.85.0) github-markup (~> 1.7.0) gitlab-chronic (~> 0.10.5) gitlab-labkit (= 0.9.1) diff --git a/Guardfile b/Guardfile index 8a43f414ca9..21ee2a9d610 100644 --- a/Guardfile +++ b/Guardfile @@ -2,7 +2,7 @@ # More info at https://github.com/guard/guard#readme -cmd = ENV['SPRING'] ? 'spring rspec' : 'bundle exec rspec' +cmd = ENV['GUARD_CMD'] || (ENV['SPRING'] ? 'spring rspec' : 'bundle exec rspec') guard :rspec, cmd: cmd do require "guard/rspec/dsl" diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index 704515cf70c..fe2ad562ad5 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -129,9 +129,6 @@ export default { crossplaneInstalled() { return this.applications.crossplane.status === APPLICATION_STATUS.INSTALLED; }, - enableClusterApplicationElasticStack() { - return gon.features && gon.features.enableClusterApplicationElasticStack; - }, ingressModSecurityDescription() { const escapedUrl = _.escape(this.ingressModSecurityHelpPath); @@ -655,7 +652,6 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity </div> </application-row> <application-row - v-if="enableClusterApplicationElasticStack" id="elastic_stack" :logo-url="elasticStackLogo" :title="applications.elastic_stack.title" diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue index f306910df05..9d6eda55c1e 100644 --- a/app/assets/javascripts/repository/components/breadcrumbs.vue +++ b/app/assets/javascripts/repository/components/breadcrumbs.vue @@ -107,10 +107,16 @@ export default { return acc.concat({ name, path, - to: `/-/tree/${this.ref}${path}`, + to: `/-/tree/${escape(this.ref)}${path}`, }); }, - [{ name: this.projectShortPath, path: '/', to: `/-/tree/${this.ref}/` }], + [ + { + name: this.projectShortPath, + path: '/', + to: `/-/tree/${escape(this.ref)}/`, + }, + ], ); }, canCreateMrFromFork() { diff --git a/app/assets/javascripts/repository/components/table/parent_row.vue b/app/assets/javascripts/repository/components/table/parent_row.vue index c919f2d42cb..096c25a693f 100644 --- a/app/assets/javascripts/repository/components/table/parent_row.vue +++ b/app/assets/javascripts/repository/components/table/parent_row.vue @@ -28,7 +28,7 @@ export default { return splitArray.join('/'); }, parentRoute() { - return { path: `/-/tree/${this.commitRef}/${this.parentPath}` }; + return { path: `/-/tree/${escape(this.commitRef)}/${this.parentPath}` }; }, }, methods: { diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue index a0a9a5657a8..e7bbca957c1 100644 --- a/app/assets/javascripts/repository/components/table/row.vue +++ b/app/assets/javascripts/repository/components/table/row.vue @@ -90,7 +90,7 @@ export default { }, computed: { routerLinkTo() { - return this.isFolder ? { path: `/-/tree/${this.ref}/${this.path}` } : null; + return this.isFolder ? { path: `/-/tree/${escape(this.ref)}/${this.path}` } : null; }, iconName() { return `fa-${getIconName(this.type, this.path)}`; diff --git a/app/assets/javascripts/repository/log_tree.js b/app/assets/javascripts/repository/log_tree.js index 2a66659fecb..aefa4963d5f 100644 --- a/app/assets/javascripts/repository/log_tree.js +++ b/app/assets/javascripts/repository/log_tree.js @@ -27,7 +27,10 @@ export function fetchLogsTree(client, path, offset, resolver = null) { fetchpromise = axios .get( - `${gon.relative_url_root}/${projectPath}/-/refs/${ref}/logs_tree/${path.replace(/^\//, '')}`, + `${gon.relative_url_root}/${projectPath}/-/refs/${escape(ref)}/logs_tree/${path.replace( + /^\//, + '', + )}`, { params: { format: 'json', offset }, }, diff --git a/app/assets/javascripts/repository/router.js b/app/assets/javascripts/repository/router.js index fa544444be8..6c31c9bae82 100644 --- a/app/assets/javascripts/repository/router.js +++ b/app/assets/javascripts/repository/router.js @@ -12,7 +12,7 @@ export default function createRouter(base, baseRef) { base: joinPaths(gon.relative_url_root || '', base), routes: [ { - path: `/-/tree/${baseRef}(/.*)?`, + path: `/-/tree/${escape(baseRef)}(/.*)?`, name: 'treePath', component: TreePage, props: route => ({ diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 52a5f801bad..2c9ee69c8c4 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -12,9 +12,6 @@ class Clusters::ClustersController < Clusters::BaseController before_action :authorize_update_cluster!, only: [:update] before_action :authorize_admin_cluster!, only: [:destroy, :clear_cache] before_action :update_applications_status, only: [:cluster_status] - before_action only: [:show] do - push_frontend_feature_flag(:enable_cluster_application_elastic_stack) - end helper_method :token_in_session diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb index c0c8474232a..72e76f56540 100644 --- a/app/controllers/projects/merge_requests/diffs_controller.rb +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -64,6 +64,10 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic options = additional_attributes.merge(diff_view: diff_view) + if @merge_request.project.context_commits_enabled? + options[:context_commits] = @merge_request.context_commits + end + render json: DiffsSerializer.new(request).represent(diffs, options) end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 7cdda071813..e21930c9cfe 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -116,6 +116,15 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo } end + def context_commits + return render_404 unless project.context_commits_enabled? + + # Get commits from repository + # or from cache if already merged + commits = ContextCommitsFinder.new(project, @merge_request, { search: params[:search], limit: params[:limit], offset: params[:offset] }).execute + render json: CommitEntity.represent(commits, { type: :full, request: merge_request }) + end + def test_reports reports_response(@merge_request.compare_test_reports) end diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 2ed29b937ad..19a8df3e9a5 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -81,7 +81,7 @@ class Projects::RepositoriesController < Projects::ApplicationController def assign_archive_vars if params[:id] - @ref, @filename = extract_ref(params[:id]) + @ref, @filename = extract_ref_and_filename(params[:id]) else @ref = params[:ref] @filename = nil @@ -89,6 +89,26 @@ class Projects::RepositoriesController < Projects::ApplicationController rescue InvalidPathError render_404 end + + # path can be of the form: + # master + # master/first.zip + # master/first/second.tar.gz + # master/first/second/third.zip + # + # In the archive case, we know that the last value is always the filename, so we + # do a greedy match to extract the ref. This avoid having to pull all ref names + # from Redis. + def extract_ref_and_filename(id) + path = id.strip + data = path.match(/(.*)\/(.*)/) + + if data + [data[1], data[2]] + else + [path, nil] + end + end end Projects::RepositoriesController.prepend_if_ee('EE::Projects::RepositoriesController') diff --git a/app/finders/context_commits_finder.rb b/app/finders/context_commits_finder.rb new file mode 100644 index 00000000000..f1b3eb43e84 --- /dev/null +++ b/app/finders/context_commits_finder.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +class ContextCommitsFinder + def initialize(project, merge_request, params = {}) + @project = project + @merge_request = merge_request + @search = params[:search] + @limit = (params[:limit] || 40).to_i + @offset = (params[:offset] || 0).to_i + end + + def execute + commits = init_collection + commits = filter_existing_commits(commits) + + commits + end + + private + + attr_reader :project, :merge_request, :search, :limit, :offset + + def init_collection + commits = + if search.present? + search_commits + else + project.repository.commits(merge_request.source_branch, { limit: limit, offset: offset }) + end + + commits + end + + def filter_existing_commits(commits) + commits.select! { |commit| already_included_ids.exclude?(commit.id) } + + commits + end + + def search_commits + key = search.strip + commits = [] + if Commit.valid_hash?(key) + mr_existing_commits_ids = merge_request.commits.map(&:id) + if mr_existing_commits_ids.exclude? key + commit_by_sha = project.repository.commit(key) + commits = [commit_by_sha] if commit_by_sha + end + else + commits = project.repository.find_commits_by_message(search, nil, nil, 20) + end + + commits + end + + def already_included_ids + mr_existing_commits_ids = merge_request.commits.map(&:id) + mr_context_commits_ids = merge_request.context_commits.map(&:id) + + mr_existing_commits_ids + mr_context_commits_ids + end +end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 620a63fdc46..4c3c4931387 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -29,6 +29,8 @@ module DiffHelper if action_name == 'diff_for_path' options[:expanded] = true options[:paths] = params.values_at(:old_path, :new_path) + elsif action_name == 'show' + options[:include_context_commits] = true unless @project.context_commits_enabled? end options diff --git a/app/models/concerns/cached_commit.rb b/app/models/concerns/cached_commit.rb new file mode 100644 index 00000000000..183d5728743 --- /dev/null +++ b/app/models/concerns/cached_commit.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module CachedCommit + extend ActiveSupport::Concern + + def to_hash + Gitlab::Git::Commit::SERIALIZE_KEYS.each_with_object({}) do |key, hash| + hash[key] = public_send(key) # rubocop:disable GitlabSecurity/PublicSend + end + end + + # We don't save these, because they would need a table or a serialised + # field. They aren't used anywhere, so just pretend the commit has no parents. + def parent_ids + [] + end +end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 48c5c0152b5..3174a3269b4 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -34,6 +34,8 @@ class MergeRequest < ApplicationRecord has_internal_id :iid, scope: :target_project, track_if: -> { !importing? }, init: ->(s) { s&.target_project&.merge_requests&.maximum(:iid) } has_many :merge_request_diffs + has_many :merge_request_context_commits + has_many :merge_request_context_commit_diff_files, through: :merge_request_context_commits, source: :diff_files has_many :merge_request_milestones has_many :milestones, through: :merge_request_milestones @@ -399,6 +401,10 @@ class MergeRequest < ApplicationRecord "#{project.to_reference_base(from, full: full)}#{reference}" end + def context_commits + @context_commits ||= merge_request_context_commits.map(&:to_commit) + end + def commits(limit: nil) return merge_request_diff.commits(limit: limit) if persisted? diff --git a/app/models/merge_request_context_commit.rb b/app/models/merge_request_context_commit.rb new file mode 100644 index 00000000000..eecb10e6dbc --- /dev/null +++ b/app/models/merge_request_context_commit.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +class MergeRequestContextCommit < ApplicationRecord + include CachedCommit + include ShaAttribute + + belongs_to :merge_request + has_many :diff_files, class_name: 'MergeRequestContextCommitDiffFile' + + sha_attribute :sha + + validates :sha, presence: true + validates :sha, uniqueness: { message: 'has already been added' } + + # delete all MergeRequestContextCommit & MergeRequestContextCommitDiffFile for given merge_request & commit SHAs + def self.delete_bulk(merge_request, commits) + commit_ids = commits.map(&:sha) + merge_request.merge_request_context_commits.where(sha: commit_ids).delete_all + end + + # create MergeRequestContextCommit by given commit sha and it's diff file record + def self.bulk_insert(*args) + Gitlab::Database.bulk_insert('merge_request_context_commits', *args) + end + + def to_commit + # Here we are storing the commit sha because to_hash removes the sha parameter and we lose + # the reference, this happens because we are storing the ID in db and the Commit class replaces + # id with sha and removes it, so in our case it will be some incremented integer which is not + # what we want + commit_hash = attributes.except('id').to_hash + commit_hash['id'] = sha + Commit.from_hash(commit_hash, merge_request.target_project) + end +end diff --git a/app/models/merge_request_context_commit_diff_file.rb b/app/models/merge_request_context_commit_diff_file.rb new file mode 100644 index 00000000000..9dce7c53ab6 --- /dev/null +++ b/app/models/merge_request_context_commit_diff_file.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class MergeRequestContextCommitDiffFile < ApplicationRecord + include Gitlab::EncodingHelper + include ShaAttribute + include DiffFile + + belongs_to :merge_request_context_commit, inverse_of: :diff_files + + sha_attribute :sha + alias_attribute :id, :sha + + # create MergeRequestContextCommitDiffFile by given diff file record(s) + def self.bulk_insert(*args) + Gitlab::Database.bulk_insert('merge_request_context_commit_diff_files', *args) + end +end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index fa633a1a725..ffe95e8f034 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -560,6 +560,10 @@ class MergeRequestDiff < ApplicationRecord opening_external_diff do collection = merge_request_diff_files + if options[:include_context_commits] + collection += merge_request.merge_request_context_commit_diff_files + end + if paths = options[:paths] collection = collection.where('old_path IN (?) OR new_path IN (?)', paths, paths) end diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb index b897bbc8cf5..aa41a68f184 100644 --- a/app/models/merge_request_diff_commit.rb +++ b/app/models/merge_request_diff_commit.rb @@ -2,6 +2,7 @@ class MergeRequestDiffCommit < ApplicationRecord include ShaAttribute + include CachedCommit belongs_to :merge_request_diff @@ -9,8 +10,6 @@ class MergeRequestDiffCommit < ApplicationRecord alias_attribute :id, :sha def self.create_bulk(merge_request_diff_id, commits) - sha_attribute = Gitlab::Database::ShaAttribute.new - rows = commits.map.with_index do |commit, index| # See #parent_ids. commit_hash = commit.to_hash.except(:parent_ids) @@ -19,7 +18,7 @@ class MergeRequestDiffCommit < ApplicationRecord commit_hash.merge( merge_request_diff_id: merge_request_diff_id, relative_order: index, - sha: sha_attribute.serialize(sha), # rubocop:disable Cop/ActiveRecordSerialize + sha: Gitlab::Database::ShaAttribute.serialize(sha), # rubocop:disable Cop/ActiveRecordSerialize authored_date: Gitlab::Database.sanitize_timestamp(commit_hash[:authored_date]), committed_date: Gitlab::Database.sanitize_timestamp(commit_hash[:committed_date]) ) @@ -27,16 +26,4 @@ class MergeRequestDiffCommit < ApplicationRecord Gitlab::Database.bulk_insert(self.table_name, rows) end - - def to_hash - Gitlab::Git::Commit::SERIALIZE_KEYS.each_with_object({}) do |key, hash| - hash[key] = public_send(key) # rubocop:disable GitlabSecurity/PublicSend - end - end - - # We don't save these, because they would need a table or a serialised - # field. They aren't used anywhere, so just pretend the commit has no parents. - def parent_ids - [] - end end diff --git a/app/models/project.rb b/app/models/project.rb index 3aa8430f3a2..8cb35904d92 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -763,6 +763,10 @@ class Project < ApplicationRecord Feature.enabled?(:unlink_fork_network_upon_visibility_decrease, self, default_enabled: true) end + def context_commits_enabled? + Feature.enabled?(:context_commits, default_enabled: true) + end + def empty_repo? repository.empty? end diff --git a/app/serializers/diffs_entity.rb b/app/serializers/diffs_entity.rb index 88e09ae8c0b..02f78180fb0 100644 --- a/app/serializers/diffs_entity.rb +++ b/app/serializers/diffs_entity.rb @@ -24,6 +24,10 @@ class DiffsEntity < Grape::Entity ) end + expose :context_commits, using: API::Entities::Commit, if: -> (diffs, options) { merge_request&.project&.context_commits_enabled? } do |diffs| + options[:context_commits] + end + expose :merge_request_diff, using: MergeRequestDiffEntity do |diffs| options[:merge_request_diff] end diff --git a/app/services/clusters/applications/base_service.rb b/app/services/clusters/applications/base_service.rb index 844da11e5cb..2585d815e07 100644 --- a/app/services/clusters/applications/base_service.rb +++ b/app/services/clusters/applications/base_service.rb @@ -58,7 +58,7 @@ module Clusters end def instantiate_application - raise_invalid_application_error if invalid_application? + raise_invalid_application_error if unknown_application? builder || raise(InvalidApplicationError, "invalid application: #{application_name}") end @@ -67,10 +67,6 @@ module Clusters raise(InvalidApplicationError, "invalid application: #{application_name}") end - def invalid_application? - unknown_application? || (application_name == Applications::ElasticStack.application_name && !Feature.enabled?(:enable_cluster_application_elastic_stack)) - end - def unknown_application? Clusters::Cluster::APPLICATIONS.keys.exclude?(application_name) end diff --git a/app/services/merge_requests/add_context_service.rb b/app/services/merge_requests/add_context_service.rb new file mode 100644 index 00000000000..bb82fa23468 --- /dev/null +++ b/app/services/merge_requests/add_context_service.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +module MergeRequests + class AddContextService < MergeRequests::BaseService + def execute + return error("You are not allowed to access the requested resource", 403) unless current_user&.can?(:update_merge_request, merge_request) + return error("Context commits: #{duplicates} are already created", 400) unless duplicates.empty? + return error("One or more context commits' sha is not valid.", 400) if commits.size != commit_ids.size + + context_commit_ids = [] + MergeRequestContextCommit.transaction do + context_commit_ids = MergeRequestContextCommit.bulk_insert(context_commit_rows, return_ids: true) + MergeRequestContextCommitDiffFile.bulk_insert(diff_rows(context_commit_ids)) + end + + commits + end + + private + + def raw_repository + project.repository.raw_repository + end + + def merge_request + params[:merge_request] + end + + def commit_ids + params[:commits] + end + + def commits + project.repository.commits_by(oids: commit_ids) + end + + def context_commit_rows + @context_commit_rows ||= build_context_commit_rows(merge_request.id, commits) + end + + def diff_rows(context_commit_ids) + @diff_rows ||= build_diff_rows(raw_repository, commits, context_commit_ids) + end + + def encode_in_base64?(diff_text) + (diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only?) || + diff_text.include?("\0") + end + + def duplicates + existing_oids = merge_request.merge_request_context_commits.map { |commit| commit.sha.to_s } + duplicate_oids = existing_oids.select do |existing_oid| + commit_ids.select { |commit_id| existing_oid.start_with?(commit_id) }.count > 0 + end + + duplicate_oids + end + + def build_context_commit_rows(merge_request_id, commits) + commits.map.with_index do |commit, index| + # generate context commit information for given commit + commit_hash = commit.to_hash.except(:parent_ids) + sha = Gitlab::Database::ShaAttribute.serialize(commit_hash.delete(:id)) + commit_hash.merge( + merge_request_id: merge_request_id, + relative_order: index, + sha: sha, + authored_date: Gitlab::Database.sanitize_timestamp(commit_hash[:authored_date]), + committed_date: Gitlab::Database.sanitize_timestamp(commit_hash[:committed_date]) + ) + end + end + + def build_diff_rows(raw_repository, commits, context_commit_ids) + diff_rows = [] + diff_order = 0 + + commits.flat_map.with_index do |commit, index| + commit_hash = commit.to_hash.except(:parent_ids) + sha = Gitlab::Database::ShaAttribute.serialize(commit_hash.delete(:id)) + # generate context commit diff information for given commit + diffs = commit.diffs + + compare = Gitlab::Git::Compare.new( + raw_repository, + diffs.diff_refs.start_sha, + diffs.diff_refs.head_sha + ) + compare.diffs.map do |diff| + diff_hash = diff.to_hash.merge( + sha: sha, + binary: false, + merge_request_context_commit_id: context_commit_ids[index], + relative_order: diff_order + ) + + # Compatibility with old diffs created with Psych. + diff_hash.tap do |hash| + diff_text = hash[:diff] + + if encode_in_base64?(diff_text) + hash[:binary] = true + hash[:diff] = [diff_text].pack('m0') + end + end + + # Increase order for commit so when present the diffs we can use it to keep order + diff_order += 1 + diff_rows << diff_hash + end + end + + diff_rows + end + end +end diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 3096b2e43fe..e8dd467c7ff 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -209,7 +209,7 @@ %span = _('Artifacts') - - if !should_display_analytics_pages_in_sidebar && project_nav_tab?(:pipelines) + - if project_nav_tab?(:pipelines) = nav_link(controller: :pipeline_schedules) do = link_to pipeline_schedules_path(@project), title: _('Schedules'), class: 'shortcuts-builds' do %span diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index f4560404c03..18bdbd42d0d 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -46,7 +46,7 @@ - if job.try(:trigger_request) %span.badge.badge-info= _('triggered') - if job.try(:allow_failure) - %span.badge.badge-danger= _('allowed to fail') + %span.badge.badge-warning= _('allowed to fail') - if job.schedulable? %span.badge.badge-info= s_('DelayedJobs|delayed') - elsif job.action? diff --git a/changelogs/unreleased/ak-remove-es-ff.yml b/changelogs/unreleased/ak-remove-es-ff.yml new file mode 100644 index 00000000000..baf95b15d15 --- /dev/null +++ b/changelogs/unreleased/ak-remove-es-ff.yml @@ -0,0 +1,5 @@ +--- +title: Add new Elastic Stack cluster application for pod log aggregation +merge_request: 23058 +author: +type: added diff --git a/changelogs/unreleased/allowed-to-fail-warning-color.yml b/changelogs/unreleased/allowed-to-fail-warning-color.yml new file mode 100644 index 00000000000..5b11ae08477 --- /dev/null +++ b/changelogs/unreleased/allowed-to-fail-warning-color.yml @@ -0,0 +1,5 @@ +--- +title: Changed color of allowed to fail badge from danger to warning +merge_request: !23437 +author: +type: changed diff --git a/changelogs/unreleased/gitaly-version-v1.85.0.yml b/changelogs/unreleased/gitaly-version-v1.85.0.yml new file mode 100644 index 00000000000..26895530db9 --- /dev/null +++ b/changelogs/unreleased/gitaly-version-v1.85.0.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade to Gitaly v1.85.0 +merge_request: 23945 +author: +type: changed diff --git a/changelogs/unreleased/sh-optimize-archive-controller-ref-names.yml b/changelogs/unreleased/sh-optimize-archive-controller-ref-names.yml new file mode 100644 index 00000000000..781c002ed65 --- /dev/null +++ b/changelogs/unreleased/sh-optimize-archive-controller-ref-names.yml @@ -0,0 +1,5 @@ +--- +title: Optimize ref name lookups in archive downloads +merge_request: 23890 +author: +type: performance diff --git a/config/initializers/attr_encrypted_thread_safe.rb b/config/initializers/attr_encrypted_thread_safe.rb new file mode 100644 index 00000000000..be0bb56ffdc --- /dev/null +++ b/config/initializers/attr_encrypted_thread_safe.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# As of v3.1.0, attr_encrypted is not thread-safe because all instances share the same `encrypted_attributes` +# This was fixed in https://github.com/attr-encrypted/attr_encrypted/commit/d4ca0e2073ca6ba5035997ce25f7fc0b4bfbe39e +# but no release was made after that so we have to patch it ourselves here + +module AttrEncrypted + module InstanceMethods + def encrypted_attributes + @encrypted_attributes ||= begin + duplicated = {} + self.class.encrypted_attributes.map { |key, value| duplicated[key] = value.dup } + duplicated + end + end + end +end diff --git a/config/routes/merge_requests.rb b/config/routes/merge_requests.rb index fd80c21deb1..f9670a5bf6e 100644 --- a/config/routes/merge_requests.rb +++ b/config/routes/merge_requests.rb @@ -18,6 +18,7 @@ resources :merge_requests, concerns: :awardable, except: [:new, :create, :show], scope constraints: ->(req) { req.format == :json }, as: :json do get :commits get :pipelines + get :context_commits get :diffs, to: 'merge_requests/diffs#show' get :diffs_batch, to: 'merge_requests/diffs#diffs_batch' get :diffs_metadata, to: 'merge_requests/diffs#diffs_metadata' diff --git a/db/migrate/20190920224341_create_merge_request_context_commits_and_diffs.rb b/db/migrate/20190920224341_create_merge_request_context_commits_and_diffs.rb new file mode 100644 index 00000000000..dcc9da670f7 --- /dev/null +++ b/db/migrate/20190920224341_create_merge_request_context_commits_and_diffs.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CreateMergeRequestContextCommitsAndDiffs < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + create_table :merge_request_context_commits do |t| + t.references :merge_request, foreign_key: { on_delete: :cascade } + t.datetime_with_timezone :authored_date + t.datetime_with_timezone :committed_date + t.binary :sha, null: false + t.integer :relative_order, null: false + t.text :author_name + t.text :author_email + t.text :committer_name + t.text :committer_email + t.text :message + t.index [:merge_request_id, :sha], unique: true, name: 'index_mr_context_commits_on_merge_request_id_and_sha' + end + + create_table :merge_request_context_commit_diff_files, id: false do |t| + t.references :merge_request_context_commit, foreign_key: { on_delete: :cascade }, index: { name: "idx_mr_cc_diff_files_on_mr_cc_id" } + t.binary :sha, null: false + t.integer :relative_order, null: false + t.string :a_mode, null: false, limit: 255 + t.string :b_mode, null: false, limit: 255 + t.boolean :new_file, null: false + t.boolean :renamed_file, null: false + t.boolean :deleted_file, null: false + t.boolean :too_large, null: false + t.boolean :binary + t.text :new_path, null: false + t.text :old_path, null: false + t.text :diff + t.index [:merge_request_context_commit_id, :sha], name: 'idx_mr_cc_diff_files_on_mr_cc_id_and_sha' + end + end +end diff --git a/db/schema.rb b/db/schema.rb index f48ead215bc..88c456322dd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2433,6 +2433,37 @@ ActiveRecord::Schema.define(version: 2020_01_27_090233) do t.index ["blocking_merge_request_id", "blocked_merge_request_id"], name: "index_mr_blocks_on_blocking_and_blocked_mr_ids", unique: true end + create_table "merge_request_context_commit_diff_files", id: false, force: :cascade do |t| + t.binary "sha", null: false + t.integer "relative_order", null: false + t.boolean "new_file", null: false + t.boolean "renamed_file", null: false + t.boolean "deleted_file", null: false + t.boolean "too_large", null: false + t.string "a_mode", limit: 255, null: false + t.string "b_mode", limit: 255, null: false + t.text "new_path", null: false + t.text "old_path", null: false + t.text "diff" + t.boolean "binary" + t.bigint "merge_request_context_commit_id" + t.index ["merge_request_context_commit_id", "sha"], name: "idx_mr_cc_diff_files_on_mr_cc_id_and_sha" + end + + create_table "merge_request_context_commits", force: :cascade do |t| + t.datetime_with_timezone "authored_date" + t.datetime_with_timezone "committed_date" + t.integer "relative_order", null: false + t.binary "sha", null: false + t.text "author_name" + t.text "author_email" + t.text "committer_name" + t.text "committer_email" + t.text "message" + t.bigint "merge_request_id" + t.index ["merge_request_id", "sha"], name: "index_mr_context_commits_on_merge_request_id_and_sha", unique: true + end + create_table "merge_request_diff_commits", id: false, force: :cascade do |t| t.datetime "authored_date" t.datetime "committed_date" @@ -4702,6 +4733,8 @@ ActiveRecord::Schema.define(version: 2020_01_27_090233) do add_foreign_key "merge_request_assignees", "users", on_delete: :cascade add_foreign_key "merge_request_blocks", "merge_requests", column: "blocked_merge_request_id", on_delete: :cascade add_foreign_key "merge_request_blocks", "merge_requests", column: "blocking_merge_request_id", on_delete: :cascade + add_foreign_key "merge_request_context_commit_diff_files", "merge_request_context_commits", on_delete: :cascade + add_foreign_key "merge_request_context_commits", "merge_requests", on_delete: :cascade add_foreign_key "merge_request_diff_commits", "merge_request_diffs", on_delete: :cascade add_foreign_key "merge_request_diff_files", "merge_request_diffs", on_delete: :cascade add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md index 73a020b87d0..650e797324a 100644 --- a/doc/administration/gitaly/praefect.md +++ b/doc/administration/gitaly/praefect.md @@ -166,16 +166,19 @@ praefect['auth_token'] = 'PRAEFECT_EXTERNAL_TOKEN' praefect['virtual_storages'] = { 'praefect' => { 'gitaly-1' => { - 'address' => 'tcp://gitaly-1.internal:8075', + # Replace GITALY_URL_OR_IP below with the real address to connect to. + 'address' => 'tcp://GITALY_URL_OR_IP:8075', 'token' => 'PRAEFECT_INTERNAL_TOKEN', 'primary' => true }, 'gitaly-2' => { - 'address' => 'tcp://gitaly-2.internal:8075', + # Replace GITALY_URL_OR_IP below with the real address to connect to. + 'address' => 'tcp://GITALY_URL_OR_IP:8075', 'token' => 'PRAEFECT_INTERNAL_TOKEN' }, 'gitaly-3' => { - 'address' => 'tcp://gitaly-3.internal:8075', + # Replace GITALY_URL_OR_IP below with the real address to connect to. + 'address' => 'tcp://GITALY_URL_OR_IP:8075', 'token' => 'PRAEFECT_INTERNAL_TOKEN' } } @@ -265,6 +268,8 @@ gitaly['auth_token'] = 'PRAEFECT_INTERNAL_TOKEN' gitaly['listen_addr'] = "0.0.0.0:8075" git_data_dirs({ + # Update this to the name of this Gitaly server which will be later + # exposed in the UI under "Admin area > Gitaly" "gitaly-1" => { "path" => "/var/opt/gitlab/git-data" } @@ -301,13 +306,14 @@ is present, there should be two storages available to GitLab: ```ruby # /etc/gitlab/gitlab.rb on gitlab server +# Replace PRAEFECT_URL_OR_IP below with real address Praefect can be accessed at. # Replace PRAEFECT_EXTERNAL_TOKEN below with real secret. git_data_dirs({ "default" => { "path" => "/var/opt/gitlab/git-data" }, "praefect" => { - "gitaly_address" => "tcp://praefect.internal:2305", + "gitaly_address" => "tcp://PRAEFECT_URL_OR_IP:2305", "gitaly_token" => 'PRAEFECT_EXTERNAL_TOKEN' } }) @@ -324,7 +330,7 @@ on the Praefect node. Save your changes and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure). -Run `gitlab-rake gitlab:gitaly:check` to confirm that GitLab can reach Praefect. +Run `sudo gitlab-rake gitlab:gitaly:check` to confirm that GitLab can reach Praefect. ### Testing Praefect diff --git a/doc/administration/packages/dependency_proxy.md b/doc/administration/packages/dependency_proxy.md index 519ec02bd33..4cbb22668d9 100644 --- a/doc/administration/packages/dependency_proxy.md +++ b/doc/administration/packages/dependency_proxy.md @@ -1,4 +1,4 @@ -# GitLab Dependency Proxy administration **(PREMIUM ONLY)** +# GitLab Dependency Proxy administration **(ULTIMATE ONLY)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.11. diff --git a/doc/api/merge_request_context_commits.md b/doc/api/merge_request_context_commits.md new file mode 100644 index 00000000000..4b8edf657e4 --- /dev/null +++ b/doc/api/merge_request_context_commits.md @@ -0,0 +1,97 @@ +# Merge request context commits API + +## List MR context commits + +Get a list of merge request context commits. + +``` +GET /projects/:id/merge_requests/:merge_request_iid/context_commits +``` + +Parameters: + +- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user +- `merge_request_iid` (required) - The internal ID of the merge request + +```json +[ + { + "id": "4a24d82dbca5c11c61556f3b35ca472b7463187e", + "short_id": "4a24d82d", + "created_at": "2017-04-11T10:08:59.000Z", + "parent_ids": null, + "title": "Update README.md to include `Usage in testing and development`", + "message": "Update README.md to include `Usage in testing and development`", + "author_name": "Luke \"Jared\" Bennett", + "author_email": "lbennett@gitlab.com", + "authored_date": "2017-04-11T10:08:59.000Z", + "committer_name": "Luke \"Jared\" Bennett", + "committer_email": "lbennett@gitlab.com", + "committed_date": "2017-04-11T10:08:59.000Z", + "author": null, + "author_gravatar_url": "https://www.gravatar.com/avatar/2acf1fb99417a2b3971def5a294abbeb?s=80&d=identicon", + "commit_url": "http://127.0.0.1:3000/gitlab-org/gitlab-test/commit/4a24d82dbca5c11c61556f3b35ca472b7463187e", + "commit_path": "/gitlab-org/gitlab-test/commit/4a24d82dbca5c11c61556f3b35ca472b7463187e", + "description_html": "", + "title_html": "Update README.md to include `Usage in testing and development`", + "signature_html": null, + "pipeline_status_path": null + } +] +``` + +## Create MR context commits + +Create a list of merge request context commits. + +``` +POST /projects/:id/merge_requests/:merge_request_iid/context_commits +``` + +Parameters: + +- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user +- `merge_request_iid` (required) - The internal ID of the merge request + +``` +POST /projects/:id/merge_requests/ +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `commits` | string array | yes | The context commits' sha | + +```json +[ + { + "id": "6d394385cf567f80a8fd85055db1ab4c5295806f", + "message": "Added contributing guide\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n", + "parent_ids": [ + "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863" + ], + "authored_date": "2014-02-27T10:05:10.000+02:00", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "committed_date": "2014-02-27T10:05:10.000+02:00", + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dmitriy.zaporozhets@gmail.com" + } +] +``` + +## Delete MR context commits + +Delete a list of merge request context commits. + +``` +DELETE /projects/:id/merge_requests/:merge_request_iid/context_commits +``` + +Parameters: + +- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user +- `merge_request_iid` (required) - The internal ID of the merge request + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `commits` | string array | yes | The context commits' sha | diff --git a/doc/development/shell_scripting_guide/index.md b/doc/development/shell_scripting_guide/index.md index e0895a088ab..a501e3def10 100644 --- a/doc/development/shell_scripting_guide/index.md +++ b/doc/development/shell_scripting_guide/index.md @@ -99,7 +99,7 @@ NOTE: **Note:** This is a work in progress. It is an [ongoing effort](https://gitlab.com/gitlab-org/gitlab-foss/issues/64016) to evaluate different tools for the -automated testing of shell scripts (like [BATS](https://github.com/sstephenson/bats)). +automated testing of shell scripts (like [BATS](https://github.com/bats-core/bats-core)). ## Code Review diff --git a/doc/update/README.md b/doc/update/README.md index 510a8fb31fb..255abc7b523 100644 --- a/doc/update/README.md +++ b/doc/update/README.md @@ -131,7 +131,7 @@ sudo -u git -H bundle exec rails runner -e production 'puts Sidekiq::Queue.new(" Major versions are reserved for backwards incompatible changes. We recommend that you first upgrade to the latest available minor version within your major version. Please follow the [Upgrade Recommendations](../policy/maintenance.md#upgrade-recommendations) -to identify the ideal upgrade path. +to identify a supported upgrade path. Before upgrading to a new major version, you should ensure that any background migration jobs from previous releases have been completed. To see the current size @@ -183,11 +183,18 @@ users first upgrade to the latest 11.11 patch release. Once upgraded to 11.11.x, users can upgrade to 12.0.x. Failure to do so may result in database migrations not being applied, which could lead to application errors. -Example 1: you are currently using GitLab 11.11.3, which is the latest patch +It is also required that you upgrade to 12.0.x before moving to a later version +of 12.x. + +Example 1: you are currently using GitLab 11.11.8, which is the latest patch release for 11.11.x. You can upgrade as usual to 12.0.x. Example 2: you are currently using a version of GitLab 10.x. To upgrade, first -upgrade to 11.11.3. Once upgraded to 11.11.3 you can safely upgrade to 12.0.x. +upgrade to the last 10.x release (10.8.7) then the last 11.x release (11.11.8). +Once upgraded to 11.11.8 you can safely upgrade to 12.0.x. + +See our [documentation on upgrade paths](../policy/maintenance.md#upgrade-recommendations) +for more information. ## Miscellaneous diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md index fba74590fac..c502cd607d7 100644 --- a/doc/user/clusters/applications.md +++ b/doc/user/clusters/applications.md @@ -435,18 +435,7 @@ and you will have access to more advanced querying capabilities. Log data is automatically deleted after 15 days using [Curator](https://www.elastic.co/guide/en/elasticsearch/client/curator/5.5/about.html). -This is a preliminary release of Elastic Stack as a GitLab-managed application. By default, -the ability to install it is disabled. - -To allow installation of Elastic Stack as a GitLab-managed application, ask a GitLab -administrator to run following command within a Rails console: - -```ruby -Feature.enable(:enable_cluster_application_elastic_stack) -``` - -Once the feature flag is set, to enable log shipping, install Elastic Stack into the cluster with the -**Install** button. +To enable log shipping, install Elastic Stack into the cluster with the **Install** button. NOTE: **Note:** The [`stable/elastic-stack`](https://github.com/helm/charts/tree/master/stable/elastic-stack) diff --git a/doc/user/packages/dependency_proxy/index.md b/doc/user/packages/dependency_proxy/index.md index 05934212a12..1cebd734a99 100644 --- a/doc/user/packages/dependency_proxy/index.md +++ b/doc/user/packages/dependency_proxy/index.md @@ -1,6 +1,6 @@ -# Dependency Proxy **(PREMIUM ONLY)** +# Dependency Proxy **(ULTIMATE ONLY)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.11. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/7934) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.11. NOTE: **Note:** This is the user guide. In order to use the dependency proxy, an administrator diff --git a/lib/api/entities.rb b/lib/api/entities.rb index ae23d3c9347..95d57b49453 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -755,6 +755,12 @@ module API end end + class MergeRequestContextCommit < Grape::Entity + expose :sha, :relative_order, :new_file, :renamed_file, + :deleted_file, :too_large, :a_mode, :b_mode, :new_path, :old_path, + :diff, :binary + end + class SSHKey < Grape::Entity expose :id, :title, :key, :created_at end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index bd857278ee5..6397a376af7 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -4,6 +4,8 @@ module API class MergeRequests < Grape::API include PaginationParams + CONTEXT_COMMITS_POST_LIMIT = 20 + before { authenticate_non_get! } helpers ::Gitlab::IssuableMetadata @@ -290,6 +292,72 @@ module API present commits, with: Entities::Commit end + desc 'Get the context commits of a merge request' do + success Entities::CommitEntity + end + get ':id/merge_requests/:merge_request_iid/context_commits' do + merge_request = find_merge_request_with_access(params[:merge_request_iid]) + project = merge_request.project + + not_found! unless project.context_commits_enabled? + + context_commits = merge_request.context_commits + CommitEntity.represent(context_commits, type: :full, request: merge_request) + end + + params do + requires :commits, type: Array, allow_blank: false, desc: 'List of context commits sha' + end + desc 'create context commits of merge request' do + success Entities::Commit + end + post ':id/merge_requests/:merge_request_iid/context_commits' do + commit_ids = params[:commits] + + if commit_ids.size > CONTEXT_COMMITS_POST_LIMIT + render_api_error!("Context commits array size should not be more than #{CONTEXT_COMMITS_POST_LIMIT}", 400) + end + + merge_request = find_merge_request_with_access(params[:merge_request_iid]) + project = merge_request.project + + not_found! unless project.context_commits_enabled? + + authorize!(:update_merge_request, merge_request) + + project = merge_request.target_project + result = ::MergeRequests::AddContextService.new(project, current_user, merge_request: merge_request, commits: commit_ids).execute + + if result.instance_of?(Array) + present result, with: Entities::Commit + else + render_api_error!(result[:message], result[:http_status]) + end + end + + params do + requires :commits, type: Array, allow_blank: false, desc: 'List of context commits sha' + end + desc 'remove context commits of merge request' + delete ':id/merge_requests/:merge_request_iid/context_commits' do + commit_ids = params[:commits] + merge_request = find_merge_request_with_access(params[:merge_request_iid]) + project = merge_request.project + + not_found! unless project.context_commits_enabled? + + authorize!(:destroy_merge_request, merge_request) + project = merge_request.target_project + commits = project.repository.commits_by(oids: commit_ids) + + if commits.size != commit_ids.size + render_api_error!("One or more context commits' sha is not valid.", 400) + end + + MergeRequestContextCommit.delete_bulk(merge_request, commits) + status 204 + end + desc 'Show the merge request changes' do success Entities::MergeRequestChanges end diff --git a/lib/feature/gitaly.rb b/lib/feature/gitaly.rb index cca78360add..6e27b500802 100644 --- a/lib/feature/gitaly.rb +++ b/lib/feature/gitaly.rb @@ -10,6 +10,7 @@ class Feature cache_invalidator inforef_uploadpack_cache commit_without_batch_check + use_core_delta_islands ].freeze DEFAULT_ON_FLAGS = Set.new([]).freeze diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb index 776e80701f1..bad6d3e2a9b 100644 --- a/lib/gitlab/database/sha_attribute.rb +++ b/lib/gitlab/database/sha_attribute.rb @@ -24,7 +24,14 @@ module Gitlab def serialize(value) arg = value ? [value].pack(PACK_FORMAT) : nil - super(arg) + BINARY_TYPE.new.serialize(arg) + end + + # Casts a SHA1 in hexadecimal to the proper binary format. + def self.serialize(value) + arg = value ? [value].pack(PACK_FORMAT) : nil + + BINARY_TYPE.new.serialize(arg) end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 826c69ee5f0..da26eb94fb0 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -791,6 +791,21 @@ describe Projects::MergeRequestsController do end end + describe 'GET context commits' do + it 'returns the commits for context commits' do + get :context_commits, + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid + }, + format: 'json' + + expect(response).to have_gitlab_http_status(:success) + expect(json_response).to be_an Array + end + end + describe 'GET exposed_artifacts' do let(:merge_request) do create(:merge_request, diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index aeb053fe9f6..42dfdd3115c 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -17,6 +17,7 @@ describe Projects::RepositoriesController do context 'as a user' do let(:user) { create(:user) } + let(:archive_name) { "#{project.path}-master" } before do project.add_developer(user) @@ -30,9 +31,18 @@ describe Projects::RepositoriesController do end it 'responds with redirect to the short name archive if fully qualified' do - get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master/#{project.path}-master" }, format: "zip" + get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master/#{archive_name}" }, format: "zip" expect(assigns(:ref)).to eq("master") + expect(assigns(:filename)).to eq(archive_name) + expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:") + end + + it 'responds with redirect for a path with multiple slashes' do + get :archive, params: { namespace_id: project.namespace, project_id: project, id: "improve/awesome/#{archive_name}" }, format: "zip" + + expect(assigns(:ref)).to eq("improve/awesome") + expect(assigns(:filename)).to eq(archive_name) expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:") end diff --git a/spec/factories/merge_request_context_commit.rb b/spec/factories/merge_request_context_commit.rb new file mode 100644 index 00000000000..f9bfc9af02e --- /dev/null +++ b/spec/factories/merge_request_context_commit.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :merge_request_context_commit do + association :merge_request, factory: :merge_request + author_name { 'test' } + author_email { 'test@test.com' } + message { '' } + relative_order { 0 } + sha { Digest::SHA1.hexdigest(SecureRandom.hex) } + end +end diff --git a/spec/factories/merge_request_context_commit_diff_file.rb b/spec/factories/merge_request_context_commit_diff_file.rb new file mode 100644 index 00000000000..eb497166d05 --- /dev/null +++ b/spec/factories/merge_request_context_commit_diff_file.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :merge_request_context_commit_diff_file do + association :merge_request_context_commit + + sha { Digest::SHA1.hexdigest(SecureRandom.hex) } + relative_order { 0 } + new_file { true } + renamed_file { false } + deleted_file { false } + too_large { false } + a_mode { 0 } + b_mode { 100644 } + new_path { 'foo' } + old_path { 'foo' } + diff { '' } + binary { false } + end +end diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb new file mode 100644 index 00000000000..e348eb80b1a --- /dev/null +++ b/spec/features/groups/navbar_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Group navbar' do + it_behaves_like 'verified navigation bar' do + let(:user) { create(:user) } + let(:group) { create(:group) } + + let(:structure) do + [ + { + nav_item: _('Group overview'), + nav_sub_items: [ + _('Details'), + _('Activity'), + (_('Contribution Analytics') if Gitlab.ee?) + ] + }, + { + nav_item: _('Issues'), + nav_sub_items: [ + _('List'), + _('Board'), + _('Labels'), + _('Milestones') + ] + }, + { + nav_item: _('Merge Requests'), + nav_sub_items: [] + }, + { + nav_item: _('Kubernetes'), + nav_sub_items: [] + }, + { + nav_item: _('Members'), + nav_sub_items: [] + } + ] + end + + before do + group.add_maintainer(user) + sign_in(user) + + visit group_path(group) + end + end +end diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb index b8efabb0cab..5364bc10b2f 100644 --- a/spec/features/projects/files/user_browses_files_spec.rb +++ b/spec/features/projects/files/user_browses_files_spec.rb @@ -171,6 +171,31 @@ describe "User browses files" do end end + context "when browsing a `improve/awesome` branch", :js do + before do + visit(project_tree_path(project, "improve/awesome")) + end + + it "shows files from a repository" do + expect(page).to have_content("VERSION") + .and have_content(".gitignore") + .and have_content("LICENSE") + end + end + + context "when browsing a `test-#` branch", :js do + before do + project.repository.create_branch('test-#', project.repository.root_ref) + visit(project_tree_path(project, "test-#")) + end + + it "shows files from a repository" do + expect(page).to have_content("VERSION") + .and have_content(".gitignore") + .and have_content("LICENSE") + end + end + context "when browsing a specific ref", :js do let(:ref) { project_tree_path(project, "6d39438") } diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb new file mode 100644 index 00000000000..f87bd5ca529 --- /dev/null +++ b/spec/features/projects/navbar_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Project navbar' do + it_behaves_like 'verified navigation bar' do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + + let(:structure) do + [ + { + nav_item: _('Project overview'), + nav_sub_items: [ + _('Details'), + _('Activity'), + _('Releases') + ] + }, + { + nav_item: _('Repository'), + nav_sub_items: [ + _('Files'), + _('Commits'), + _('Branches'), + _('Tags'), + _('Contributors'), + _('Graph'), + _('Compare'), + (_('Locked Files') if Gitlab.ee?) + ] + }, + { + nav_item: _('Issues'), + nav_sub_items: [ + _('List'), + _('Boards'), + _('Labels'), + _('Milestones') + ] + }, + { + nav_item: _('Merge Requests'), + nav_sub_items: [] + }, + { + nav_item: _('CI / CD'), + nav_sub_items: [ + _('Pipelines'), + _('Jobs'), + _('Artifacts'), + _('Schedules') + ] + }, + { + nav_item: _('Operations'), + nav_sub_items: [ + _('Metrics'), + _('Environments'), + _('Error Tracking'), + _('Serverless'), + _('Kubernetes'), + _('Auto DevOps') + ] + }, + { + nav_item: _('Analytics'), + nav_sub_items: [ + (_('Code Review') if Gitlab.ee?), + _('Cycle Analytics'), + _('Repository Analytics') + ] + }, + { + nav_item: _('Wiki'), + nav_sub_items: [] + }, + { + nav_item: _('Snippets'), + nav_sub_items: [] + }, + { + nav_item: _('Settings'), + nav_sub_items: [ + _('General'), + _('Members'), + _('Integrations'), + _('Repository'), + _('CI / CD'), + _('Operations'), + (_('Audit Events') if Gitlab.ee?) + ].compact + } + ] + end + + before do + project.add_maintainer(user) + sign_in(user) + + visit project_path(project) + end + end +end diff --git a/spec/finders/context_commits_finder_spec.rb b/spec/finders/context_commits_finder_spec.rb new file mode 100644 index 00000000000..13cfa32ecfc --- /dev/null +++ b/spec/finders/context_commits_finder_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ContextCommitsFinder do + describe "#execute" do + let(:project) { create(:project, :repository) } + let(:merge_request) { create(:merge_request) } + let(:commit) { create(:commit, id: '6d394385cf567f80a8fd85055db1ab4c5295806f') } + + it 'filters commits by valid sha/commit message' do + params = { search: commit.id } + + commits = described_class.new(project, merge_request, params).execute + + expect(commits.length).to eq(1) + expect(commits[0].id).to eq(commit.id) + end + + it 'returns nothing when searched by invalid sha/commit message' do + params = { search: 'zzz' } + + commits = described_class.new(project, merge_request, params).execute + + expect(commits).to be_empty + end + end +end diff --git a/spec/frontend/clusters/components/applications_spec.js b/spec/frontend/clusters/components/applications_spec.js index 01e9b04dcd7..c3336edfe59 100644 --- a/spec/frontend/clusters/components/applications_spec.js +++ b/spec/frontend/clusters/components/applications_spec.js @@ -14,9 +14,6 @@ describe('Applications', () => { beforeEach(() => { Applications = Vue.extend(applications); - - gon.features = gon.features || {}; - gon.features.enableClusterApplicationElasticStack = true; }); afterEach(() => { diff --git a/spec/initializers/attr_encrypted_thread_safe_spec.rb b/spec/initializers/attr_encrypted_thread_safe_spec.rb new file mode 100644 index 00000000000..096b8b196b4 --- /dev/null +++ b/spec/initializers/attr_encrypted_thread_safe_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe AttrEncrypted do + describe '#encrypted_attributes' do + subject do + Class.new(ActiveRecord::Base) do + self.table_name = 'projects' + + attr_accessor :encrypted_foo + attr_accessor :encrypted_foo_iv + + attr_encrypted :foo, key: 'This is a key that is 256 bits!!' + end + end + + it 'does not share state with other instances' do + instance = subject.new + instance.foo = 'bar' + + another_instance = subject.new + + expect(instance.encrypted_attributes[:foo][:operation]).to eq(:encrypting) + expect(another_instance.encrypted_attributes[:foo][:operation]).to be_nil + end + end +end diff --git a/spec/lib/gitlab/database/sha_attribute_spec.rb b/spec/lib/gitlab/database/sha_attribute_spec.rb index c6fc55291f5..15695bc8069 100644 --- a/spec/lib/gitlab/database/sha_attribute_spec.rb +++ b/spec/lib/gitlab/database/sha_attribute_spec.rb @@ -25,7 +25,7 @@ describe Gitlab::Database::ShaAttribute do describe '#serialize' do it 'converts a SHA String to binary data' do - expect(attribute.serialize(sha).to_s).to eq(binary_sha) + expect(described_class.serialize(sha).to_s).to eq(binary_sha) end end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 08e57e541a4..22c2fc5545e 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -125,6 +125,8 @@ merge_requests: - merge_user - merge_request_diffs - merge_request_diff +- merge_request_context_commits +- merge_request_context_commit_diff_files - events - merge_requests_closing_issues - cached_closes_issues @@ -170,6 +172,9 @@ merge_request_diff_commits: - merge_request_diff merge_request_diff_files: - merge_request_diff +merge_request_context_commits: +- merge_request +- diff_files ci_pipelines: - project - user diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index ad363233bfe..9c4854a30ad 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -225,6 +225,31 @@ MergeRequestDiffFile: - b_mode - too_large - binary +MergeRequestContextCommit: +- id +- authored_date +- committed_date +- relative_order +- sha +- author_name +- author_email +- committer_name +- committer_email +- message +- merge_request_id +MergeRequestContextCommitDiffFile: +- sha +- relative_order +- new_file +- renamed_file +- deleted_file +- new_path +- old_path +- a_mode +- b_mode +- too_large +- binary +- text MergeRequest::Metrics: - id - created_at diff --git a/spec/models/merge_request_context_commit_diff_file_spec.rb b/spec/models/merge_request_context_commit_diff_file_spec.rb new file mode 100644 index 00000000000..37d44662326 --- /dev/null +++ b/spec/models/merge_request_context_commit_diff_file_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeRequestContextCommitDiffFile do + describe 'associations' do + it { is_expected.to belong_to(:merge_request_context_commit) } + end +end diff --git a/spec/models/merge_request_context_commit_spec.rb b/spec/models/merge_request_context_commit_spec.rb new file mode 100644 index 00000000000..5a1bf9874ac --- /dev/null +++ b/spec/models/merge_request_context_commit_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeRequestContextCommit do + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } + let(:raw_repository) { project.repository.raw_repository } + let(:commits) do + [ + project.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e'), + project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') + ] + end + + describe 'associations' do + it { is_expected.to belong_to(:merge_request) } + it { is_expected.to have_many(:diff_files).class_name("MergeRequestContextCommitDiffFile") } + end + + describe '.delete_bulk' do + let(:context_commit1) { create(:merge_request_context_commit, merge_request: merge_request, sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + let(:context_commit2) { create(:merge_request_context_commit, merge_request: merge_request, sha: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d') } + + it 'deletes context commits for given commit sha\'s and returns the commit' do + expect(described_class.delete_bulk(merge_request, [context_commit1, context_commit2])).to eq(2) + end + + it 'doesn\'t delete context commits when commit sha\'s are not passed' do + expect(described_class.delete_bulk(merge_request, [])).to eq(0) + end + end +end diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb index c0a09dab0b0..a74c0b20642 100644 --- a/spec/models/merge_request_diff_commit_spec.rb +++ b/spec/models/merge_request_diff_commit_spec.rb @@ -18,7 +18,6 @@ describe MergeRequestDiffCommit do end describe '.create_bulk' do - let(:sha_attribute) { Gitlab::Database::ShaAttribute.new } let(:merge_request_diff_id) { merge_request.merge_request_diff.id } let(:commits) do [ @@ -38,7 +37,7 @@ describe MergeRequestDiffCommit do "committer_email": "dmitriy.zaporozhets@gmail.com", "merge_request_diff_id": merge_request_diff_id, "relative_order": 0, - "sha": sha_attribute.serialize("5937ac0a7beb003549fc5fd26fc247adbce4a52e") + "sha": Gitlab::Database::ShaAttribute.serialize("5937ac0a7beb003549fc5fd26fc247adbce4a52e") }, { "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", @@ -50,7 +49,7 @@ describe MergeRequestDiffCommit do "committer_email": "dmitriy.zaporozhets@gmail.com", "merge_request_diff_id": merge_request_diff_id, "relative_order": 1, - "sha": sha_attribute.serialize("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") + "sha": Gitlab::Database::ShaAttribute.serialize("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") } ] end @@ -81,7 +80,7 @@ describe MergeRequestDiffCommit do "committer_email": "alejorro70@gmail.com", "merge_request_diff_id": merge_request_diff_id, "relative_order": 0, - "sha": sha_attribute.serialize("ba3343bc4fa403a8dfbfcab7fc1a8c29ee34bd69") + "sha": Gitlab::Database::ShaAttribute.serialize("ba3343bc4fa403a8dfbfcab7fc1a8c29ee34bd69") }] end diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index 93feef83067..cb10bebfd98 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -326,7 +326,7 @@ describe API::Internal::Base do expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) - expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-cache-invalidator' => 'true', 'gitaly-feature-commit-without-batch-check' => 'true') + expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-cache-invalidator' => 'true', 'gitaly-feature-commit-without-batch-check' => 'true', 'gitaly-feature-use-core-delta-islands' => 'true') expect(user.reload.last_activity_on).to eql(Date.today) end end @@ -346,7 +346,7 @@ describe API::Internal::Base do expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) - expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-cache-invalidator' => 'true', 'gitaly-feature-commit-without-batch-check' => 'true') + expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-cache-invalidator' => 'true', 'gitaly-feature-commit-without-batch-check' => 'true', 'gitaly-feature-use-core-delta-islands' => 'true') expect(user.reload.last_activity_on).to be_nil end end @@ -594,7 +594,7 @@ describe API::Internal::Base do expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) - expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-cache-invalidator' => 'true', 'gitaly-feature-commit-without-batch-check' => 'true') + expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-inforef-uploadpack-cache' => 'true', 'gitaly-feature-cache-invalidator' => 'true', 'gitaly-feature-commit-without-batch-check' => 'true', 'gitaly-feature-use-core-delta-islands' => 'true') end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index c3d92a90d11..58ae4565582 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -12,7 +12,8 @@ describe API::MergeRequests do let(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace, only_allow_merge_if_pipeline_succeeds: false) } let(:milestone) { create(:milestone, title: '1.0.0', project: project) } let(:milestone1) { create(:milestone, title: '0.9', project: project) } - let!(:merge_request) { create(:merge_request, :simple, milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, source_branch: 'markdown', title: "Test", created_at: base_time) } + let(:merge_request_context_commit) {create(:merge_request_context_commit, message: 'test')} + let!(:merge_request) { create(:merge_request, :simple, milestone: milestone1, author: user, assignees: [user], merge_request_context_commits: [merge_request_context_commit], source_project: project, target_project: project, source_branch: 'markdown', title: "Test", created_at: base_time) } let!(:merge_request_closed) { create(:merge_request, state: "closed", milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) } let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignees: [user], source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') } let!(:merge_request_locked) { create(:merge_request, state: "locked", milestone: milestone1, author: user, assignees: [user], source_project: project, target_project: project, title: "Locked test", created_at: base_time + 1.second) } @@ -1066,6 +1067,20 @@ describe API::MergeRequests do end end + describe 'GET /projects/:id/merge_requests/:merge_request_iid/:context_commits' do + it 'returns a 200 when merge request is valid' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/context_commits", user) + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(merge_request.context_commits.size) + end + + it 'returns a 404 when merge_request_iid not found' do + get api("/projects/#{project.id}/merge_requests/0/context_commits", user) + expect(response).to have_gitlab_http_status(404) + end + end + describe 'GET /projects/:id/merge_requests/:merge_request_iid/changes' do it 'returns the change information of the merge_request' do get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/changes", user) @@ -1540,6 +1555,93 @@ describe API::MergeRequests do end end + describe "POST /projects/:id/merge_requests/:merge_request_iid/context_commits" do + let(:merge_request_iid) { merge_request.iid } + let(:authenticated_user) { user } + let(:commit) { project.repository.commit } + + let(:params) do + { + commits: [commit.id] + } + end + + let(:params_empty_commits) do + { + commits: [] + } + end + + let(:params_invalid_shas) do + { + commits: ['invalid'] + } + end + + describe 'when authenticated' do + it 'creates and returns the new context commit' do + post api("/projects/#{project.id}/merge_requests/#{merge_request_iid}/context_commits", authenticated_user), params: params + expect(response).to have_gitlab_http_status(201) + expect(json_response).to be_an Array + expect(json_response.first['short_id']).to eq(commit.short_id) + expect(json_response.first['title']).to eq(commit.title) + expect(json_response.first['message']).to eq(commit.message) + expect(json_response.first['author_name']).to eq(commit.author_name) + expect(json_response.first['author_email']).to eq(commit.author_email) + expect(json_response.first['committer_name']).to eq(commit.committer_name) + expect(json_response.first['committer_email']).to eq(commit.committer_email) + end + + context 'doesnt create when its already created' do + before do + create(:merge_request_context_commit, merge_request: merge_request, sha: commit.id) + end + it 'returns 400 when the context commit is already created' do + post api("/projects/#{project.id}/merge_requests/#{merge_request_iid}/context_commits", authenticated_user), params: params + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']).to eq("Context commits: [\"#{commit.id}\"] are already created") + end + end + + it 'returns 400 when one or more shas are invalid' do + post api("/projects/#{project.id}/merge_requests/#{merge_request_iid}/context_commits", authenticated_user), params: params_invalid_shas + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']).to eq('One or more context commits\' sha is not valid.') + end + + it 'returns 400 when the commits are empty' do + post api("/projects/#{project.id}/merge_requests/#{merge_request_iid}/context_commits", authenticated_user), params: params_empty_commits + expect(response).to have_gitlab_http_status(400) + end + + it 'returns 400 when params is empty' do + post api("/projects/#{project.id}/merge_requests/#{merge_request_iid}/context_commits", authenticated_user) + expect(response).to have_gitlab_http_status(400) + end + + it 'returns 403 when creating new context commit for guest role' do + guest = create(:user) + project.add_guest(guest) + post api("/projects/#{project.id}/merge_requests/#{merge_request_iid}/context_commits", guest), params: params + expect(response).to have_gitlab_http_status(403) + end + + it 'returns 403 when creating new context commit for reporter role' do + reporter = create(:user) + project.add_reporter(reporter) + post api("/projects/#{project.id}/merge_requests/#{merge_request_iid}/context_commits", reporter), params: params + expect(response).to have_gitlab_http_status(403) + end + end + + context 'when unauthenticated' do + it 'returns 401 if user tries to create context commits' do + post api("/projects/#{project.id}/merge_requests/#{merge_request_iid}/context_commits"), params: params + expect(response).to have_gitlab_http_status(401) + end + end + end + describe "DELETE /projects/:id/merge_requests/:merge_request_iid" do context "when the user is developer" do let(:developer) { create(:user) } @@ -1579,6 +1681,79 @@ describe API::MergeRequests do end end + describe "DELETE /projects/:id/merge_requests/:merge_request_iid/context_commits" do + let(:merge_request_iid) { merge_request.iid } + let(:authenticated_user) { user } + let(:commit) { project.repository.commit } + + context "when authenticated" do + let(:params) do + { + commits: [commit.id] + } + end + + let(:params_invalid_shas) do + { + commits: ["invalid"] + } + end + + let(:params_empty_commits) do + { + commits: [] + } + end + + it "deletes context commit" do + delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/context_commits", authenticated_user), params: params + + expect(response).to have_gitlab_http_status(204) + end + + it "returns 400 when invalid commit sha is passed" do + delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/context_commits", authenticated_user), params: params_invalid_shas + + expect(response).to have_gitlab_http_status(400) + expect(json_response["message"]).to eq('One or more context commits\' sha is not valid.') + end + + it "returns 400 when commits is empty" do + delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/context_commits", authenticated_user), params: params_empty_commits + + expect(response).to have_gitlab_http_status(400) + end + + it "returns 400 when no params is passed" do + delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/context_commits", authenticated_user) + + expect(response).to have_gitlab_http_status(400) + end + + it 'returns 403 when deleting existing context commit for guest role' do + guest = create(:user) + project.add_guest(guest) + delete api("/projects/#{project.id}/merge_requests/#{merge_request_iid}/context_commits", guest), params: params + expect(response).to have_gitlab_http_status(403) + end + + it 'returns 403 when deleting existing context commit for reporter role' do + reporter = create(:user) + project.add_reporter(reporter) + delete api("/projects/#{project.id}/merge_requests/#{merge_request_iid}/context_commits", reporter), params: params + expect(response).to have_gitlab_http_status(403) + end + end + + context "when unauthenticated" do + it "returns 401, unauthorised error" do + delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/context_commits") + + expect(response).to have_gitlab_http_status(401) + end + end + end + describe "PUT /projects/:id/merge_requests/:merge_request_iid/merge", :clean_gitlab_redis_cache do let(:pipeline) { create(:ci_pipeline) } diff --git a/spec/serializers/diffs_metadata_entity_spec.rb b/spec/serializers/diffs_metadata_entity_spec.rb index 0fa643d37b3..86438bd59d7 100644 --- a/spec/serializers/diffs_metadata_entity_spec.rb +++ b/spec/serializers/diffs_metadata_entity_spec.rb @@ -29,6 +29,7 @@ describe DiffsMetadataEntity do :added_lines, :removed_lines, :render_overflow_warning, :email_patch_path, :plain_diff_path, :merge_request_diffs, + :context_commits, # Attributes :diff_files ) diff --git a/spec/services/merge_requests/add_context_service_spec.rb b/spec/services/merge_requests/add_context_service_spec.rb new file mode 100644 index 00000000000..d4e95c2f1ea --- /dev/null +++ b/spec/services/merge_requests/add_context_service_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeRequests::AddContextService do + let(:project) { create(:project, :repository) } + let(:admin) { create(:admin) } + let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: admin) } + let(:commits) { ["874797c3a73b60d2187ed6e2fcabd289ff75171e"] } + let(:raw_repository) { project.repository.raw } + + subject(:service) { described_class.new(project, admin, merge_request: merge_request, commits: commits) } + + describe "#execute" do + it "adds context commit" do + service.execute + + expect(merge_request.merge_request_context_commit_diff_files.length).to eq(2) + end + + context "when user doesn't have permission to update merge request" do + let(:user) { create(:user) } + let(:merge_request1) { create(:merge_request, source_project: project, author: user) } + + subject(:service) { described_class.new(project, user, merge_request: merge_request, commits: commits) } + + it "doesn't add context commit" do + subject.execute + + expect(merge_request.merge_request_context_commit_diff_files.length).to eq(0) + end + end + + context "when the commits array is empty" do + subject(:service) { described_class.new(project, admin, merge_request: merge_request, commits: []) } + + it "doesn't add context commit" do + subject.execute + + expect(merge_request.merge_request_context_commit_diff_files.length).to eq(0) + end + end + end +end diff --git a/spec/support/shared_examples/features/navbar_shared_examples.rb b/spec/support/shared_examples/features/navbar_shared_examples.rb new file mode 100644 index 00000000000..a963739878e --- /dev/null +++ b/spec/support/shared_examples/features/navbar_shared_examples.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'verified navigation bar' do + it 'renders correctly' do + current_structure = page.find_all('.sidebar-top-level-items > li', class: ['!hidden']).map do |item| + nav_item = item.find_all('a').first.text.gsub(/\s+\d+$/, '') # remove counts at the end + + nav_sub_items = item + .find_all('.sidebar-sub-level-items a') + .map(&:text) + .drop(1) # remove the first hidden item + + { nav_item: nav_item, nav_sub_items: nav_sub_items } + end + + structure.each { |s| s[:nav_sub_items].compact! } + + expect(current_structure).to eq(structure) + end +end |