summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorOswaldo Ferreira <oswaldo@gitlab.com>2019-01-31 16:32:44 -0200
committerOswaldo Ferreira <oswaldo@gitlab.com>2019-02-25 10:40:58 -0300
commit1ad699677fa4b24a9bc002c6dc20164b8832bca5 (patch)
tree8018d8af35b14d78b20144788a0b271226347a9b /app
parent992183534dd1af54bf891789aca5777ea91c0942 (diff)
downloadgitlab-ce-1ad699677fa4b24a9bc002c6dc20164b8832bca5.tar.gz
Support merge to ref for merge-commit and squash
Adds the ground work for writing into the merge ref refs/merge-requests/:iid/merge the merge result between source and target branches of a MR, without further side-effects such as mailing, MR updates and target branch changes.
Diffstat (limited to 'app')
-rw-r--r--app/models/merge_request.rb14
-rw-r--r--app/models/project.rb8
-rw-r--r--app/models/repository.rb6
-rw-r--r--app/services/merge_requests/merge_base_service.rb53
-rw-r--r--app/services/merge_requests/merge_service.rb39
-rw-r--r--app/services/merge_requests/merge_to_ref_service.rb61
6 files changed, 144 insertions, 37 deletions
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 75fca96ce0a..1468ae1c34a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -764,6 +764,16 @@ class MergeRequest < ActiveRecord::Base
true
end
+ def mergeable_to_ref?
+ return false if merged?
+ return false if broken?
+
+ # Given the `merge_ref_path` will have the same
+ # state the `target_branch` would have. Ideally
+ # we need to check if it can be merged to it.
+ project.repository.can_be_merged?(diff_head_sha, target_branch)
+ end
+
def ff_merge_possible?
project.repository.ancestor?(target_branch_sha, diff_head_sha)
end
@@ -1077,6 +1087,10 @@ class MergeRequest < ActiveRecord::Base
"refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/head"
end
+ def merge_ref_path
+ "refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/merge"
+ end
+
def in_locked_state
begin
lock_mr
diff --git a/app/models/project.rb b/app/models/project.rb
index 83f8d004a46..889572e693a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1925,6 +1925,14 @@ class Project < ActiveRecord::Base
persisted? && path_changed?
end
+ def human_merge_method
+ if merge_method == :ff
+ 'Fast-forward'
+ else
+ merge_method.to_s.humanize
+ end
+ end
+
def merge_method
if self.merge_requests_ff_only_enabled
:ff
diff --git a/app/models/repository.rb b/app/models/repository.rb
index ed55a6e572b..cd761a29618 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -854,6 +854,12 @@ class Repository
end
end
+ def merge_to_ref(user, source_sha, merge_request, target_ref, message)
+ branch = merge_request.target_branch
+
+ raw.merge_to_ref(user, source_sha, branch, target_ref, message)
+ end
+
def ff_merge(user, source, target_branch, merge_request: nil)
their_commit_id = commit(source)&.id
raise 'Invalid merge source' if their_commit_id.nil?
diff --git a/app/services/merge_requests/merge_base_service.rb b/app/services/merge_requests/merge_base_service.rb
new file mode 100644
index 00000000000..4c5a0d96957
--- /dev/null
+++ b/app/services/merge_requests/merge_base_service.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class MergeBaseService < MergeRequests::BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ MergeError = Class.new(StandardError)
+
+ attr_reader :merge_request
+
+ # Overridden in EE.
+ def hooks_validation_pass?(_merge_request)
+ true
+ end
+
+ # Overridden in EE.
+ def hooks_validation_error(_merge_request)
+ end
+
+ def source
+ if merge_request.squash
+ squash_sha!
+ else
+ merge_request.diff_head_sha
+ end
+ end
+
+ private
+
+ def handle_merge_error(*args)
+ # No-op
+ end
+
+ def commit_message
+ params[:commit_message] ||
+ merge_request.default_merge_commit_message
+ end
+
+ def squash_sha!
+ strong_memoize(:squash_sha) do
+ params[:merge_request] = merge_request
+ squash_result = ::MergeRequests::SquashService.new(project, current_user, params).execute
+
+ case squash_result[:status]
+ when :success
+ squash_result[:squash_sha]
+ when :error
+ raise ::MergeRequests::MergeService::MergeError, squash_result[:message]
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 449997bcf07..b1d01955d85 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -7,13 +7,7 @@ module MergeRequests
# mark merge request as merged and execute all hooks and notifications
# Executed when you do merge via GitLab UI
#
- class MergeService < MergeRequests::BaseService
- include Gitlab::Utils::StrongMemoize
-
- MergeError = Class.new(StandardError)
-
- attr_reader :merge_request, :source
-
+ class MergeService < MergeRequests::MergeBaseService
delegate :merge_jid, :state, to: :@merge_request
def execute(merge_request)
@@ -38,19 +32,6 @@ module MergeRequests
handle_merge_error(log_message: e.message, save_message_on_model: true)
end
- def source
- if merge_request.squash
- squash_sha!
- else
- merge_request.diff_head_sha
- end
- end
-
- # Overridden in EE.
- def hooks_validation_pass?(_merge_request)
- true
- end
-
private
def error_check!
@@ -79,24 +60,8 @@ module MergeRequests
merge_request.update!(merge_commit_sha: commit_id)
end
- def squash_sha!
- strong_memoize(:squash_sha) do
- params[:merge_request] = merge_request
- squash_result = ::MergeRequests::SquashService.new(project, current_user, params).execute
-
- case squash_result[:status]
- when :success
- squash_result[:squash_sha]
- when :error
- raise ::MergeRequests::MergeService::MergeError, squash_result[:message]
- end
- end
- end
-
def try_merge
- message = params[:commit_message] || merge_request.default_merge_commit_message
-
- repository.merge(current_user, source, merge_request, message)
+ repository.merge(current_user, source, merge_request, commit_message)
rescue Gitlab::Git::PreReceiveError => e
handle_merge_error(log_message: e.message)
raise MergeError, 'Something went wrong during merge pre-receive hook'
diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb
new file mode 100644
index 00000000000..c4a40044143
--- /dev/null
+++ b/app/services/merge_requests/merge_to_ref_service.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ # Performs the merge between source SHA and the target branch. Instead
+ # of writing the result to the MR target branch, it targets the `target_ref`.
+ #
+ # Ideally this should leave the `target_ref` state with the same state the
+ # target branch would have if we used the regular `MergeService`, but without
+ # every side-effect that comes with it (MR updates, mails, source branch
+ # deletion, etc). This service should be kept idempotent (i.e. can
+ # be executed regardless of the `target_ref` current state).
+ #
+ class MergeToRefService < MergeRequests::MergeBaseService
+ def execute(merge_request)
+ @merge_request = merge_request
+
+ error_check!
+
+ commit_id = commit
+
+ raise MergeError, 'Conflicts detected during merge' unless commit_id
+
+ success(commit_id: commit_id)
+ rescue MergeError => error
+ error(error.message)
+ end
+
+ private
+
+ def error_check!
+ error =
+ if !merge_method_supported?
+ "#{project.human_merge_method} to #{target_ref} is currently not supported."
+ elsif !hooks_validation_pass?(merge_request)
+ hooks_validation_error(merge_request)
+ elsif @merge_request.should_be_rebased?
+ 'Fast-forward merge is not possible. Please update your source branch.'
+ elsif !@merge_request.mergeable_to_ref?
+ "Merge request is not mergeable to #{target_ref}"
+ elsif !source
+ 'No source for merge'
+ end
+
+ raise MergeError, error if error
+ end
+
+ def target_ref
+ merge_request.merge_ref_path
+ end
+
+ def commit
+ repository.merge_to_ref(current_user, source, merge_request, target_ref, commit_message)
+ rescue Gitlab::Git::PreReceiveError => error
+ raise MergeError, error.message
+ end
+
+ def merge_method_supported?
+ [:merge, :rebase_merge].include?(project.merge_method)
+ end
+ end
+end