diff --git a/ruby/lib/gitaly_server/operations_service.rb b/ruby/lib/gitaly_server/operations_service.rb index 7b5555cc1ebd0e41ed4043517c4c86f82ad7f4a2..0d465fdcb83a99feb6ae535d9cecbc0c40e701ef 100644 --- a/ruby/lib/gitaly_server/operations_service.rb +++ b/ruby/lib/gitaly_server/operations_service.rb @@ -9,12 +9,13 @@ module GitalyServer gitaly_user = get_param!(request, :user) user = Gitlab::Git::User.from_gitaly(gitaly_user) + user_repo = Gitlab::Git::UserRepository.new(repo, user) tag_name = get_param!(request, :tag_name) target_revision = get_param!(request, :target_revision) - created_tag = repo.add_tag(tag_name, user: user, target: target_revision, message: request.message.presence) + created_tag = user_repo.add_tag(tag_name, target: target_revision, message: request.message.presence) return Gitaly::UserCreateTagResponse.new unless created_tag rugged_commit = created_tag.dereferenced_target.rugged_commit @@ -42,7 +43,7 @@ module GitalyServer tag_name = get_param!(request, :tag_name) - repo.rm_tag(tag_name, user: user) + Gitlab::Git::UserRepository.new(repo, user).rm_tag(tag_name) Gitaly::UserDeleteTagResponse.new rescue Gitlab::Git::PreReceiveError => e @@ -60,9 +61,11 @@ module GitalyServer target = get_param!(request, :start_point) gitaly_user = get_param!(request, :user) - branch_name = request.branch_name user = Gitlab::Git::User.from_gitaly(gitaly_user) - created_branch = repo.add_branch(branch_name, user: user, target: target) + user_repo = Gitlab::Git::UserRepository.new(repo, user) + + branch_name = request.branch_name + created_branch = user_repo.add_branch(branch_name, target: target) return Gitaly::UserCreateBranchResponse.new unless created_branch rugged_commit = created_branch.dereferenced_target.rugged_commit @@ -85,9 +88,9 @@ module GitalyServer newrev = get_param!(request, :newrev) oldrev = get_param!(request, :oldrev) gitaly_user = get_param!(request, :user) - user = Gitlab::Git::User.from_gitaly(gitaly_user) - repo.update_branch(branch_name, user: user, newrev: newrev, oldrev: oldrev) + user_repo = Gitlab::Git::UserRepository.new(repo, user) + user_repo.update_branch(branch_name, newrev: newrev, oldrev: oldrev) Gitaly::UserUpdateBranchResponse.new rescue Gitlab::Git::Repository::InvalidRef, Gitlab::Git::CommitError => ex @@ -104,7 +107,7 @@ module GitalyServer repo = Gitlab::Git::Repository.from_gitaly(request.repository, call) user = Gitlab::Git::User.from_gitaly(request.user) - repo.rm_branch(request.branch_name, user: user) + Gitlab::Git::UserRepository.new(repo, user).rm_branch(request.branch_name) Gitaly::UserDeleteBranchResponse.new rescue Gitlab::Git::PreReceiveError => e @@ -122,12 +125,14 @@ module GitalyServer repository = Gitlab::Git::Repository.from_gitaly(first_request.repository, call) user = Gitlab::Git::User.from_gitaly(first_request.user) + user_repo = Gitlab::Git::UserRepository.new(repository, user) + source_sha = first_request.commit_id.dup target_branch = first_request.branch.dup message = first_request.message.dup begin - result = repository.merge(user, source_sha, target_branch, message) do |commit_id| + result = user_repo.merge(source_sha, target_branch, message) do |commit_id| y << Gitaly::UserMergeBranchResponse.new(commit_id: commit_id) second_request = session.next @@ -147,8 +152,8 @@ module GitalyServer begin repo = Gitlab::Git::Repository.from_gitaly(request.repository, call) user = Gitlab::Git::User.from_gitaly(request.user) - - result = repo.ff_merge(user, request.commit_id, request.branch) + user_repo = Gitlab::Git::UserRepository.new(repo, user) + result = user_repo.ff_merge(request.commit_id, request.branch) branch_update = branch_update_result(result) Gitaly::UserFFBranchResponse.new(branch_update: branch_update) @@ -167,11 +172,11 @@ module GitalyServer begin repo = Gitlab::Git::Repository.from_gitaly(request.repository, call) user = Gitlab::Git::User.from_gitaly(request.user) + user_repo = Gitlab::Git::UserRepository.new(repo, user) commit = Gitlab::Git::Commit.new(repo, request.commit) start_repository = Gitlab::Git::GitalyRemoteRepository.new(request.start_repository || request.repository, call) - result = repo.cherry_pick( - user: user, + result = user_repo.cherry_pick( commit: commit, branch_name: request.branch_name, message: request.message.dup, @@ -199,8 +204,7 @@ module GitalyServer commit = Gitlab::Git::Commit.new(repo, request.commit) start_repository = Gitlab::Git::GitalyRemoteRepository.new(request.start_repository || request.repository, call) - result = repo.revert( - user: user, + result = Gitlab::Git::UserRepository.new(repo, user).revert( commit: commit, branch_name: request.branch_name, message: request.message.dup, @@ -225,12 +229,13 @@ module GitalyServer begin repo = Gitlab::Git::Repository.from_gitaly(request.repository, call) user = Gitlab::Git::User.from_gitaly(request.user) + user_repo = Gitlab::Git::UserRepository.new(repo, user) remote_repository = Gitlab::Git::GitalyRemoteRepository.new(request.remote_repository, call) - rebase_sha = repo.rebase(user, request.rebase_id, - branch: request.branch, - branch_sha: request.branch_sha, - remote_repository: remote_repository, - remote_branch: request.remote_branch) + rebase_sha = user_repo.rebase(request.rebase_id, + branch: request.branch, + branch_sha: request.branch_sha, + remote_repository: remote_repository, + remote_branch: request.remote_branch) Gitaly::UserRebaseResponse.new(rebase_sha: rebase_sha) rescue Gitlab::Git::PreReceiveError => e @@ -262,9 +267,10 @@ module GitalyServer repo = Gitlab::Git::Repository.from_gitaly(header.repository, call) user = Gitlab::Git::User.from_gitaly(header.user) + user_repo = Gitlab::Git::UserRepository.new(repo, user) opts = commit_files_opts(call, header, actions) - branch_update = branch_update_result(repo.multi_action(user, opts)) + branch_update = branch_update_result(user_repo.multi_action(opts)) Gitaly::UserCommitFilesResponse.new(branch_update: branch_update) rescue Gitlab::Git::Index::IndexError => e @@ -282,14 +288,15 @@ module GitalyServer repo = Gitlab::Git::Repository.from_gitaly(request.repository, call) user = Gitlab::Git::User.from_gitaly(request.user) author = Gitlab::Git::User.from_gitaly(request.author) + user_repo = Gitlab::Git::UserRepository.new(repo, user) begin - squash_sha = repo.squash(user, request.squash_id, - branch: request.branch, - start_sha: request.start_sha, - end_sha: request.end_sha, - author: author, - message: request.commit_message) + squash_sha = user_repo.squash(request.squash_id, + branch: request.branch, + start_sha: request.start_sha, + end_sha: request.end_sha, + author: author, + message: request.commit_message) Gitaly::UserSquashResponse.new(squash_sha: squash_sha) rescue Gitlab::Git::Repository::GitError => e diff --git a/ruby/lib/gitaly_server/repository_service.rb b/ruby/lib/gitaly_server/repository_service.rb index 4790b600b87ae6b4eb42d9b6995a2564ad9452c5..603a076bbcf31e2f5a7dd7ba236e268cc26c71e6 100644 --- a/ruby/lib/gitaly_server/repository_service.rb +++ b/ruby/lib/gitaly_server/repository_service.rb @@ -24,20 +24,6 @@ module GitalyServer end end - # TODO: Can be removed once https://gitlab.com/gitlab-org/gitaly/merge_requests/738 - # is well and truly out in the wild. - def fsck(request, call) - repo = Gitlab::Git::Repository.from_gitaly(request.repository, call) - - repo.fsck - - Gitaly::FsckResponse.new - rescue Gitlab::Git::Repository::GitError => ex - Gitaly::FsckResponse.new(error: ex.message.b) - rescue Rugged::RepositoryError => ex - Gitaly::FsckResponse.new(error: ex.message.b) - end - def fetch_remote(request, call) bridge_exceptions do gitlab_projects = Gitlab::Git::GitlabProjects.from_gitaly(request.repository, call) diff --git a/ruby/lib/gitlab/git/conflict/resolver.rb b/ruby/lib/gitlab/git/conflict/resolver.rb index a2621ac933d79a47ff5f3d956913508b09ec83e4..a784a92568b373d36dd45a96d763fb7bdbdd0eb1 100644 --- a/ruby/lib/gitlab/git/conflict/resolver.rb +++ b/ruby/lib/gitlab/git/conflict/resolver.rb @@ -89,7 +89,8 @@ module Gitlab parents: [@our_commit_oid, @their_commit_oid] } - source_repository.commit_index(resolution.user, source_branch, index, commit_params) + UserRepository.new(source_repository, resolution.user) + .commit_index(source_branch, index, commit_params) end end end diff --git a/ruby/lib/gitlab/git/repository.rb b/ruby/lib/gitlab/git/repository.rb index f3e6505c96c6fd310cfba02bdf0bbb6e7d6126e1..2afd0f35a2682aa2a91a8b52fb8235484c37a10a 100644 --- a/ruby/lib/gitlab/git/repository.rb +++ b/ruby/lib/gitlab/git/repository.rb @@ -15,8 +15,6 @@ module Gitlab GIT_OBJECT_DIRECTORY_RELATIVE GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE ].freeze - SEARCH_CONTEXT_LINES = 3 - REV_LIST_COMMIT_LIMIT = 2_000 # In https://gitlab.com/gitlab-org/gitaly/merge_requests/698 # We copied these two prefixes into gitaly-go, so don't change these # or things will break! (REBASE_WORKTREE_PREFIX and SQUASH_WORKTREE_PREFIX) @@ -24,19 +22,15 @@ module Gitlab SQUASH_WORKTREE_PREFIX = 'squash'.freeze GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'.freeze GITLAB_PROJECTS_TIMEOUT = Gitlab.config.gitlab_shell.git_timeout - EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000'.freeze AUTOCRLF_VALUES = { 'true' => true, 'false' => false, 'input' => :input }.freeze RUGGED_KEY = :rugged_list NoRepository = Class.new(StandardError) - InvalidRepository = Class.new(StandardError) - InvalidBlobName = Class.new(StandardError) InvalidRef = Class.new(StandardError) GitError = Class.new(StandardError) DeleteBranchError = Class.new(StandardError) CreateTreeError = Class.new(StandardError) TagExistsError = Class.new(StandardError) - ChecksumError = Class.new(StandardError) class << self def from_gitaly(gitaly_repository, call) @@ -121,16 +115,6 @@ module Gitlab [storage, relative_path] == [other.storage, other.relative_path] end - def add_branch(branch_name, user:, target:) - target_object = Ref.dereference_object(lookup(target)) - raise InvalidRef, "target not found: #{target}" unless target_object - - OperationService.new(user, self).add_branch(branch_name, target_object.oid) - find_branch(branch_name) - rescue Rugged::ReferenceError => ex - raise InvalidRef, ex - end - attr_reader :gitaly_repository attr_reader :alternate_object_directories @@ -152,13 +136,6 @@ module Gitlab end end - # TODO: Can be removed once https://gitlab.com/gitlab-org/gitaly/merge_requests/738 - # is well and truly out in the wild. - def fsck - msg, status = run_git(%W[--git-dir=#{path} fsck], nice: true) - raise GitError, "Could not fsck repository: #{msg}" unless status.zero? - end - def exists? File.exist?(File.join(path, 'refs')) end @@ -208,10 +185,6 @@ module Gitlab end end - def tag_names - rugged.tags.map(&:name) - end - def tags rugged.references.each("refs/tags/*").map do |ref| message = nil @@ -321,190 +294,14 @@ module Gitlab [old_mode, new_mode, blob_id, rest] end - def add_tag(tag_name, user:, target:, message: nil) - target_object = Ref.dereference_object(lookup(target)) - raise InvalidRef, "target not found: #{target}" unless target_object - - user = Gitlab::Git::User.from_gitlab(user) unless user.respond_to?(:gl_id) - - options = nil # Use nil, not the empty hash. Rugged cares about this. - if message - options = { - message: message, - tagger: Gitlab::Git.committer_hash(email: user.email, name: user.name) - } - end - - Gitlab::Git::OperationService.new(user, self).add_tag(tag_name, target_object.oid, options) - - find_tag(tag_name) - rescue Rugged::ReferenceError => ex - raise InvalidRef, ex - rescue Rugged::TagError - raise TagExistsError - end - - def update_branch(branch_name, user:, newrev:, oldrev:) - OperationService.new(user, self).update_branch(branch_name, newrev, oldrev) - end - - def rm_branch(branch_name, user:) - branch = find_branch(branch_name) - - raise InvalidRef, "branch not found: #{branch_name}" unless branch - - OperationService.new(user, self).rm_branch(branch) - end - - def rm_tag(tag_name, user:) - tag = find_tag(tag_name) - - raise InvalidRef, "tag not found: #{tag_name}" unless tag - - Gitlab::Git::OperationService.new(user, self).rm_tag(tag) - end - def find_tag(name) tags.find { |tag| tag.name == name } end - def merge(user, source_sha, target_branch, message) - committer = Gitlab::Git.committer_hash(email: user.email, name: user.name) - - OperationService.new(user, self).with_branch(target_branch) do |start_commit| - our_commit = start_commit.sha - their_commit = source_sha - - raise 'Invalid merge target' unless our_commit - raise 'Invalid merge source' unless their_commit - - merge_index = rugged.merge_commits(our_commit, their_commit) - break if merge_index.conflicts? - - options = { - parents: [our_commit, their_commit], - tree: merge_index.write_tree(rugged), - message: message, - author: committer, - committer: committer - } - - commit_id = create_commit(options) - - yield commit_id - - commit_id - end - rescue Gitlab::Git::CommitError # when merge_index.conflicts? - nil - end - - def ff_merge(user, source_sha, target_branch) - OperationService.new(user, self).with_branch(target_branch) do |our_commit| - raise ArgumentError, 'Invalid merge target' unless our_commit - - source_sha - end - rescue Rugged::ReferenceError, InvalidRef - raise ArgumentError, 'Invalid merge source' - end - - def revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) - OperationService.new(user, self).with_branch( - branch_name, - start_branch_name: start_branch_name, - start_repository: start_repository - ) do |start_commit| - - Gitlab::Git.check_namespace!(commit, start_repository) - - revert_tree_id = check_revert_content(commit, start_commit.sha) - raise CreateTreeError unless revert_tree_id - - committer = user_to_committer(user) - - create_commit(message: message, - author: committer, - committer: committer, - tree: revert_tree_id, - parents: [start_commit.sha]) - end - end - - def cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) - args = { - user: user, - commit: commit, - branch_name: branch_name, - message: message, - start_branch_name: start_branch_name, - start_repository: start_repository - } - - rugged_cherry_pick(args) - end - def diff_exists?(sha1, sha2) rugged.diff(sha1, sha2).size.positive? end - def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:) - rebase_path = worktree_path(REBASE_WORKTREE_PREFIX, rebase_id) - env = git_env_for_user(user) - - if remote_repository.is_a?(RemoteRepository) - env.merge!(remote_repository.fetch_env) - remote_repo_path = GITALY_INTERNAL_URL - else - remote_repo_path = remote_repository.path - end - - with_worktree(rebase_path, branch, env: env) do - run_git!( - %W[pull --rebase #{remote_repo_path} #{remote_branch}], - chdir: rebase_path, env: env - ) - - rebase_sha = run_git!(%w[rev-parse HEAD], chdir: rebase_path, env: env).strip - - update_branch(branch, user: user, newrev: rebase_sha, oldrev: branch_sha) - - rebase_sha - end - end - - def squash(user, squash_id, branch:, start_sha:, end_sha:, author:, message:) - squash_path = worktree_path(SQUASH_WORKTREE_PREFIX, squash_id) - env = git_env_for_user(user).merge( - 'GIT_AUTHOR_NAME' => author.name, - 'GIT_AUTHOR_EMAIL' => author.email - ) - diff_range = "#{start_sha}...#{end_sha}" - diff_files = run_git!( - %W[diff --name-only --diff-filter=ar --binary #{diff_range}] - ).chomp - - with_worktree(squash_path, branch, sparse_checkout_files: diff_files, env: env) do - # Apply diff of the `diff_range` to the worktree - diff = run_git!(%W[diff --binary #{diff_range}]) - run_git!(%w[apply --index --whitespace=nowarn], chdir: squash_path, env: env) do |stdin| - stdin.binmode - stdin.write(diff) - end - - # Commit the `diff_range` diff - run_git!(%W[commit --no-verify --message #{message}], chdir: squash_path, env: env) - - # Return the squash sha. May print a warning for ambiguous refs, but - # we can ignore that with `--quiet` and just take the SHA, if present. - # HEAD here always refers to the current HEAD commit, even if there is - # another ref called HEAD. - run_git!( - %w[rev-parse --quiet --verify HEAD], chdir: squash_path, env: env - ).chomp - end - end - def push_remote_branches(remote_name, branch_names, forced: true) success = @gitlab_projects.push_branches(remote_name, GITLAB_PROJECTS_TIMEOUT, forced, branch_names) @@ -517,42 +314,6 @@ module Gitlab success || gitlab_projects_error end - def multi_action( - user, branch_name:, message:, actions:, - author_email: nil, author_name: nil, - start_branch_name: nil, start_repository: self - ) - - OperationService.new(user, self).with_branch( - branch_name, - start_branch_name: start_branch_name, - start_repository: start_repository - ) do |start_commit| - - index = Gitlab::Git::Index.new(self) - parents = [] - - if start_commit - index.read_tree(start_commit.rugged_commit.tree) - parents = [start_commit.sha] - end - - actions.each { |opts| index.apply(opts.delete(:action), opts) } - - committer = user_to_committer(user) - author = Gitlab::Git.committer_hash(email: author_email, name: author_name) || committer - options = { - tree: index.write_tree, - message: message, - parents: parents, - author: author, - committer: committer - } - - create_commit(options) - end - end - def raw_log(options) sha = unless options[:all] @@ -674,10 +435,6 @@ module Gitlab nil end - def user_to_committer(user) - Gitlab::Git.committer_hash(email: user.email, name: user.name) - end - def write_ref(ref_path, ref, old_ref: nil, shell: true) if shell shell_write_ref(ref_path, ref, old_ref) @@ -702,20 +459,6 @@ module Gitlab rugged.rev_parse(oid_or_ref_name) end - def commit_index(user, branch_name, index, options) - committer = user_to_committer(user) - - OperationService.new(user, self).with_branch(branch_name) do - commit_params = options.merge( - tree: index.write_tree(rugged), - author: committer, - committer: committer - ) - - create_commit(commit_params) - end - end - # Return the object that +revspec+ points to. If +revspec+ is an # annotated tag, then return the tag's target instead. def rev_parse_target(revspec) @@ -812,8 +555,6 @@ module Gitlab rugged&.close if defined?(@rugged) end - private - def run_git(args, chdir: path, env: {}, nice: false, lazy_block: nil, &block) cmd = [Gitlab.config.git.bin_path, *args] cmd.unshift("nice") if nice @@ -832,20 +573,6 @@ module Gitlab output end - def run_git_with_timeout(args, timeout, env: {}) - popen_with_timeout([Gitlab.config.git.bin_path, *args], timeout, path, env) - end - - def git_env_for_user(user) - { - 'GIT_COMMITTER_NAME' => user.name, - 'GIT_COMMITTER_EMAIL' => user.email, - 'GL_ID' => Gitlab::GlId.gl_id(user), - 'GL_PROTOCOL' => Gitlab::Git::Hook::GL_PROTOCOL, - 'GL_REPOSITORY' => gl_repository - } - end - def check_revert_content(target_commit, source_sha) args = [target_commit.sha, source_sha] args << { mainline: 1 } if target_commit.merge_commit? @@ -946,32 +673,6 @@ module Gitlab raise GitError, "Could not delete refs #{ref_names}: #{message}" unless status.zero? end - def rugged_cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) - OperationService.new(user, self).with_branch( - branch_name, - start_branch_name: start_branch_name, - start_repository: start_repository - ) do |start_commit| - - Gitlab::Git.check_namespace!(commit, start_repository) - - cherry_pick_tree_id = check_cherry_pick_content(commit, start_commit.sha) - raise CreateTreeError unless cherry_pick_tree_id - - committer = user_to_committer(user) - - create_commit(message: message, - author: { - email: commit.author_email, - name: commit.author_name, - time: commit.authored_date - }, - committer: committer, - tree: cherry_pick_tree_id, - parents: [start_commit.sha]) - end - end - def check_cherry_pick_content(target_commit, source_sha) args = [target_commit.sha, source_sha] args << 1 if target_commit.merge_commit? diff --git a/ruby/lib/gitlab/git/user_repository.rb b/ruby/lib/gitlab/git/user_repository.rb new file mode 100644 index 0000000000000000000000000000000000000000..b99f30ee5927644b0c6ca5977720776c7ea930f6 --- /dev/null +++ b/ruby/lib/gitlab/git/user_repository.rb @@ -0,0 +1,280 @@ +module Gitlab + module Git + class UserRepository + delegate :run_git, :run_git!, :with_worktree, :lookup, :find_branch, + :worktree_path, :find_tag, :create_commit, :check_revert_content, + :check_cherry_pick_content, + to: :repository + + def initialize(repository, user) + @repository = repository + @user = user + end + + def add_branch(branch_name, target:) + target_object = Ref.dereference_object(lookup(target)) + raise Gitlab::Git::Repository::InvalidRef, "target not found: #{target}" unless target_object + + operation_service.add_branch(branch_name, target_object.oid) + find_branch(branch_name) + rescue Rugged::ReferenceError => ex + raise Gitlab::Git::Repository::InvalidRef, ex + end + + def add_tag(tag_name, target:, message: nil) + target_object = Ref.dereference_object(lookup(target)) + raise Gitlab::Git::Repository::InvalidRef, "target not found: #{target}" unless target_object + + options = nil # Use nil, not the empty hash. Rugged cares about this. + if message + options = { + message: message, + tagger: Gitlab::Git.committer_hash(email: user.email, name: user.name) + } + end + + operation_service.add_tag(tag_name, target_object.oid, options) + + find_tag(tag_name) + rescue Rugged::ReferenceError => ex + raise Gitlab::Git::Repository::InvalidRef, ex + rescue Rugged::TagError + raise Gitlab::Git::Repository::TagExistsError + end + + def update_branch(branch_name, newrev:, oldrev:) + operation_service.update_branch(branch_name, newrev, oldrev) + end + + def rm_branch(branch_name) + branch = find_branch(branch_name) + + raise Gitlab::Git::Repository::InvalidRef, "branch not found: #{branch_name}" unless branch + + operation_service.rm_branch(branch) + end + + def rm_tag(tag_name) + tag = find_tag(tag_name) + + raise Gitlab::Git::Repository::InvalidRef, "tag not found: #{tag_name}" unless tag + + operation_service.rm_tag(tag) + end + + def rebase(rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:) + rebase_path = worktree_path(Gitlab::Git::Repository::REBASE_WORKTREE_PREFIX, rebase_id) + env = git_env + + if remote_repository.is_a?(RemoteRepository) + env.merge!(remote_repository.fetch_env) + remote_repo_path = Gitlab::Git::Repository::GITALY_INTERNAL_URL + else + remote_repo_path = remote_repository.path + end + + with_worktree(rebase_path, branch, env: env) do + run_git!( + %W[pull --rebase #{remote_repo_path} #{remote_branch}], + chdir: rebase_path, env: env + ) + + rebase_sha = run_git!(%w[rev-parse HEAD], chdir: rebase_path, env: env).strip + + update_branch(branch, newrev: rebase_sha, oldrev: branch_sha) + + rebase_sha + end + end + + def squash(squash_id, branch:, start_sha:, end_sha:, author:, message:) + squash_path = repository.worktree_path(Gitlab::Git::Repository::SQUASH_WORKTREE_PREFIX, squash_id) + env = git_env.merge( + 'GIT_AUTHOR_NAME' => author.name, + 'GIT_AUTHOR_EMAIL' => author.email + ) + diff_range = "#{start_sha}...#{end_sha}" + diff_files = repository.run_git!( + %W[diff --name-only --diff-filter=ar --binary #{diff_range}] + ).chomp + + with_worktree(squash_path, branch, sparse_checkout_files: diff_files, env: env) do + # Apply diff of the `diff_range` to the worktree + diff = run_git!(%W[diff --binary #{diff_range}]) + run_git!(%w[apply --index --whitespace=nowarn], chdir: squash_path, env: env) do |stdin| + stdin.binmode + stdin.write(diff) + end + + # Commit the `diff_range` diff + run_git!(%W[commit --no-verify --message #{message}], chdir: squash_path, env: env) + + # Return the squash sha. May print a warning for ambiguous refs, but + # we can ignore that with `--quiet` and just take the SHA, if present. + # HEAD here always refers to the current HEAD commit, even if there is + # another ref called HEAD. + run_git!( + %w[rev-parse --quiet --verify HEAD], chdir: squash_path, env: env + ).chomp + end + end + + def merge(source_sha, target_branch, message) + committer = user_as_committer + + operation_service.with_branch(target_branch) do |start_commit| + our_commit = start_commit.sha + their_commit = source_sha + + raise 'Invalid merge target' unless our_commit + raise 'Invalid merge source' unless their_commit + + merge_index = repository.rugged.merge_commits(our_commit, their_commit) + break if merge_index.conflicts? + + options = { + parents: [our_commit, their_commit], + tree: merge_index.write_tree(repository.rugged), + message: message, + author: committer, + committer: committer + } + + commit_id = create_commit(options) + + yield commit_id + + commit_id + end + rescue Gitlab::Git::CommitError # when merge_index.conflicts? + nil + end + + def ff_merge(source_sha, target_branch) + operation_service.with_branch(target_branch) do |our_commit| + raise ArgumentError, 'Invalid merge target' unless our_commit + + source_sha + end + rescue Rugged::ReferenceError, Gitlab::Git::Repository::InvalidRef + raise ArgumentError, 'Invalid merge source' + end + + def revert(commit:, branch_name:, message:, start_branch_name:, start_repository:) + operation_service.with_branch( + branch_name, + start_branch_name: start_branch_name, + start_repository: start_repository + ) do |start_commit| + + Gitlab::Git.check_namespace!(commit, start_repository) + + revert_tree_id = check_revert_content(commit, start_commit.sha) + raise Gitlab::Git::Repository::CreateTreeError unless revert_tree_id + + create_commit(message: message, + author: user_as_committer, + committer: user_as_committer, + tree: revert_tree_id, + parents: [start_commit.sha]) + end + end + + def cherry_pick(commit:, branch_name:, message:, start_branch_name:, start_repository:) + operation_service.with_branch( + branch_name, + start_branch_name: start_branch_name, + start_repository: start_repository + ) do |start_commit| + + Gitlab::Git.check_namespace!(commit, start_repository) + + cherry_pick_tree_id = check_cherry_pick_content(commit, start_commit.sha) + raise Gitlab::Git::Repository::CreateTreeError unless cherry_pick_tree_id + + committer = user_as_committer + + create_commit(message: message, + author: { + email: commit.author_email, + name: commit.author_name, + time: commit.authored_date + }, + committer: committer, + tree: cherry_pick_tree_id, + parents: [start_commit.sha]) + end + end + + def multi_action(branch_name:, message:, actions:, + author_email: nil, author_name: nil, + start_branch_name: nil, start_repository: repository) + + operation_service.with_branch( + branch_name, + start_branch_name: start_branch_name, + start_repository: start_repository + ) do |start_commit| + + index = Gitlab::Git::Index.new(repository) + parents = [] + + if start_commit + index.read_tree(start_commit.rugged_commit.tree) + parents = [start_commit.sha] + end + + actions.each { |opts| index.apply(opts.delete(:action), opts) } + + committer = user_as_committer + author = Gitlab::Git.committer_hash(email: author_email, name: author_name) || committer + options = { + tree: index.write_tree, + message: message, + parents: parents, + author: author, + committer: committer + } + + create_commit(options) + end + end + + def commit_index(branch_name, index, options) + committer = user_as_committer + + operation_service.with_branch(branch_name) do + commit_params = options.merge( + tree: index.write_tree(repository.rugged), + author: committer, + committer: committer + ) + + create_commit(commit_params) + end + end + + private + + attr_reader :user, :repository + + def operation_service + @operation_service ||= OperationService.new(user, repository) + end + + def git_env + { + 'GIT_COMMITTER_NAME' => user.name, + 'GIT_COMMITTER_EMAIL' => user.email, + 'GL_ID' => Gitlab::GlId.gl_id(user), + 'GL_PROTOCOL' => Gitlab::Git::Hook::GL_PROTOCOL, + 'GL_REPOSITORY' => repository.gl_repository + } + end + + def user_as_committer + Gitlab::Git.committer_hash(email: user.email, name: user.name) + end + end + end +end