1# frozen_string_literal: true
2
3module Gitlab
4  module GitalyClient
5    class ConflictsService
6      include Gitlab::EncodingHelper
7
8      MAX_MSG_SIZE = 128.kilobytes.freeze
9
10      def initialize(repository, our_commit_oid, their_commit_oid)
11        @gitaly_repo = repository.gitaly_repository
12        @repository = repository
13        @our_commit_oid = our_commit_oid
14        @their_commit_oid = their_commit_oid
15      end
16
17      def list_conflict_files(allow_tree_conflicts: false)
18        request = Gitaly::ListConflictFilesRequest.new(
19          repository: @gitaly_repo,
20          our_commit_oid: @our_commit_oid,
21          their_commit_oid: @their_commit_oid,
22          allow_tree_conflicts: allow_tree_conflicts
23        )
24        response = GitalyClient.call(@repository.storage, :conflicts_service, :list_conflict_files, request, timeout: GitalyClient.long_timeout)
25        GitalyClient::ConflictFilesStitcher.new(response, @gitaly_repo)
26      end
27
28      def conflicts?
29        list_conflict_files.any?
30      rescue GRPC::FailedPrecondition, GRPC::Unknown
31        # The server raises FailedPrecondition when it encounters
32        # ConflictSideMissing, which means a conflict exists but its `theirs` or
33        # `ours` data is nil due to a non-existent file in one of the trees.
34        #
35        # GRPC::Unknown comes from Rugged::ReferenceError and Rugged::OdbError.
36        true
37      end
38
39      def resolve_conflicts(target_repository, resolution, source_branch, target_branch)
40        reader = binary_io(resolution.files.to_json)
41
42        req_enum = Enumerator.new do |y|
43          header = resolve_conflicts_request_header(target_repository, resolution, source_branch, target_branch)
44          y.yield Gitaly::ResolveConflictsRequest.new(header: header)
45
46          until reader.eof?
47            chunk = reader.read(MAX_MSG_SIZE)
48
49            y.yield Gitaly::ResolveConflictsRequest.new(files_json: chunk)
50          end
51        end
52
53        response = GitalyClient.call(@repository.storage, :conflicts_service, :resolve_conflicts, req_enum, remote_storage: target_repository.storage, timeout: GitalyClient.long_timeout)
54
55        if response.resolution_error.present?
56          raise Gitlab::Git::Conflict::Resolver::ResolutionError, response.resolution_error
57        end
58      end
59
60      private
61
62      def resolve_conflicts_request_header(target_repository, resolution, source_branch, target_branch)
63        Gitaly::ResolveConflictsRequestHeader.new(
64          repository: @gitaly_repo,
65          our_commit_oid: @our_commit_oid,
66          target_repository: target_repository.gitaly_repository,
67          their_commit_oid: @their_commit_oid,
68          source_branch: encode_binary(source_branch),
69          target_branch: encode_binary(target_branch),
70          commit_message: encode_binary(resolution.commit_message),
71          user: Gitlab::Git::User.from_gitlab(resolution.user).to_gitaly,
72          timestamp: Google::Protobuf::Timestamp.new(seconds: Time.now.utc.to_i)
73        )
74      end
75    end
76  end
77end
78