summaryrefslogtreecommitdiff
path: root/lib/gitlab/git/attributes.rb
diff options
context:
space:
mode:
authorRobert Speicher <rspeicher@gmail.com>2017-01-04 13:43:06 -0500
committerRobert Speicher <rspeicher@gmail.com>2017-01-04 19:09:28 -0500
commita00578ce5cdf4bab95990bca9e806c6322bb1384 (patch)
tree5aafe058fa55086038dbb3086121ad624447c0e4 /lib/gitlab/git/attributes.rb
parentaec04a47c16665f1dfb1fb61647c3f78a4dde20f (diff)
downloadgitlab-ce-a00578ce5cdf4bab95990bca9e806c6322bb1384.tar.gz
Absorb gitlab_git
Diffstat (limited to 'lib/gitlab/git/attributes.rb')
-rw-r--r--lib/gitlab/git/attributes.rb131
1 files changed, 131 insertions, 0 deletions
diff --git a/lib/gitlab/git/attributes.rb b/lib/gitlab/git/attributes.rb
new file mode 100644
index 00000000000..42140ecc993
--- /dev/null
+++ b/lib/gitlab/git/attributes.rb
@@ -0,0 +1,131 @@
+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