BigW Consortium Gitlab

ref_service.rb 9.33 KB
Newer Older
1 2
module Gitlab
  module GitalyClient
3
    class RefService
4 5
      include Gitlab::EncodingHelper

6 7
      # 'repository' is a Gitlab::Git::Repository
      def initialize(repository)
8
        @repository = repository
9
        @gitaly_repo = repository.gitaly_repository
10
        @storage = repository.storage
11 12
      end

13 14 15 16
      def branches
        request = Gitaly::FindAllBranchesRequest.new(repository: @gitaly_repo)
        response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request)

17 18 19 20 21 22 23 24 25 26 27 28
        consume_find_all_branches_response(response)
      end

      def merged_branches(branch_names = [])
        request = Gitaly::FindAllBranchesRequest.new(
          repository: @gitaly_repo,
          merged_only: true,
          merged_branches: branch_names.map { |s| encode_binary(s) }
        )
        response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request)

        consume_find_all_branches_response(response)
29 30
      end

31
      def default_branch_name
32
        request = Gitaly::FindDefaultBranchNameRequest.new(repository: @gitaly_repo)
33
        response = GitalyClient.call(@storage, :ref_service, :find_default_branch_name, request)
34
        Gitlab::Git.branch_name(response.name)
35 36 37
      end

      def branch_names
38
        request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo)
39
        response = GitalyClient.call(@storage, :ref_service, :find_all_branch_names, request)
40
        consume_refs_response(response) { |name| Gitlab::Git.branch_name(name) }
41 42 43
      end

      def tag_names
44
        request = Gitaly::FindAllTagNamesRequest.new(repository: @gitaly_repo)
45
        response = GitalyClient.call(@storage, :ref_service, :find_all_tag_names, request)
46
        consume_refs_response(response) { |name| Gitlab::Git.tag_name(name) }
47 48
      end

49
      def find_ref_name(commit_id, ref_prefix)
Jacob Vosmaer committed
50
        request = Gitaly::FindRefNameRequest.new(
51
          repository: @gitaly_repo,
52 53 54
          commit_id: commit_id,
          prefix: ref_prefix
        )
55 56
        response = GitalyClient.call(@storage, :ref_service, :find_ref_name, request, timeout: GitalyClient.medium_timeout)
        encode!(response.name.dup)
57 58
      end

59 60 61 62 63 64 65 66
      def count_tag_names
        tag_names.count
      end

      def count_branch_names
        branch_names.count
      end

67 68 69
      def local_branches(sort_by: nil)
        request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo)
        request.sort_by = sort_by_param(sort_by) if sort_by
70
        response = GitalyClient.call(@storage, :ref_service, :find_local_branches, request)
71
        consume_find_local_branches_response(response)
72 73
      end

74 75 76 77 78 79
      def tags
        request = Gitaly::FindAllTagsRequest.new(repository: @gitaly_repo)
        response = GitalyClient.call(@storage, :ref_service, :find_all_tags, request)
        consume_tags_response(response)
      end

80
      def ref_exists?(ref_name)
81
        request = Gitaly::RefExistsRequest.new(repository: @gitaly_repo, ref: encode_binary(ref_name))
82 83 84 85 86 87
        response = GitalyClient.call(@storage, :ref_service, :ref_exists, request)
        response.value
      rescue GRPC::InvalidArgument => e
        raise ArgumentError, e.message
      end

88
      def find_branch(branch_name)
89
        request = Gitaly::FindBranchRequest.new(
90
          repository: @gitaly_repo,
91
          name: encode_binary(branch_name)
92 93 94 95 96 97 98 99 100 101
        )

        response = GitalyClient.call(@repository.storage, :ref_service, :find_branch, request)
        branch = response.branch
        return unless branch

        target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target_commit)
        Gitlab::Git::Branch.new(@repository, encode!(branch.name.dup), branch.target_commit.id, target_commit)
      end

102 103 104
      def create_branch(ref, start_point)
        request = Gitaly::CreateBranchRequest.new(
          repository: @gitaly_repo,
105 106
          name: encode_binary(ref),
          start_point: encode_binary(start_point)
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
        )

        response = GitalyClient.call(@repository.storage, :ref_service, :create_branch, request)

        case response.status
        when :OK
          branch = response.branch
          target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target_commit)
          Gitlab::Git::Branch.new(@repository, branch.name, branch.target_commit.id, target_commit)
        when :ERR_INVALID
          invalid_ref!("Invalid ref name")
        when :ERR_EXISTS
          invalid_ref!("Branch #{ref} already exists")
        when :ERR_INVALID_START_POINT
          invalid_ref!("Invalid reference #{start_point}")
        else
          raise "Unknown response status: #{response.status}"
        end
      end

      def delete_branch(branch_name)
        request = Gitaly::DeleteBranchRequest.new(
          repository: @gitaly_repo,
130
          name: encode_binary(branch_name)
131 132 133 134 135
        )

        GitalyClient.call(@repository.storage, :ref_service, :delete_branch, request)
      end

