Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
7cc6872401
commit
23288f62da
68 changed files with 1258 additions and 79 deletions
|
@ -1 +1 @@
|
|||
1.83.0
|
||||
1.85.0
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -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'
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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)}`;
|
||||
|
|
|
@ -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 },
|
||||
},
|
||||
|
|
|
@ -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 => ({
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
62
app/finders/context_commits_finder.rb
Normal file
62
app/finders/context_commits_finder.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
17
app/models/concerns/cached_commit.rb
Normal file
17
app/models/concerns/cached_commit.rb
Normal file
|
@ -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
|
|
@ -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?
|
||||
|
||||
|
|
35
app/models/merge_request_context_commit.rb
Normal file
35
app/models/merge_request_context_commit.rb
Normal file
|
@ -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
|
17
app/models/merge_request_context_commit_diff_file.rb
Normal file
17
app/models/merge_request_context_commit_diff_file.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
116
app/services/merge_requests/add_context_service.rb
Normal file
116
app/services/merge_requests/add_context_service.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
5
changelogs/unreleased/ak-remove-es-ff.yml
Normal file
5
changelogs/unreleased/ak-remove-es-ff.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add new Elastic Stack cluster application for pod log aggregation
|
||||
merge_request: 23058
|
||||
author:
|
||||
type: added
|
5
changelogs/unreleased/allowed-to-fail-warning-color.yml
Normal file
5
changelogs/unreleased/allowed-to-fail-warning-color.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Changed color of allowed to fail badge from danger to warning
|
||||
merge_request: !23437
|
||||
author:
|
||||
type: changed
|
5
changelogs/unreleased/gitaly-version-v1.85.0.yml
Normal file
5
changelogs/unreleased/gitaly-version-v1.85.0.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Upgrade to Gitaly v1.85.0
|
||||
merge_request: 23945
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Optimize ref name lookups in archive downloads
|
||||
merge_request: 23890
|
||||
author:
|
||||
type: performance
|
17
config/initializers/attr_encrypted_thread_safe.rb
Normal file
17
config/initializers/attr_encrypted_thread_safe.rb
Normal file
|
@ -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
|
|
@ -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'
|
||||
|
|
|
@ -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
|
33
db/schema.rb
33
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
97
doc/api/merge_request_context_commits.md
Normal file
97
doc/api/merge_request_context_commits.md
Normal file
|
@ -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 |
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
12
spec/factories/merge_request_context_commit.rb
Normal file
12
spec/factories/merge_request_context_commit.rb
Normal file
|
@ -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
|
20
spec/factories/merge_request_context_commit_diff_file.rb
Normal file
20
spec/factories/merge_request_context_commit_diff_file.rb
Normal file
|
@ -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
|
51
spec/features/groups/navbar_spec.rb
Normal file
51
spec/features/groups/navbar_spec.rb
Normal file
|
@ -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
|
|
@ -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") }
|
||||
|
||||
|
|
104
spec/features/projects/navbar_spec.rb
Normal file
104
spec/features/projects/navbar_spec.rb
Normal file
|
@ -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
|
28
spec/finders/context_commits_finder_spec.rb
Normal file
28
spec/finders/context_commits_finder_spec.rb
Normal file
|
@ -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
|
|
@ -14,9 +14,6 @@ describe('Applications', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
Applications = Vue.extend(applications);
|
||||
|
||||
gon.features = gon.features || {};
|
||||
gon.features.enableClusterApplicationElasticStack = true;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
28
spec/initializers/attr_encrypted_thread_safe_spec.rb
Normal file
28
spec/initializers/attr_encrypted_thread_safe_spec.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
33
spec/models/merge_request_context_commit_spec.rb
Normal file
33
spec/models/merge_request_context_commit_spec.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
44
spec/services/merge_requests/add_context_service_spec.rb
Normal file
44
spec/services/merge_requests/add_context_service_spec.rb
Normal file
|
@ -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
|
|
@ -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
|
Loading…
Reference in a new issue