diff options
Diffstat (limited to 'app/services')
-rw-r--r-- | app/services/commits/change_service.rb | 20 | ||||
-rw-r--r-- | app/services/compare_service.rb | 34 | ||||
-rw-r--r-- | app/services/create_branch_service.rb | 30 | ||||
-rw-r--r-- | app/services/files/base_service.rb | 17 | ||||
-rw-r--r-- | app/services/files/create_dir_service.rb | 10 | ||||
-rw-r--r-- | app/services/files/create_service.rb | 12 | ||||
-rw-r--r-- | app/services/files/delete_service.rb | 10 | ||||
-rw-r--r-- | app/services/files/multi_service.rb | 6 | ||||
-rw-r--r-- | app/services/files/update_service.rb | 8 | ||||
-rw-r--r-- | app/services/git_operation_service.rb | 168 | ||||
-rw-r--r-- | app/services/merge_requests/build_service.rb | 5 | ||||
-rw-r--r-- | app/services/validate_new_branch_service.rb | 22 |
12 files changed, 280 insertions, 62 deletions
diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 4d410f66c55..9b241aa8b04 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -29,9 +29,17 @@ module Commits tree_id = repository.public_send("check_#{action}_content", @commit, @target_branch) if tree_id - create_target_branch(into) if @create_merge_request + validate_target_branch(into) if @create_merge_request + + repository.public_send( + action, + current_user, + @commit, + into, + tree_id, + source_project: @source_project, + source_branch_name: @target_branch) - repository.public_send(action, current_user, @commit, into, tree_id) success else error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically. @@ -50,12 +58,12 @@ module Commits true end - def create_target_branch(new_branch) + def validate_target_branch(new_branch) # Temporary branch exists and contains the change commit - return success if repository.find_branch(new_branch) + return if repository.find_branch(new_branch) - result = CreateBranchService.new(@project, current_user) - .execute(new_branch, @target_branch, source_project: @source_project) + result = ValidateNewBranchService.new(@project, current_user). + execute(new_branch) if result[:status] == :error raise ChangeError, "There was an error creating the source branch: #{result[:message]}" diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 5e8fafca98c..31c371c4b34 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,23 +3,33 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - def execute(source_project, source_branch, target_project, target_branch, straight: false) - source_commit = source_project.commit(source_branch) - return unless source_commit + attr_reader :source_project, :source_branch_name - source_sha = source_commit.sha + def initialize(new_source_project, new_source_branch_name) + @source_project = new_source_project + @source_branch_name = new_source_branch_name + end + + def execute(target_project, target_branch, straight: false) + source_sha = source_project.repository. + commit(source_branch_name).try(:sha) + + return unless source_sha # If compare with other project we need to fetch ref first - unless target_project == source_project - random_string = SecureRandom.hex - - target_project.repository.fetch_ref( - source_project.repository.path_to_repo, - "refs/heads/#{source_branch}", - "refs/tmp/#{random_string}/head" - ) + if target_project == source_project + compare(source_sha, target_project, target_branch, straight) + else + target_project.repository.with_tmp_ref( + source_project.repository, source_branch_name) do + compare(source_sha, target_project, target_branch, straight) + end end + end + + private + def compare(source_sha, target_project, target_branch, straight) raw_compare = Gitlab::Git::Compare.new( target_project.repository.raw_repository, target_branch, diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index e004a303496..1b5e504573a 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -1,31 +1,11 @@ class CreateBranchService < BaseService - def execute(branch_name, ref, source_project: @project) - valid_branch = Gitlab::GitRefValidator.validate(branch_name) + def execute(branch_name, ref) + result = ValidateNewBranchService.new(project, current_user). + execute(branch_name) - unless valid_branch - return error('Branch name is invalid') - end - - repository = project.repository - existing_branch = repository.find_branch(branch_name) - - if existing_branch - return error('Branch already exists') - end - - new_branch = if source_project != @project - repository.fetch_ref( - source_project.repository.path_to_repo, - "refs/heads/#{ref}", - "refs/heads/#{branch_name}" - ) - - repository.after_create_branch + return result if result[:status] == :error - repository.find_branch(branch_name) - else - repository.add_branch(current_user, branch_name, ref) - end + new_branch = repository.add_branch(current_user, branch_name, ref) if new_branch success(new_branch) diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 9bd4bd464f7..80e1d1d60f2 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -5,7 +5,7 @@ module Files def execute @source_project = params[:source_project] || @project @source_branch = params[:source_branch] - @target_branch = params[:target_branch] + @target_branch = params[:target_branch] @commit_message = params[:commit_message] @file_path = params[:file_path] @@ -23,9 +23,7 @@ module Files validate # Create new branch if it different from source_branch - if different_branch? - create_target_branch - end + validate_target_branch if different_branch? result = commit if result @@ -61,22 +59,23 @@ module Files end unless project.empty_repo? - unless @source_project.repository.branch_names.include?(@source_branch) + unless @source_project.repository.branch_exists?(@source_branch) raise_error('You can only create or edit files when you are on a branch') end if different_branch? - if repository.branch_names.include?(@target_branch) + if repository.branch_exists?(@target_branch) raise_error('Branch with such name already exists. You need to switch to this branch in order to make changes') end end end end - def create_target_branch - result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project) + def validate_target_branch + result = ValidateNewBranchService.new(project, current_user). + execute(@target_branch) - unless result[:status] == :success + if result[:status] == :error raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}") end end diff --git a/app/services/files/create_dir_service.rb b/app/services/files/create_dir_service.rb index e5b4d60e467..ee4e130a38f 100644 --- a/app/services/files/create_dir_service.rb +++ b/app/services/files/create_dir_service.rb @@ -1,7 +1,15 @@ module Files class CreateDirService < Files::BaseService def commit - repository.commit_dir(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name) + repository.commit_dir( + current_user, + @file_path, + message: @commit_message, + branch_name: @target_branch, + author_email: @author_email, + author_name: @author_name, + source_project: @source_project, + source_branch_name: @source_branch) end def validate diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index b23576b9a28..853c471666d 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -1,7 +1,17 @@ module Files class CreateService < Files::BaseService def commit - repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, false, author_email: @author_email, author_name: @author_name) + repository.commit_file( + current_user, + @file_path, + @file_content, + message: @commit_message, + branch_name: @target_branch, + update: false, + author_email: @author_email, + author_name: @author_name, + source_project: @source_project, + source_branch_name: @source_branch) end def validate diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb index 4f7e7a5baaa..cfe532d49b3 100644 --- a/app/services/files/delete_service.rb +++ b/app/services/files/delete_service.rb @@ -1,7 +1,15 @@ module Files class DeleteService < Files::BaseService def commit - repository.remove_file(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name) + repository.remove_file( + current_user, + @file_path, + message: @commit_message, + branch_name: @target_branch, + author_email: @author_email, + author_name: @author_name, + source_project: @source_project, + source_branch_name: @source_branch) end end end diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb index 54446e90007..f77e5d91103 100644 --- a/app/services/files/multi_service.rb +++ b/app/services/files/multi_service.rb @@ -5,11 +5,13 @@ module Files def commit repository.multi_action( user: current_user, - branch: @target_branch, message: @commit_message, + branch_name: @target_branch, actions: params[:actions], author_email: @author_email, - author_name: @author_name + author_name: @author_name, + source_project: @source_project, + source_branch_name: @source_branch ) end diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index 47a18e3e132..5f671817cdb 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -4,11 +4,13 @@ module Files def commit repository.update_file(current_user, @file_path, @file_content, - branch: @target_branch, - previous_path: @previous_path, message: @commit_message, + branch_name: @target_branch, + previous_path: @previous_path, author_email: @author_email, - author_name: @author_name) + author_name: @author_name, + source_project: @source_project, + source_branch_name: @source_branch) end private diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb new file mode 100644 index 00000000000..0d1bd05e552 --- /dev/null +++ b/app/services/git_operation_service.rb @@ -0,0 +1,168 @@ + +class GitOperationService + attr_reader :user, :repository + + def initialize(new_user, new_repository) + @user = new_user + @repository = new_repository + end + + def add_branch(branch_name, newrev) + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name + oldrev = Gitlab::Git::BLANK_SHA + + update_ref_in_hooks(ref, newrev, oldrev) + end + + def rm_branch(branch) + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name + oldrev = branch.dereferenced_target.id + newrev = Gitlab::Git::BLANK_SHA + + update_ref_in_hooks(ref, newrev, oldrev) + end + + def add_tag(tag_name, newrev, options = {}) + ref = Gitlab::Git::TAG_REF_PREFIX + tag_name + oldrev = Gitlab::Git::BLANK_SHA + + with_hooks(ref, newrev, oldrev) do |service| + raw_tag = repository.rugged.tags.create(tag_name, newrev, options) + service.newrev = raw_tag.target_id + end + end + + # Whenever `source_branch_name` is passed, if `branch_name` doesn't exist, + # it would be created from `source_branch_name`. + # If `source_project` is passed, and the branch doesn't exist, + # it would try to find the source from it instead of current repository. + def with_branch( + branch_name, + source_branch_name: nil, + source_project: repository.project) + + check_with_branch_arguments!( + branch_name, source_branch_name, source_project) + + source_commit = source_project.repository.find_branch( + source_branch_name || branch_name).try(:dereferenced_target) + + update_branch_with_hooks(branch_name) do + if repository.project == source_project + yield(source_commit) + else + repository.with_tmp_ref( + source_project.repository, source_branch_name) do + yield(source_commit) + end + end + end + end + + private + + def update_branch_with_hooks(branch_name) + update_autocrlf_option + + was_empty = repository.empty? + + # Make commit + newrev = yield + + unless newrev + raise Repository::CommitError.new('Failed to create commit') + end + + branch = repository.find_branch(branch_name) + oldrev = if branch + # This could verify we're not losing commits + repository.rugged.merge_base(newrev, branch.target) + else + Gitlab::Git::BLANK_SHA + end + + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name + update_ref_in_hooks(ref, newrev, oldrev) + + # If repo was empty expire cache + repository.after_create if was_empty + repository.after_create_branch if was_empty || + Gitlab::Git.blank_ref?(oldrev) + + newrev + end + + def update_ref_in_hooks(ref, newrev, oldrev) + with_hooks(ref, newrev, oldrev) do + update_ref(ref, newrev, oldrev) + end + end + + def with_hooks(ref, newrev, oldrev) + result = nil + + GitHooksService.new.execute( + user, + repository.path_to_repo, + oldrev, + newrev, + ref) do |service| + + result = yield(service) if block_given? + end + + result + end + + def update_ref(ref, newrev, oldrev) + # We use 'git update-ref' because libgit2/rugged currently does not + # offer 'compare and swap' ref updates. Without compare-and-swap we can + # (and have!) accidentally reset the ref to an earlier state, clobbering + # commits. See also https://github.com/libgit2/libgit2/issues/1534. + command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z] + _, status = Gitlab::Popen.popen( + command, + repository.path_to_repo) do |stdin| + stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00") + end + + unless status.zero? + raise Repository::CommitError.new( + "Could not update branch #{Gitlab::Git.branch_name(ref)}." \ + " Please refresh and try again.") + end + end + + def update_autocrlf_option + if repository.raw_repository.autocrlf != :input + repository.raw_repository.autocrlf = :input + end + end + + def check_with_branch_arguments!( + branch_name, source_branch_name, source_project) + return if repository.branch_exists?(branch_name) + + if repository.project != source_project + unless source_branch_name + raise ArgumentError, + 'Should also pass :source_branch_name if' + + ' :source_project is different from current project' + end + + unless source_project.repository.branch_exists?(source_branch_name) + raise ArgumentError, + "Cannot find branch #{branch_name} nor" \ + " #{source_branch_name} from" \ + " #{source_project.path_with_namespace}" + end + elsif source_branch_name + unless repository.branch_exists?(source_branch_name) + raise ArgumentError, + "Cannot find branch #{branch_name} nor" \ + " #{source_branch_name} from" \ + " #{repository.project.path_with_namespace}" + end + end + end +end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 6a7393a9921..548c7b9baf4 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -16,9 +16,10 @@ module MergeRequests messages = validate_branches(merge_request) return build_failed(merge_request, messages) unless messages.empty? - compare = CompareService.new.execute( + compare = CompareService.new( merge_request.source_project, - merge_request.source_branch, + merge_request.source_branch + ).execute( merge_request.target_project, merge_request.target_branch, ) diff --git a/app/services/validate_new_branch_service.rb b/app/services/validate_new_branch_service.rb new file mode 100644 index 00000000000..2f61be184ce --- /dev/null +++ b/app/services/validate_new_branch_service.rb @@ -0,0 +1,22 @@ +require_relative 'base_service' + +class ValidateNewBranchService < BaseService + def execute(branch_name) + valid_branch = Gitlab::GitRefValidator.validate(branch_name) + + unless valid_branch + return error('Branch name is invalid') + end + + repository = project.repository + existing_branch = repository.find_branch(branch_name) + + if existing_branch + return error('Branch already exists') + end + + success + rescue GitHooksService::PreReceiveError => ex + error(ex.message) + end +end |