From 2eb5c1e99267e00408bd728ffe4c371bf8e3d3be Mon Sep 17 00:00:00 2001 From: Igor Drozdov <idrozdov@gitlab.com> Date: Mon, 5 Oct 2020 20:13:46 +0300 Subject: [PATCH] Allow conflicts to be merged in diff When we're calculating merge-ref-head diff, we want the conflicts to be merged in diff in order to display them differently --- Gemfile | 2 +- Gemfile.lock | 4 +-- app/models/merge_request.rb | 2 +- app/models/repository.rb | 4 +-- .../merge_requests/merge_to_ref_service.rb | 9 ++++- .../mergeability_check_service.rb | 13 +++++--- .../display_merge_conflicts_in_diff.yml | 7 ++++ lib/gitlab/git/repository.rb | 4 +-- lib/gitlab/gitaly_client/operation_service.rb | 5 +-- spec/lib/gitlab/git/repository_spec.rb | 3 +- .../gitaly_client/operation_service_spec.rb | 3 +- spec/models/merge_request_spec.rb | 24 ++++++++++---- .../merge_to_ref_service_spec.rb | 11 +++++++ .../mergeability_check_service_spec.rb | 33 +++++++++++++++++-- 14 files changed, 99 insertions(+), 25 deletions(-) create mode 100644 config/feature_flags/development/display_merge_conflicts_in_diff.yml diff --git a/Gemfile b/Gemfile index e5f4930f95e2..4f4cf0753c31 100644 --- a/Gemfile +++ b/Gemfile @@ -466,7 +466,7 @@ group :ed25519 do end # Gitaly GRPC protocol definitions -gem 'gitaly', '~> 13.5.0-rc1' +gem 'gitaly', '~> 13.5.0-rc2' gem 'grpc', '~> 1.30.2' diff --git a/Gemfile.lock b/Gemfile.lock index 415ce44aab18..ac70d988c0f7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -416,7 +416,7 @@ GEM rails (>= 3.2.0) git (1.7.0) rchardet (~> 1.8) - gitaly (13.5.0.pre.rc1) + gitaly (13.5.0.pre.rc2) grpc (~> 1.0) github-markup (1.7.0) gitlab-chronic (0.10.5) @@ -1327,7 +1327,7 @@ DEPENDENCIES gettext (~> 3.3) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly (~> 13.5.0.pre.rc1) + gitaly (~> 13.5.0.pre.rc2) github-markup (~> 1.7.0) gitlab-chronic (~> 0.10.5) gitlab-fog-azure-rm (~> 1.0) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ed53676ea977..24541ba32180 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -929,7 +929,7 @@ def check_mergeability(async: false) # rubocop: enable CodeReuse/ServiceClass def diffable_merge_ref? - can_be_merged? && merge_ref_head.present? + merge_ref_head.present? && (Feature.enabled?(:display_merge_conflicts_in_diff, project) || can_be_merged?) end # Returns boolean indicating the merge_status should be rechecked in order to diff --git a/app/models/repository.rb b/app/models/repository.rb index 8142398050df..d4fd202b9668 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -860,10 +860,10 @@ def merge(user, source_sha, merge_request, message) end end - def merge_to_ref(user, source_sha, merge_request, target_ref, message, first_parent_ref) + def merge_to_ref(user, source_sha, merge_request, target_ref, message, first_parent_ref, allow_conflicts = false) branch = merge_request.target_branch - raw.merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref) + raw.merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref, allow_conflicts) end def delete_refs(*ref_names) diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb index 1876b1096fe5..c0115e949039 100644 --- a/app/services/merge_requests/merge_to_ref_service.rb +++ b/app/services/merge_requests/merge_to_ref_service.rb @@ -58,8 +58,15 @@ def first_parent_ref params[:first_parent_ref] || merge_request.target_branch_ref end + ## + # The parameter `allow_conflicts` is a flag whether merge conflicts should be merged into diff + # Default is false + def allow_conflicts + params[:allow_conflicts] || false + end + def commit - repository.merge_to_ref(current_user, source, merge_request, target_ref, commit_message, first_parent_ref) + repository.merge_to_ref(current_user, source, merge_request, target_ref, commit_message, first_parent_ref, allow_conflicts) rescue Gitlab::Git::PreReceiveError, Gitlab::Git::CommandError => error raise MergeError, error.message end diff --git a/app/services/merge_requests/mergeability_check_service.rb b/app/services/merge_requests/mergeability_check_service.rb index b41a5fa317e0..627c747203cf 100644 --- a/app/services/merge_requests/mergeability_check_service.rb +++ b/app/services/merge_requests/mergeability_check_service.rb @@ -115,10 +115,14 @@ def merge_ref_head_payload def update_merge_status return unless merge_request.recheck_merge_status? + return merge_request.mark_as_unmergeable if merge_request.broken? - if can_git_merge? && merge_to_ref + merge_to_ref_success = merge_to_ref + + update_diff_discussion_positions! if merge_to_ref_success + + if merge_to_ref_success && can_git_merge? merge_request.mark_as_mergeable - update_diff_discussion_positions! else merge_request.mark_as_unmergeable end @@ -149,13 +153,14 @@ def outdated_merge_ref? end def can_git_merge? - !merge_request.broken? && repository.can_be_merged?(merge_request.diff_head_sha, merge_request.target_branch) + repository.can_be_merged?(merge_request.diff_head_sha, merge_request.target_branch) end def merge_to_ref return true unless merge_ref_auto_sync_enabled? - result = MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request) + params = { allow_conflicts: Feature.enabled?(:display_merge_conflicts_in_diff, project) } + result = MergeRequests::MergeToRefService.new(project, merge_request.author, params).execute(merge_request) result[:status] == :success end diff --git a/config/feature_flags/development/display_merge_conflicts_in_diff.yml b/config/feature_flags/development/display_merge_conflicts_in_diff.yml new file mode 100644 index 000000000000..678037fa3666 --- /dev/null +++ b/config/feature_flags/development/display_merge_conflicts_in_diff.yml @@ -0,0 +1,7 @@ +--- +name: display_merge_conflicts_in_diff +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45008 +rollout_issue_url: +type: development +group: group::source code +default_enabled: false diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 5da85570a3b3..1a3409c1f84e 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -587,9 +587,9 @@ def find_tag(name) tags.find { |tag| tag.name == name } end - def merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref) + def merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref, allow_conflicts) wrapped_gitaly_errors do - gitaly_operation_client.user_merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref) + gitaly_operation_client.user_merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref, allow_conflicts) end end diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb index 786eb3ca4ae2..4850d646de4a 100644 --- a/lib/gitlab/gitaly_client/operation_service.rb +++ b/lib/gitlab/gitaly_client/operation_service.rb @@ -102,7 +102,7 @@ def user_delete_branch(branch_name, user) end end - def user_merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref) + def user_merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref, allow_conflicts) request = Gitaly::UserMergeToRefRequest.new( repository: @gitaly_repo, source_sha: source_sha, @@ -110,7 +110,8 @@ def user_merge_to_ref(user, source_sha, branch, target_ref, message, first_paren target_ref: encode_binary(target_ref), user: Gitlab::Git::User.from_gitlab(user).to_gitaly, message: encode_binary(message), - first_parent_ref: encode_binary(first_parent_ref) + first_parent_ref: encode_binary(first_parent_ref), + allow_conflicts: allow_conflicts ) response = GitalyClient.call(@repository.storage, :operation_service, diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 1c818bd4a88b..6dfa791f70b4 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1650,13 +1650,14 @@ def commit_files(commit) let(:right_branch) { 'test-master' } let(:first_parent_ref) { 'refs/heads/test-master' } let(:target_ref) { 'refs/merge-requests/999/merge' } + let(:allow_conflicts) { false } before do repository.create_branch(right_branch, branch_head) unless repository.ref_exists?(first_parent_ref) end def merge_to_ref - repository.merge_to_ref(user, left_sha, right_branch, target_ref, 'Merge message', first_parent_ref) + repository.merge_to_ref(user, left_sha, right_branch, target_ref, 'Merge message', first_parent_ref, allow_conflicts) end it 'generates a commit in the target_ref' do diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb index b974f456914d..ce01566b8704 100644 --- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb @@ -88,9 +88,10 @@ let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' } let(:ref) { 'refs/merge-requests/x/merge' } let(:message) { 'validación' } + let(:allow_conflicts) { false } let(:response) { Gitaly::UserMergeToRefResponse.new(commit_id: 'new-commit-id') } - subject { client.user_merge_to_ref(user, source_sha, nil, ref, message, first_parent_ref) } + subject { client.user_merge_to_ref(user, source_sha, nil, ref, message, first_parent_ref, allow_conflicts) } it 'sends a user_merge_to_ref message' do expect_any_instance_of(Gitaly::OperationService::Stub) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index b562e41ea2d5..ddb3ffdda2f1 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -4228,14 +4228,26 @@ def create_build(pipeline, coverage, name) it 'returns true' do expect(subject.diffable_merge_ref?).to eq(true) end - end - end - context 'merge request cannot be merged' do - it 'returns false' do - subject.mark_as_unchecked! + context 'merge request cannot be merged' do + before do + subject.mark_as_unchecked! + end + + it 'returns false' do + expect(subject.diffable_merge_ref?).to eq(true) + end + + context 'display_merge_conflicts_in_diff is disabled' do + before do + stub_feature_flags(display_merge_conflicts_in_diff: false) + end - expect(subject.diffable_merge_ref?).to eq(false) + it 'returns false' do + expect(subject.diffable_merge_ref?).to eq(false) + end + end + end end end end diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index b482e8d6724b..14ef5b0b772c 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -252,5 +252,16 @@ def process_merge_to_ref end end end + + context 'allow conflicts to be merged in diff' do + let(:params) { { allow_conflicts: true } } + + it 'calls merge_to_ref with allow_conflicts param' do + expect(project.repository).to receive(:merge_to_ref) + .with(anything, anything, anything, anything, anything, anything, true) + + service.execute(merge_request) + end + end end end diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb index bffd4c4d34d3..725fc16fa7cd 100644 --- a/spec/services/merge_requests/mergeability_check_service_spec.rb +++ b/spec/services/merge_requests/mergeability_check_service_spec.rb @@ -211,11 +211,18 @@ def execute_within_threads(amount:, retry_lease: true) target_branch: 'conflict-start') end - it_behaves_like 'unmergeable merge request' + it 'does not change the merge ref HEAD' do + expect(merge_request.merge_ref_head).to be_nil - it 'returns ServiceResponse.error' do + subject + + expect(merge_request.reload.merge_ref_head).not_to be_nil + end + + it 'returns ServiceResponse.error and keeps merge status as cannot_be_merged' do result = subject + expect(merge_request.merge_status).to eq('cannot_be_merged') expect(result).to be_a(ServiceResponse) expect(result.error?).to be(true) expect(result.message).to eq('Merge request is not mergeable') @@ -373,5 +380,27 @@ def execute_within_threads(amount:, retry_lease: true) end end end + + context 'merge with conflicts' do + it 'calls MergeToRefService with true allow_conflicts param' do + expect(MergeRequests::MergeToRefService).to receive(:new) + .with(project, merge_request.author, { allow_conflicts: true }).and_call_original + + subject + end + + context 'when display_merge_conflicts_in_diff is disabled' do + before do + stub_feature_flags(display_merge_conflicts_in_diff: false) + end + + it 'calls MergeToRefService with false allow_conflicts param' do + expect(MergeRequests::MergeToRefService).to receive(:new) + .with(project, merge_request.author, { allow_conflicts: false }).and_call_original + + subject + end + end + end end end -- GitLab