summaryrefslogtreecommitdiff
path: root/lib/gitlab/diff/position.rb
blob: f967494199e0ecc23eb2741f67a5aba585e7b2e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# Defines a specific location, identified by paths line numbers and image coordinates,
# within a specific diff, identified by start, head and base commit ids.
module Gitlab
  module Diff
    class Position
      attr_accessor :formatter

      delegate :old_path,
               :new_path,
               :base_sha,
               :start_sha,
               :head_sha,
               :old_line,
               :new_line,
               :width,
               :height,
               :x,
               :y,
               :position_type, to: :formatter

      # A position can belong to a text line or to an image coordinate
      # it depends of the position_type argument.
      # Text position will have: new_line and old_line
      # Image position will have: width, height, x, y
      def initialize(attrs = {})
        @formatter = get_formatter_class(attrs[:position_type]).new(attrs)
      end

      # `Gitlab::Diff::Position` objects are stored as serialized attributes in
      # `DiffNote`, which use YAML to encode and decode objects.
      # `#init_with` and `#encode_with` can be used to customize the en/decoding
      # behavior. In this case, we override these to prevent memoized instance
      # variables like `@diff_file` and `@diff_line` from being serialized.
      def init_with(coder)
        initialize(coder['attributes'])

        self
      end

      def encode_with(coder)
        coder['attributes'] = formatter.to_h
      end

      def key
        formatter.key
      end

      def ==(other)
        other.is_a?(self.class) &&
          other.diff_refs == diff_refs &&
          other.old_path == old_path &&
          other.new_path == new_path &&
          other.formatter == formatter
      end

      def to_h
        formatter.to_h
      end

      def inspect
        %(#<#{self.class}:#{object_id} #{to_h}>)
      end

      def complete?
        file_path.present? && formatter.complete? && diff_refs.complete?
      end

      def to_json(opts = nil)
        JSON.generate(formatter.to_h, opts)
      end

      def as_json(opts = nil)
        to_h.as_json(opts)
      end

      def type
        formatter.line_age
      end

      def unchanged?
        type.nil?
      end

      def added?
        type == 'new'
      end

      def removed?
        type == 'old'
      end

      def paths
        [old_path, new_path].compact.uniq
      end

      def file_path
        new_path.presence || old_path
      end

      def diff_refs
        @diff_refs ||= DiffRefs.new(base_sha: base_sha, start_sha: start_sha, head_sha: head_sha)
      end

      def diff_file(repository)
        return @diff_file if defined?(@diff_file)

        @diff_file = begin
          key = {
            project_id: repository.project.id,
            start_sha: start_sha,
            head_sha: head_sha,
            path: file_path
          }

          Gitlab::SafeRequestStore.fetch(key) { find_diff_file(repository) }
        end
      end

      def diff_options
        { paths: paths, expanded: true, include_stats: false }
      end

      def diff_line(repository)
        @diff_line ||= diff_file(repository)&.line_for_position(self)
      end

      def line_code(repository)
        @line_code ||= diff_file(repository)&.line_code_for_position(self)
      end

      private

      def find_diff_file(repository)
        return unless diff_refs.complete?
        return unless comparison = diff_refs.compare_in(repository.project)

        comparison.diffs(diff_options).diff_files.first
      end

      def get_formatter_class(type)
        type ||= "text"

        case type
        when 'image'
          Gitlab::Diff::Formatters::ImageFormatter
        else
          Gitlab::Diff::Formatters::TextFormatter
        end
      end
    end
  end
end