gitlab-org--gitlab-foss/lib/gitlab/gitaly_client/operation_service.rb
Markus Koller f8cecafb07
Add start_sha to commits API
When passing start_branch on committing from the WebIDE, it's possible
that the branch has changed since editing started, which results in the
change being applied on top of the latest commit in the branch and
overwriting the new changes.

By passing the start_sha instead we can make sure that the change is
applied on top of the commit which the user started editing from.
2019-07-18 09:19:18 +02:00

479 lines
17 KiB
Ruby

# 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 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_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref)
request = Gitaly::UserMergeToRefRequest.new(
repository: @gitaly_repo,
source_sha: source_sha,
branch: encode_binary(branch),
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)
)
response = GitalyClient.call(@repository.storage, :operation_service, :user_merge_to_ref, request)
if pre_receive_error = response.pre_receive_error.presence
raise Gitlab::Git::PreReceiveError, pre_receive_error
end
response.commit_id
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
# DEPRECATED: https://gitlab.com/gitlab-org/gitaly/issues/1628
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 rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
request_enum = QueueEnumerator.new
rebase_sha = nil
response_enum = GitalyClient.call(
@repository.storage,
:operation_service,
:user_rebase_confirmable,
request_enum.each,
remote_storage: remote_repository.storage
)
# First request
request_enum.push(
Gitaly::UserRebaseConfirmableRequest.new(
header: Gitaly::UserRebaseConfirmableRequest::Header.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)
)
)
)
perform_next_gitaly_rebase_request(response_enum) do |response|
rebase_sha = response.rebase_sha
end
yield rebase_sha
# Second request confirms with gitaly to finalize the rebase
request_enum.push(Gitaly::UserRebaseConfirmableRequest.new(apply: true))
perform_next_gitaly_rebase_request(response_enum)
rebase_sha
ensure
request_enum.close
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
# rubocop:disable Metrics/ParameterLists
def user_commit_files(
user, branch_name, commit_message, actions, author_email, author_name,
start_branch_name, start_repository, force = false, start_sha = nil)
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, force, start_sha)
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_io(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
# rubocop:enable Metrics/ParameterLists
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_io(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 perform_next_gitaly_rebase_request(response_enum)
response = response_enum.next
if response.pre_receive_error.present?
raise Gitlab::Git::PreReceiveError, response.pre_receive_error
elsif response.git_error.present?
raise Gitlab::Git::Repository::GitError, response.git_error
end
yield response if block_given?
response
end
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
# rubocop:disable Metrics/ParameterLists
def user_commit_files_request_header(
user, branch_name, commit_message, actions, author_email, author_name,
start_branch_name, start_repository, force, start_sha)
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,
force: force,
start_sha: encode_binary(start_sha)
)
end
# rubocop:enable Metrics/ParameterLists
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],
infer_content: !!action[:infer_content]
)
rescue RangeError
raise ArgumentError, "Unknown action '#{action[:action]}'"
end
end
end
end