Commit 23288f62 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 7cc68724
......@@ -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')
# 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
......
# 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?
......
# 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
# 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
......
# 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?
......
---
title: Add new Elastic Stack cluster application for pod log aggregation
merge_request: 23058
author:
type: added
---
title: Changed color of allowed to fail badge from danger to warning
merge_request: !23437
author:
type: changed
---
title: Upgrade to Gitaly v1.85.0
merge_request: 23945
author:
type: changed
---
title: Optimize ref name lookups in archive downloads
merge_request: 23890
author:
type: performance
# 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'
......
# 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
......@@ -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
......
# 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.
......
# 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)
......
# 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
......
# 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
# 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
# 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") }
......
# 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
# 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(() => {
......
# 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
......
# 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
# 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
)
......
# 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
# 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment