BigW Consortium Gitlab

attributes.rb 3.73 KB
Newer Older
1 2 3 4 5
# Gitaly note: JV: not sure what to make of this class. Why does it use
# the full disk path of the repository to look up attributes This is
# problematic in Gitaly, because Gitaly hides the full disk path to the
# repository from gitlab-ce.

Robert Speicher committed
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
module Gitlab
  module Git
    # Class for parsing Git attribute files and extracting the attributes for
    # file patterns.
    #
    # Unlike Rugged this parser only needs a single IO call (a call to `open`),
    # vastly reducing the time spent in extracting attributes.
    #
    # This class _only_ supports parsing the attributes file located at
    # `$GIT_DIR/info/attributes` as GitLab doesn't use any other files
    # (`.gitattributes` is copied to this particular path).
    #
    # Basic usage:
    #
    #     attributes = Gitlab::Git::Attributes.new(some_repo.path)
    #
    #     attributes.attributes('README.md') # => { "eol" => "lf }
    class Attributes
      # path - The path to the Git repository.
      def initialize(path)
        @path = File.expand_path(path)
        @patterns = nil
      end

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

        patterns.each do |pattern, attrs|
          return attrs if File.fnmatch?(pattern, full_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
        full_path = File.join(@path, 'info/attributes')

        return unless File.exist?(full_path)

        File.open(full_path, 'r') do |handle|
          handle.each_line do |line|
            break unless line.valid_encoding?

            yield line.strip
          end
        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) : {}

          pairs << [File.join(@path, pattern), parsed]
        end

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