diff options
Diffstat (limited to 'lib/gitlab/git/operation_service.rb')
-rw-r--r-- | lib/gitlab/git/operation_service.rb | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/lib/gitlab/git/operation_service.rb b/lib/gitlab/git/operation_service.rb new file mode 100644 index 00000000000..9e6fca8c80c --- /dev/null +++ b/lib/gitlab/git/operation_service.rb @@ -0,0 +1,168 @@ +module Gitlab + module Git + class OperationService + attr_reader :committer, :repository + + def initialize(committer, new_repository) + committer = Gitlab::Git::Committer.from_user(committer) if committer.is_a?(User) + @committer = committer + + # Refactoring aid + unless new_repository.is_a?(Gitlab::Git::Repository) + raise "expected a Gitlab::Git::Repository, got #{new_repository}" + end + + @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.target + 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| + # We want to pass the OID of the tag object to the hooks. For an + # annotated tag we don't know that OID until after the tag object + # (raw_tag) is created in the repository. That is why we have to + # update the value after creating the tag object. Only the + # "post-receive" hook will receive the correct value in this case. + raw_tag = repository.rugged.tags.create(tag_name, newrev, options) + service.newrev = raw_tag.target_id + end + end + + def rm_tag(tag) + ref = Gitlab::Git::TAG_REF_PREFIX + tag.name + oldrev = tag.target + newrev = Gitlab::Git::BLANK_SHA + + update_ref_in_hooks(ref, newrev, oldrev) do + repository.rugged.tags.delete(tag_name) + end + end + + # Whenever `start_branch_name` is passed, if `branch_name` doesn't exist, + # it would be created from `start_branch_name`. + # If `start_project` is passed, and the branch doesn't exist, + # it would try to find the commits from it instead of current repository. + def with_branch( + branch_name, + start_branch_name: nil, + start_repository: repository, + &block) + + # Refactoring aid + unless start_repository.is_a?(Gitlab::Git::Repository) + raise "expected a Gitlab::Git::Repository, got #{start_repository}" + end + + start_branch_name = nil if start_repository.empty_repo? + + if start_branch_name && !start_repository.branch_exists?(start_branch_name) + raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.full_path}" + end + + update_branch_with_hooks(branch_name) do + repository.with_repo_branch_commit( + start_repository, + start_branch_name || branch_name, + &block) + end + end + + private + + # Returns [newrev, should_run_after_create, should_run_after_create_branch] + def update_branch_with_hooks(branch_name) + update_autocrlf_option + + was_empty = repository.empty? + + # Make commit + newrev = yield + + unless newrev + raise Gitlab::Git::CommitError.new('Failed to create commit') + end + + branch = repository.find_branch(branch_name) + oldrev = find_oldrev_from_branch(newrev, branch) + + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name + update_ref_in_hooks(ref, newrev, oldrev) + + [newrev, was_empty, was_empty || Gitlab::Git.blank_ref?(oldrev)] + end + + def find_oldrev_from_branch(newrev, branch) + return Gitlab::Git::BLANK_SHA unless branch + + oldrev = branch.target + + if oldrev == repository.rugged.merge_base(newrev, branch.target) + oldrev + else + raise Gitlab::Git::CommitError.new('Branch diverged') + end + 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) + Gitlab::Git::HooksService.new.execute( + committer, + repository, + oldrev, + newrev, + ref) do |service| + + yield(service) + end + end + + # Gitaly note: JV: wait with migrating #update_ref until we know how to migrate its call sites. + 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) do |stdin| + stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00") + end + + unless status.zero? + raise Gitlab::Git::CommitError.new( + "Could not update branch #{Gitlab::Git.branch_name(ref)}." \ + " Please refresh and try again.") + end + end + + def update_autocrlf_option + if repository.autocrlf != :input + repository.autocrlf = :input + end + end + end + end +end |