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