Commit c830b8e3 authored by Zeger-Jan van de Weg's avatar Zeger-Jan van de Weg

Client implementation for InfoAttributes

Clients can now request the attributes from `$GIT_DIR/info/attributes`
through Gitaly. The Gitaly migration is described in gitlab-org/gitaly#1082.

The parser algorithm was implemented in a way it could handle both file
contents or a File handle, and both were already tested.

Other than that, using the boy scout rule, I've removed a class,
InfoAttributes, as it was delegating everything to the parser and
therefor wasn't really needed in my opinion.
parent 863e1a7a
...@@ -3,12 +3,8 @@ module Gitlab ...@@ -3,12 +3,8 @@ module Gitlab
# Class for parsing Git attribute files and extracting the attributes for # Class for parsing Git attribute files and extracting the attributes for
# file patterns. # file patterns.
class AttributesParser class AttributesParser
def initialize(attributes_data) def initialize(attributes_data = "")
@data = attributes_data || "" @data = attributes_data || ""
if @data.is_a?(File)
@patterns = parse_file
end
end end
# Returns all the Git attributes for the given path. # Returns all the Git attributes for the given path.
...@@ -28,7 +24,7 @@ module Gitlab ...@@ -28,7 +24,7 @@ module Gitlab
# Returns a Hash containing the file patterns and their attributes. # Returns a Hash containing the file patterns and their attributes.
def patterns def patterns
@patterns ||= parse_file @patterns ||= parse_data
end end
# Parses an attribute string. # Parses an attribute string.
...@@ -91,8 +87,8 @@ module Gitlab ...@@ -91,8 +87,8 @@ module Gitlab
private private
# Parses the Git attributes file. # Parses the Git attributes file contents.
def parse_file def parse_data
pairs = [] pairs = []
comment = '#' comment = '#'
......
# 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.
module Gitlab
module Git
# Parses gitattributes at `$GIT_DIR/info/attributes`
#
# 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::InfoAttributes.new(some_repo.path)
#
# attributes.attributes('README.md') # => { "eol" => "lf }
class InfoAttributes
delegate :attributes, :patterns, to: :parser
# path - The path to the Git repository.
def initialize(path)
@repo_path = File.expand_path(path)
end
def parser
@parser ||= begin
if File.exist?(attributes_path)
File.open(attributes_path, 'r') do |file_handle|
AttributesParser.new(file_handle)
end
else
AttributesParser.new("")
end
end
end
private
def attributes_path
@attributes_path ||= File.join(@repo_path, 'info/attributes')
end
end
end
end
...@@ -105,7 +105,6 @@ module Gitlab ...@@ -105,7 +105,6 @@ module Gitlab
) )
@path = File.join(storage_path, @relative_path) @path = File.join(storage_path, @relative_path)
@name = @relative_path.split("/").last @name = @relative_path.split("/").last
@attributes = Gitlab::Git::InfoAttributes.new(path)
end end
def ==(other) def ==(other)
...@@ -993,11 +992,32 @@ module Gitlab ...@@ -993,11 +992,32 @@ module Gitlab
raise InvalidRef raise InvalidRef
end end
def info_attributes
return @info_attributes if @info_attributes
content =
gitaly_migrate(:get_info_attributes) do |is_enabled|
if is_enabled
gitaly_repository_client.info_attributes
else
attributes_path = File.join(File.expand_path(@path), 'info', 'attributes')
if File.exist?(attributes_path)
File.read(attributes_path)
else
""
end
end
end
@info_attributes = AttributesParser.new(content)
end
# Returns the Git attributes for the given file path. # Returns the Git attributes for the given file path.
# #
# See `Gitlab::Git::Attributes` for more information. # See `Gitlab::Git::Attributes` for more information.
def attributes(path) def attributes(path)
@attributes.attributes(path) info_attributes.attributes(path)
end end
def gitattribute(path, name) def gitattribute(path, name)
......
...@@ -50,6 +50,15 @@ module Gitlab ...@@ -50,6 +50,15 @@ module Gitlab
GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request) GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request)
end end
def info_attributes
request = Gitaly::GetInfoAttributesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :repository_service, :get_info_attributes, request)
response.each_with_object("") do |message, attributes|
attributes << message.attributes
end
end
def fetch_remote(remote, ssh_auth:, forced:, no_tags:, timeout:, prune: true) def fetch_remote(remote, ssh_auth:, forced:, no_tags:, timeout:, prune: true)
request = Gitaly::FetchRemoteRequest.new( request = Gitaly::FetchRemoteRequest.new(
repository: @gitaly_repo, remote: remote, force: forced, repository: @gitaly_repo, remote: remote, force: forced,
......
...@@ -66,18 +66,6 @@ describe Gitlab::Git::AttributesParser, seed_helper: true do ...@@ -66,18 +66,6 @@ describe Gitlab::Git::AttributesParser, seed_helper: true do
end end
end end
context 'when attributes data is a file handle' do
subject do
File.open(attributes_path, 'r') do |file_handle|
described_class.new(file_handle)
end
end
it 'returns the attributes as a Hash' do
expect(subject.attributes('test.txt')).to eq({ 'text' => true })
end
end
context 'when attributes data is nil' do context 'when attributes data is nil' do
let(:data) { nil } let(:data) { nil }
......
require 'spec_helper'
describe Gitlab::Git::InfoAttributes, seed_helper: true do
let(:path) do
File.join(SEED_STORAGE_PATH, 'with-git-attributes.git')
end
subject { described_class.new(path) }
describe '#attributes' do
context 'using a path with attributes' do
it 'returns the attributes as a Hash' do
expect(subject.attributes('test.txt')).to eq({ 'text' => true })
end
it 'returns an empty Hash for a defined path without attributes' do
expect(subject.attributes('bla/bla.txt')).to eq({})
end
end
end
describe '#parser' do
it 'parses a file with entries' do
expect(subject.patterns).to be_an_instance_of(Hash)
expect(subject.patterns["/*.txt"]).to eq({ 'text' => true })
end
it 'does not parse anything when the attributes file does not exist' do
expect(File).to receive(:exist?)
.with(File.join(path, 'info/attributes'))
.and_return(false)
expect(subject.patterns).to eq({})
end
it 'does not parse attributes files with unsupported encoding' do
path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git')
subject = described_class.new(path)
expect(subject.patterns).to eq({})
end
end
end
...@@ -84,6 +84,17 @@ describe Gitlab::GitalyClient::RepositoryService do ...@@ -84,6 +84,17 @@ describe Gitlab::GitalyClient::RepositoryService do
end end
end end
describe '#info_attributes' do
it 'reads the info attributes' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:get_info_attributes)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([])
client.info_attributes
end
end
describe '#has_local_branches?' do describe '#has_local_branches?' do
it 'sends a has_local_branches message' do it 'sends a has_local_branches message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub) expect_any_instance_of(Gitaly::RepositoryService::Stub)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment