summaryrefslogtreecommitdiff
path: root/lib/gitlab/satellite/merge_action.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/satellite/merge_action.rb')
-rw-r--r--lib/gitlab/satellite/merge_action.rb146
1 files changed, 146 insertions, 0 deletions
diff --git a/lib/gitlab/satellite/merge_action.rb b/lib/gitlab/satellite/merge_action.rb
new file mode 100644
index 00000000000..f9bf286697e
--- /dev/null
+++ b/lib/gitlab/satellite/merge_action.rb
@@ -0,0 +1,146 @@
+module Gitlab
+ module Satellite
+ # GitLab server-side merge
+ class MergeAction < Action
+ attr_accessor :merge_request
+
+ def initialize(user, merge_request)
+ super user, merge_request.target_project
+ @merge_request = merge_request
+ end
+
+ # Checks if a merge request can be executed without user interaction
+ def can_be_merged?
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ merge_in_satellite!(merge_repo)
+ end
+ end
+
+ # Merges the source branch into the target branch in the satellite and
+ # pushes it back to the repository.
+ # It also removes the source branch if requested in the merge request (and this is permitted by the merge request).
+ #
+ # Returns false if the merge produced conflicts
+ # Returns false if pushing from the satellite to the repository failed or was rejected
+ # Returns true otherwise
+ def merge!(merge_commit_message = nil)
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ if merge_in_satellite!(merge_repo, merge_commit_message)
+ # push merge back to bare repo
+ # will raise CommandFailed when push fails
+ merge_repo.git.push(default_options, :origin, merge_request.target_branch)
+
+ # remove source branch
+ if merge_request.remove_source_branch?
+ # will raise CommandFailed when push fails
+ merge_repo.git.push(default_options, :origin, ":#{merge_request.source_branch}")
+ end
+ # merge, push and branch removal successful
+ true
+ end
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
+ def diff_in_satellite
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ update_satellite_source_and_target!(merge_repo)
+
+ # Only show what is new in the source branch compared to the target branch, not the other way around.
+ # The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2)
+ # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B"
+ common_commit = merge_repo.git.native(:merge_base, default_options, ["origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}"]).strip
+ merge_repo.git.native(:diff, default_options, common_commit, "source/#{merge_request.source_branch}")
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
+ def diffs_between_satellite
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ update_satellite_source_and_target!(merge_repo)
+ if merge_request.for_fork?
+ repository = Gitlab::Git::Repository.new(merge_repo.path)
+ diffs = Gitlab::Git::Diff.between(
+ repository,
+ "source/#{merge_request.source_branch}",
+ "origin/#{merge_request.target_branch}"
+ )
+ else
+ raise "Attempt to determine diffs between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
+ end
+
+ return diffs
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
+ # Get commit as an email patch
+ def format_patch
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ update_satellite_source_and_target!(merge_repo)
+ patch = merge_repo.git.format_patch(default_options({ stdout: true }), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}")
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
+ # Retrieve an array of commits between the source and the target
+ def commits_between
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ update_satellite_source_and_target!(merge_repo)
+ if merge_request.for_fork?
+ repository = Gitlab::Git::Repository.new(merge_repo.path)
+ commits = Gitlab::Git::Commit.between(
+ repository,
+ "origin/#{merge_request.target_branch}",
+ "source/#{merge_request.source_branch}"
+ )
+ else
+ raise "Attempt to determine commits between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
+ end
+
+ return commits
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
+ private
+ # Merges the source_branch into the target_branch in the satellite.
+ #
+ # Note: it will clear out the satellite before doing anything
+ #
+ # Returns false if the merge produced conflicts
+ # Returns true otherwise
+ def merge_in_satellite!(repo, message = nil)
+ update_satellite_source_and_target!(repo)
+
+ message ||= "Merge branch '#{merge_request.source_branch}' into '#{merge_request.target_branch}'"
+
+ # merge the source branch into the satellite
+ # will raise CommandFailed when merge fails
+ repo.git.merge(default_options({ no_ff: true }), "-m#{message}", "source/#{merge_request.source_branch}")
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
+ # Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for merges, diffs etc
+ def update_satellite_source_and_target!(repo)
+ repo.remote_add('source', merge_request.source_project.repository.path_to_repo)
+ repo.remote_fetch('source')
+ repo.git.checkout(default_options({ b: true }), merge_request.target_branch, "origin/#{merge_request.target_branch}")
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+ end
+ end
+end