Skip to content
Snippets Groups Projects
Select Git revision
  • move-gl-dropdown
  • improve-table-pagination-spec
  • move-markdown-preview
  • winh-fix-merge-request-spec
  • master default
  • index-namespaces-lower-name
  • winh-single-karma-test
  • 10-3-stable
  • 36782-replace-team-user-role-with-add_role-user-in-specs
  • winh-modal-internal-state
  • tz-ide-file-icons
  • 38869-milestone-select
  • update-autodevops-template
  • jivl-activate-repo-cookie-preferences
  • qa-add-deploy-key
  • docs-move-article-ldap
  • 40780-choose-file
  • 22643-manual-job-page
  • refactor-cluster-show-page-conservative
  • dm-sidekiq-versioning
  • v10.4.0.pre
  • v10.3.0
  • v10.3.0-rc5
  • v10.3.0-rc4
  • v10.3.0-rc3
  • v10.3.0-rc2
  • v10.2.5
  • v10.3.0-rc1
  • v10.0.7
  • v10.1.5
  • v10.2.4
  • v10.2.3
  • v10.2.2
  • v10.2.1
  • v10.3.0.pre
  • v10.2.0
  • v10.2.0-rc4
  • v10.2.0-rc3
  • v10.1.4
  • v10.2.0-rc2
40 results

git_operation_service.rb

Forked from GitLab.org / GitLab FOSS
9681 commits behind the upstream repository.
git_operation_service.rb 4.99 KiB
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.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 `base_branch_name` is passed, if `branch_name` doesn't exist,
  # it would be created from `base_branch_name`.
  # If `base_project` is passed, and the branch doesn't exist,
  # it would try to find the base from it instead of current repository.
  def with_branch(
    branch_name,
    base_branch_name: nil,
    base_project: repository.project,
    &block)

    check_with_branch_arguments!(
      branch_name, base_branch_name, base_project)

    update_branch_with_hooks(branch_name) do
      repository.with_repo_branch_commit(
        base_project.repository,
        base_branch_name || branch_name,
        &block)
    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, base_branch_name, base_project)
    return if repository.branch_exists?(branch_name)

    if repository.project != base_project
      unless base_branch_name
        raise ArgumentError,
          'Should also pass :base_branch_name if' +
          ' :base_project is different from current project'
      end

      unless base_project.repository.branch_exists?(base_branch_name)
        raise ArgumentError,
          "Cannot find branch #{branch_name} nor" \
          " #{base_branch_name} from" \
          " #{base_project.path_with_namespace}"
      end
    elsif base_branch_name
      unless repository.branch_exists?(base_branch_name)
        raise ArgumentError,
          "Cannot find branch #{branch_name} nor" \
          " #{base_branch_name} from" \
          " #{repository.project.path_with_namespace}"
      end
    end
  end
end