# frozen_string_literal: true module Gitlab module GitalyClient class OperationService include Gitlab::EncodingHelper MAX_MSG_SIZE = 128.kilobytes.freeze def initialize(repository) @gitaly_repo = repository.gitaly_repository @repository = repository end def rm_tag(tag_name, user) request = Gitaly::UserDeleteTagRequest.new( repository: @gitaly_repo, tag_name: encode_binary(tag_name), user: Gitlab::Git::User.from_gitlab(user).to_gitaly ) response = GitalyClient.call(@repository.storage, :operation_service, :user_delete_tag, request, timeout: GitalyClient.medium_timeout) if pre_receive_error = response.pre_receive_error.presence raise Gitlab::Git::PreReceiveError, pre_receive_error end end def add_tag(tag_name, user, target, message) request = Gitaly::UserCreateTagRequest.new( repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, tag_name: encode_binary(tag_name), target_revision: encode_binary(target), message: encode_binary(message.to_s) ) response = GitalyClient.call(@repository.storage, :operation_service, :user_create_tag, request, timeout: GitalyClient.medium_timeout) if pre_receive_error = response.pre_receive_error.presence raise Gitlab::Git::PreReceiveError, pre_receive_error elsif response.exists raise Gitlab::Git::Repository::TagExistsError end Gitlab::Git::Tag.new(@repository, response.tag) rescue GRPC::FailedPrecondition => e raise Gitlab::Git::Repository::InvalidRef, e end def user_create_branch(branch_name, user, start_point) request = Gitaly::UserCreateBranchRequest.new( repository: @gitaly_repo, branch_name: encode_binary(branch_name), user: Gitlab::Git::User.from_gitlab(user).to_gitaly, start_point: encode_binary(start_point) ) response = GitalyClient.call(@repository.storage, :operation_service, :user_create_branch, request) if response.pre_receive_error.present? raise Gitlab::Git::PreReceiveError.new(response.pre_receive_error) end branch = response.branch return nil unless branch target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target_commit) Gitlab::Git::Branch.new(@repository, branch.name, target_commit.id, target_commit) rescue GRPC::FailedPrecondition => ex raise Gitlab::Git::Repository::InvalidRef, ex end def user_update_branch(branch_name, user, newrev, oldrev) request = Gitaly::UserUpdateBranchRequest.new( repository: @gitaly_repo, branch_name: encode_binary(branch_name), user: Gitlab::Git::User.from_gitlab(user).to_gitaly, newrev: encode_binary(newrev), oldrev: encode_binary(oldrev) ) response = GitalyClient.call(@repository.storage, :operation_service, :user_update_branch, request) if pre_receive_error = response.pre_receive_error.presence raise Gitlab::Git::PreReceiveError, pre_receive_error end end def user_delete_branch(branch_name, user) request = Gitaly::UserDeleteBranchRequest.new( repository: @gitaly_repo, branch_name: encode_binary(branch_name), user: Gitlab::Git::User.from_gitlab(user).to_gitaly ) response = GitalyClient.call(@repository.storage, :operation_service, :user_delete_branch, request) if pre_receive_error = response.pre_receive_error.presence raise Gitlab::Git::PreReceiveError, pre_receive_error end end def user_merge_branch(user, source_sha, target_branch, message) request_enum = QueueEnumerator.new response_enum = GitalyClient.call( @repository.storage, :operation_service, :user_merge_branch, request_enum.each ) request_enum.push( Gitaly::UserMergeBranchRequest.new( repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, commit_id: source_sha, branch: encode_binary(target_branch), message: encode_binary(message) ) ) yield response_enum.next.commit_id request_enum.push(Gitaly::UserMergeBranchRequest.new(apply: true)) second_response = response_enum.next if second_response.pre_receive_error.present? raise Gitlab::Git::PreReceiveError, second_response.pre_receive_error end branch_update = second_response.branch_update return if branch_update.nil? raise Gitlab::Git::CommitError.new('failed to apply merge to branch') unless branch_update.commit_id.present? Gitlab::Git::OperationService::BranchUpdate.from_gitaly(branch_update) ensure request_enum.close end def user_ff_branch(user, source_sha, target_branch) request = Gitaly::UserFFBranchRequest.new( repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, commit_id: source_sha, branch: encode_binary(target_branch) ) response = GitalyClient.call( @repository.storage, :operation_service, :user_ff_branch, request ) Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update) rescue GRPC::FailedPrecondition => e raise Gitlab::Git::CommitError, e end def user_cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) call_cherry_pick_or_revert(:cherry_pick, user: user, commit: commit, branch_name: branch_name, message: message, start_branch_name: start_branch_name, start_repository: start_repository) end def user_revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) call_cherry_pick_or_revert(:revert, user: user, commit: commit, branch_name: branch_name, message: message, start_branch_name: start_branch_name, start_repository: start_repository) end def user_rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:) request = Gitaly::UserRebaseRequest.new( repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, rebase_id: rebase_id.to_s, branch: encode_binary(branch), branch_sha: branch_sha, remote_repository: remote_repository.gitaly_repository, remote_branch: encode_binary(remote_branch) ) response = GitalyClient.call( @repository.storage, :operation_service, :user_rebase, request, remote_storage: remote_repository.storage ) if response.pre_receive_error.presence raise Gitlab::Git::PreReceiveError, response.pre_receive_error elsif response.git_error.presence raise Gitlab::Git::Repository::GitError, response.git_error else response.rebase_sha end end def user_squash(user, squash_id, branch, start_sha, end_sha, author, message) request = Gitaly::UserSquashRequest.new( repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, squash_id: squash_id.to_s, branch: encode_binary(branch), start_sha: start_sha, end_sha: end_sha, author: Gitlab::Git::User.from_gitlab(author).to_gitaly, commit_message: encode_binary(message) ) response = GitalyClient.call( @repository.storage, :operation_service, :user_squash, request ) if response.git_error.presence raise Gitlab::Git::Repository::GitError, response.git_error end response.squash_sha end def user_update_submodule(user:, submodule:, commit_sha:, branch:, message:) request = Gitaly::UserUpdateSubmoduleRequest.new( repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, commit_sha: commit_sha, branch: encode_binary(branch), submodule: encode_binary(submodule), commit_message: encode_binary(message) ) response = GitalyClient.call( @repository.storage, :operation_service, :user_update_submodule, request ) if response.pre_receive_error.present? raise Gitlab::Git::PreReceiveError, response.pre_receive_error elsif response.commit_error.present? raise Gitlab::Git::CommitError, response.commit_error else Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update) end end def user_commit_files( user, branch_name, commit_message, actions, author_email, author_name, start_branch_name, start_repository) req_enum = Enumerator.new do |y| header = user_commit_files_request_header(user, branch_name, commit_message, actions, author_email, author_name, start_branch_name, start_repository) y.yield Gitaly::UserCommitFilesRequest.new(header: header) actions.each do |action| action_header = user_commit_files_action_header(action) y.yield Gitaly::UserCommitFilesRequest.new( action: Gitaly::UserCommitFilesAction.new(header: action_header) ) reader = binary_stringio(action[:content]) until reader.eof? chunk = reader.read(MAX_MSG_SIZE) y.yield Gitaly::UserCommitFilesRequest.new( action: Gitaly::UserCommitFilesAction.new(content: chunk) ) end end end response = GitalyClient.call(@repository.storage, :operation_service, :user_commit_files, req_enum, remote_storage: start_repository.storage) if (pre_receive_error = response.pre_receive_error.presence) raise Gitlab::Git::PreReceiveError, pre_receive_error end if (index_error = response.index_error.presence) raise Gitlab::Git::Index::IndexError, index_error end Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update) end def user_commit_patches(user, branch_name, patches) header = Gitaly::UserApplyPatchRequest::Header.new( repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, target_branch: encode_binary(branch_name) ) reader = binary_stringio(patches) chunks = Enumerator.new do |chunk| chunk.yield Gitaly::UserApplyPatchRequest.new(header: header) until reader.eof? patch_chunk = reader.read(MAX_MSG_SIZE) chunk.yield(Gitaly::UserApplyPatchRequest.new(patches: patch_chunk)) end end response = GitalyClient.call(@repository.storage, :operation_service, :user_apply_patch, chunks) Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update) end private def call_cherry_pick_or_revert(rpc, user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) request_class = "Gitaly::User#{rpc.to_s.camelcase}Request".constantize request = request_class.new( repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, commit: commit.to_gitaly_commit, branch_name: encode_binary(branch_name), message: encode_binary(message), start_branch_name: encode_binary(start_branch_name.to_s), start_repository: start_repository.gitaly_repository ) response = GitalyClient.call( @repository.storage, :operation_service, :"user_#{rpc}", request, remote_storage: start_repository.storage, timeout: GitalyClient.medium_timeout ) handle_cherry_pick_or_revert_response(response) end def handle_cherry_pick_or_revert_response(response) if response.pre_receive_error.presence raise Gitlab::Git::PreReceiveError, response.pre_receive_error elsif response.commit_error.presence raise Gitlab::Git::CommitError, response.commit_error elsif response.create_tree_error.presence raise Gitlab::Git::Repository::CreateTreeError, response.create_tree_error end Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update) end def user_commit_files_request_header( user, branch_name, commit_message, actions, author_email, author_name, start_branch_name, start_repository) Gitaly::UserCommitFilesRequestHeader.new( repository: @gitaly_repo, user: Gitlab::Git::User.from_gitlab(user).to_gitaly, branch_name: encode_binary(branch_name), commit_message: encode_binary(commit_message), commit_author_name: encode_binary(author_name), commit_author_email: encode_binary(author_email), start_branch_name: encode_binary(start_branch_name), start_repository: start_repository.gitaly_repository ) end def user_commit_files_action_header(action) Gitaly::UserCommitFilesActionHeader.new( action: action[:action].upcase.to_sym, file_path: encode_binary(action[:file_path]), previous_path: encode_binary(action[:previous_path]), base64_content: action[:encoding] == 'base64', execute_filemode: !!action[:execute_filemode] ) rescue RangeError raise ArgumentError, "Unknown action '#{action[:action]}'" end end end end