summaryrefslogtreecommitdiff
path: root/lib/gitlab/diff/position.rb
blob: fc728123c97c61b05b0bc0f31bc4a32aeee005b8 (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
153
154
155
156
157
158
159
160
161
162
# Defines a specific location, identified by paths and line numbers,
# within a specific diff, identified by start, head and base commit ids.
module Gitlab
  module Diff
    class Position
      attr_reader :old_path
      attr_reader :new_path
      attr_reader :old_line
      attr_reader :new_line
      attr_reader :base_sha
      attr_reader :start_sha
      attr_reader :head_sha

      def initialize(attrs = {})
        @old_path = attrs[:old_path]
        @new_path = attrs[:new_path]
        @old_line = attrs[:old_line]
        @new_line = attrs[:new_line]

        if attrs[:diff_refs]
          @base_sha  = attrs[:diff_refs].base_sha
          @start_sha = attrs[:diff_refs].start_sha
          @head_sha  = attrs[:diff_refs].head_sha
        else
          @base_sha  = attrs[:base_sha]
          @start_sha = attrs[:start_sha]
          @head_sha  = attrs[:head_sha]
        end
      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'] = self.to_h
      end

      def key
        @key ||= [base_sha, start_sha, head_sha, Digest::SHA1.hexdigest(old_path || ""), Digest::SHA1.hexdigest(new_path || ""), old_line, new_line]
      end

      def ==(other)
        other.is_a?(self.class) && key == other.key
      end

      def to_h
        {
          old_path: old_path,
          new_path: new_path,
          old_line: old_line,
          new_line: new_line,
          base_sha:  base_sha,
          start_sha: start_sha,
          head_sha:  head_sha
        }
      end

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

      def complete?
        file_path.present? &&
          (old_line || new_line) &&
          diff_refs.complete?
      end

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

      def type
        if old_line && new_line
          nil
        elsif new_line
          'new'
        else
          'old'
        end
      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)
        @diff_file ||= begin
          if RequestStore.active?
            key = {
              project_id: repository.project.id,
              start_sha: start_sha,
              head_sha: head_sha,
              path: file_path
            }

            RequestStore.fetch(key) { find_diff_file(repository) }
          else
            find_diff_file(repository)
          end
        end
      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)
        # We're at the initial commit, so just get that as we can't compare to anything.
        compare =
          if Gitlab::Git.blank_ref?(start_sha)
            Gitlab::Git::Commit.find(repository.raw_repository, head_sha)
          else
            Gitlab::Git::Compare.new(
              repository.raw_repository,
              start_sha,
              head_sha
            )
          end

        diff = compare.diffs(paths: paths).first

        return unless diff

        Gitlab::Diff::File.new(diff, repository: repository, diff_refs: diff_refs)
      end
    end
  end
end