136
      def delete_refs(refs: [], except_with_prefixes: [])
137 138
        request = Gitaly::DeleteRefsRequest.new(
          repository: @gitaly_repo,
139 140
          refs: refs.map { |r| encode_binary(r) },
          except_with_prefix: except_with_prefixes.map { |r| encode_binary(r) }
141 142
        )

143 144 145
        response = GitalyClient.call(@repository.storage, :ref_service, :delete_refs, request)

        raise Gitlab::Git::Repository::GitError, response.git_error if response.git_error.present?
146 147
      end

148
      # Limit: 0 implies no limit, thus all tag names will be returned
149
      def tag_names_contains_sha(sha, limit: 0)
150 151 152 153 154 155 156 157
        request = Gitaly::ListTagNamesContainingCommitRequest.new(
          repository: @gitaly_repo,
          commit_id: sha,
          limit: limit
        )

        stream = GitalyClient.call(@repository.storage, :ref_service, :list_tag_names_containing_commit, request)

158
        consume_ref_contains_sha_response(stream, :tag_names)
159 160 161 162 163 164 165 166 167 168 169 170
      end

      # Limit: 0 implies no limit, thus all tag names will be returned
      def branch_names_contains_sha(sha, limit: 0)
        request = Gitaly::ListBranchNamesContainingCommitRequest.new(
          repository: @gitaly_repo,
          commit_id: sha,
          limit: limit
        )

        stream = GitalyClient.call(@repository.storage, :ref_service, :list_branch_names_containing_commit, request)

171
        consume_ref_contains_sha_response(stream, :branch_names)
172 173
      end

174 175
      private

176 177
      def consume_refs_response(response)
        response.flat_map { |message| message.names.map { |name| yield(name) } }
178
      end
179 180

      def sort_by_param(sort_by)
181 182
        sort_by = 'name' if sort_by == 'name_asc'

183 184
        enum_value = Gitaly::FindLocalBranchesRequest::SortBy.resolve(sort_by.upcase.to_sym)
        raise ArgumentError, "Invalid sort_by key `#{sort_by}`" unless enum_value
185

186 187 188
        enum_value
      end

189
      def consume_find_local_branches_response(response)
190 191 192 193 194
        response.flat_map do |message|
          message.branches.map do |gitaly_branch|
            Gitlab::Git::Branch.new(
              @repository,
              encode!(gitaly_branch.name.dup),
195 196
              gitaly_branch.commit_id,
              commit_from_local_branches_response(gitaly_branch)
197 198 199
            )
          end
        end
200
      end
201

202 203 204 205 206 207 208 209 210
      def consume_find_all_branches_response(response)
        response.flat_map do |message|
          message.branches.map do |branch|
            target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target)
            Gitlab::Git::Branch.new(@repository, branch.name, branch.target.id, target_commit)
          end
        end
      end

211 212
      def consume_tags_response(response)
        response.flat_map do |message|
213
          message.tags.map { |gitaly_tag| Util.gitlab_tag_from_gitaly_tag(@repository, gitaly_tag) }
214 215 216
        end
      end

217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
      def commit_from_local_branches_response(response)
        # Git messages have no encoding enforcements. However, in the UI we only
        # handle UTF-8, so basically we cross our fingers that the message force
        # encoded to UTF-8 is readable.
        message = response.commit_subject.dup.force_encoding('UTF-8')

        # NOTE: For ease of parsing in Gitaly, we have only the subject of
        # the commit and not the full message. This is ok, since all the
        # code that uses `local_branches` only cares at most about the
        # commit message.
        # TODO: Once gitaly "takes over" Rugged consider separating the
        # subject from the message to make it clearer when there's one
        # available but not the other.
        hash = {
          id: response.commit_id,
          message: message,
          authored_date: Time.at(response.commit_author.date.seconds),
234 235
          author_name: response.commit_author.name.dup,
          author_email: response.commit_author.email.dup,
236
          committed_date: Time.at(response.commit_committer.date.seconds),
237 238
          committer_name: response.commit_committer.name.dup,
          committer_email: response.commit_committer.email.dup
239 240
        }

241
        Gitlab::Git::Commit.decorate(@repository, hash)
242
      end
243

244 245 246 247 248 249 250
      def consume_ref_contains_sha_response(stream, collection_name)
        stream.each_with_object([]) do |response, array|
          encoded_names = response.send(collection_name).map { |b| Gitlab::Git.ref_name(b) } # rubocop:disable GitlabSecurity/PublicSend
          array.concat(encoded_names)
        end
      end

251 252 253
      def invalid_ref!(message)
        raise Gitlab::Git::Repository::InvalidRef.new(message)
      end
254 255 256
    end
  end
end