summaryrefslogtreecommitdiff
path: root/lib/gitlab/git/attributes_parser.rb
blob: d8aeabb6cba0488c8a5de64c13cb48648a54953f (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
module Gitlab
  module Git
    # Class for parsing Git attribute files and extracting the attributes for
    # file patterns.
    class AttributesParser
      def initialize(attributes_data)
        @data = attributes_data || ""

        if @data.is_a?(File)
          @patterns = parse_file
        end
      end

      # Returns all the Git attributes for the given path.
      #
      # file_path - A path to a file for which to get the attributes.
      #
      # Returns a Hash.
      def attributes(file_path)
        absolute_path = File.join('/', file_path)

        patterns.each do |pattern, attrs|
          return attrs if File.fnmatch?(pattern, absolute_path)
        end

        {}
      end

      # Returns a Hash containing the file patterns and their attributes.
      def patterns
        @patterns ||= parse_file
      end

      # Parses an attribute string.
      #
      # These strings can be in the following formats:
      #
      #     text      # => { "text" => true }
      #     -text     # => { "text" => false }
      #     key=value # => { "key" => "value" }
      #
      # string - The string to parse.
      #
      # Returns a Hash containing the attributes and their values.
      def parse_attributes(string)
        values = {}
        dash = '-'
        equal = '='
        binary = 'binary'

        string.split(/\s+/).each do |chunk|
          # Data such as "foo = bar" should be treated as "foo" and "bar" being
          # separate boolean attributes.
          next if chunk == equal

          key = chunk

          # Input: "-foo"
          if chunk.start_with?(dash)
            key = chunk.byteslice(1, chunk.length - 1)
            value = false

          # Input: "foo=bar"
          elsif chunk.include?(equal)
            key, value = chunk.split(equal, 2)

          # Input: "foo"
          else
            value = true
          end

          values[key] = value

          # When the "binary" option is set the "diff" option should be set to
          # the inverse. If "diff" is later set it should overwrite the
          # automatically set value.
          values['diff'] = false if key == binary && value
        end

        values
      end

      # Iterates over every line in the attributes file.
      def each_line
        @data.each_line do |line|
          break unless line.valid_encoding?

          yield line.strip
        end
      end

      private

      # Parses the Git attributes file.
      def parse_file
        pairs = []
        comment = '#'

        each_line do |line|
          next if line.start_with?(comment) || line.empty?

          pattern, attrs = line.split(/\s+/, 2)

          parsed = attrs ? parse_attributes(attrs) : {}

          absolute_pattern = File.join('/', pattern)
          pairs << [absolute_pattern, parsed]
        end

        # Newer entries take precedence over older entries.
        pairs.reverse.to_h
      end
    end
  end
end