summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrzegorz Bizon <grzesiek.bizon@gmail.com>2017-09-05 12:17:56 +0200
committerGrzegorz Bizon <grzesiek.bizon@gmail.com>2017-09-05 12:17:56 +0200
commitc53f319f883064e6c5e77f9b48a235b23b0f4363 (patch)
tree97f6ab51502c663c5beb69c742db319869d99576
parentdfb8fcbb651812d209d2f42baf6c2bb0e851c861 (diff)
downloadgitlab-ce-c53f319f883064e6c5e77f9b48a235b23b0f4363.tar.gz
Extract a class that represents artifacts file path
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata/entry.rb244
-rw-r--r--lib/gitlab/ci/build/artifacts/path.rb51
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/path_spec.rb64
3 files changed, 237 insertions, 122 deletions
diff --git a/lib/gitlab/ci/build/artifacts/metadata/entry.rb b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
index 2e073334abc..321ddfd8c29 100644
--- a/lib/gitlab/ci/build/artifacts/metadata/entry.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
@@ -1,129 +1,129 @@
module Gitlab
- module Ci::Build::Artifacts
- class Metadata
- ##
- # Class that represents an entry (path and metadata) to a file or
- # directory in GitLab CI Build Artifacts binary file / archive
- #
- # This is IO-operations safe class, that does similar job to
- # Ruby's Pathname but without the risk of accessing filesystem.
- #
- # This class is working only with UTF-8 encoded paths.
- #
- class Entry
- attr_reader :path, :entries
- attr_accessor :name
-
- def initialize(path, entries)
- @path = path.dup.force_encoding('UTF-8')
- @entries = entries
-
- if path.include?("\0")
- raise ArgumentError, 'Path contains zero byte character!'
- end
+ module Ci
+ module Build
+ module Artifacts
+ class Metadata
+ ##
+ # Class that represents an entry (path and metadata) to a file or
+ # directory in GitLab CI Build Artifacts binary file / archive
+ #
+ # This is IO-operations safe class, that does similar job to
+ # Ruby's Pathname but without the risk of accessing filesystem.
+ #
+ # This class is working only with UTF-8 encoded paths.
+ #
+ class Entry
+ attr_reader :entries
+ attr_accessor :name
+
+ def initialize(path, entries)
+ @entries = entries
+ @path = Artifacts::Path.new(path)
+ end
+
+ delegate :empty?, to: :children
+
+ def directory?
+ blank_node? || @path.directory?
+ end
+
+ def file?
+ !directory?
+ end
+
+ def blob
+ return unless file?
+
+ @blob ||= Blob.decorate(::Ci::ArtifactBlob.new(self), nil)
+ end
+
+ def has_parent?
+ nodes > 0
+ end
+
+ def parent
+ return nil unless has_parent?
+ self.class.new(@path.to_s.chomp(basename), @entries)
+ end
+
+ def basename
+ (directory? && !blank_node?) ? name + '/' : name
+ end
+
+ def name
+ @name || @path.name
+ end
+
+ def children
+ return [] unless directory?
+ return @children if @children
+
+ child_pattern = %r{^#{Regexp.escape(@path.to_s)}[^/]+/?$}
+ @children = select_entries { |path| path =~ child_pattern }
+ end
+
+ def directories(opts = {})
+ return [] unless directory?
+ dirs = children.select(&:directory?)
+ return dirs unless has_parent? && opts[:parent]
+
+ dotted_parent = parent
+ dotted_parent.name = '..'
+ dirs.prepend(dotted_parent)
+ end
+
+ def files
+ return [] unless directory?
+ children.select(&:file?)
+ end
+
+ def metadata
+ @entries[@path.to_s] || {}
+ end
+
+ def nodes
+ @path.nodes + (file? ? 1 : 0)
+ end
+
+ def blank_node?
+ @path.to_s.empty? # "" is considered to be './'
+ end
+
+ def exists?
+ blank_node? || @entries.include?(@path.to_s)
+ end
+
+ def total_size
+ descendant_pattern = %r{^#{Regexp.escape(@path.to_s)}}
+ entries.sum do |path, entry|
+ (entry[:size] if path =~ descendant_pattern).to_i
+ end
+ end
+
+ def path
+ @path.to_s
+ end
+
+ def to_s
+ @path.to_s
+ end
+
+ def ==(other)
+ path == other.path && @entries == other.entries
+ end
+
+ def inspect
+ "#{self.class.name}: #{self.to_s}"
+ end
- unless path.valid_encoding?
- raise ArgumentError, 'Path contains non-UTF-8 byte sequence!'
+ private
+
+ def select_entries
+ selected = @entries.select { |path, _metadata| yield path }
+ selected.map { |path, _metadata| self.class.new(path, @entries) }
+ end
end
end
-
- delegate :empty?, to: :children
-
- def directory?
- blank_node? || @path.end_with?('/')
- end
-
- def file?
- !directory?
- end
-
- def blob
- return unless file?
-
- @blob ||= Blob.decorate(::Ci::ArtifactBlob.new(self), nil)
- end
-
- def has_parent?
- nodes > 0
- end
-
- def parent
- return nil unless has_parent?
- self.class.new(@path.chomp(basename), @entries)
- end
-
- def basename
- (directory? && !blank_node?) ? name + '/' : name
- end
-
- def name
- @name || @path.split('/').last.to_s
- end
-
- def children
- return [] unless directory?
- return @children if @children
-
- child_pattern = %r{^#{Regexp.escape(@path)}[^/]+/?$}
- @children = select_entries { |path| path =~ child_pattern }
- end
-
- def directories(opts = {})
- return [] unless directory?
- dirs = children.select(&:directory?)
- return dirs unless has_parent? && opts[:parent]
-
- dotted_parent = parent
- dotted_parent.name = '..'
- dirs.prepend(dotted_parent)
- end
-
- def files
- return [] unless directory?
- children.select(&:file?)
- end
-
- def metadata
- @entries[@path] || {}
- end
-
- def nodes
- @path.count('/') + (file? ? 1 : 0)
- end
-
- def blank_node?
- @path.empty? # "" is considered to be './'
- end
-
- def exists?
- blank_node? || @entries.include?(@path)
- end
-
- def total_size
- descendant_pattern = %r{^#{Regexp.escape(@path)}}
- entries.sum do |path, entry|
- (entry[:size] if path =~ descendant_pattern).to_i
- end
- end
-
- def to_s
- @path
- end
-
- def ==(other)
- @path == other.path && @entries == other.entries
- end
-
- def inspect
- "#{self.class.name}: #{@path}"
- end
-
- private
-
- def select_entries
- selected = @entries.select { |path, _metadata| yield path }
- selected.map { |path, _metadata| self.class.new(path, @entries) }
- end
end
end
end
diff --git a/lib/gitlab/ci/build/artifacts/path.rb b/lib/gitlab/ci/build/artifacts/path.rb
new file mode 100644
index 00000000000..9cd9b36c5f8
--- /dev/null
+++ b/lib/gitlab/ci/build/artifacts/path.rb
@@ -0,0 +1,51 @@
+module Gitlab
+ module Ci
+ module Build
+ module Artifacts
+ class Path
+ def initialize(path)
+ @path = path.dup.force_encoding('UTF-8')
+ end
+
+ def valid?
+ nonzero? && utf8?
+ end
+
+ def directory?
+ @path.end_with?('/')
+ end
+
+ def name
+ @path.split('/').last.to_s
+ end
+
+ def nodes
+ @path.count('/')
+ end
+
+ def to_s
+ @path.tap do |path|
+ unless nonzero?
+ raise ArgumentError, 'Path contains zero byte character!'
+ end
+
+ unless utf8?
+ raise ArgumentError, 'Path contains non-UTF-8 byte sequence!'
+ end
+ end
+ end
+
+ private
+
+ def nonzero?
+ @path.exclude?("\0")
+ end
+
+ def utf8?
+ @path.valid_encoding?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/artifacts/path_spec.rb b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
new file mode 100644
index 00000000000..7bd6a2ead25
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
@@ -0,0 +1,64 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Artifacts::Path do
+ describe '#valid?' do
+ context 'when path contains a zero character' do
+ it 'is not valid' do
+ expect(described_class.new("something/\255")).not_to be_valid
+ end
+ end
+
+ context 'when path is not utf8 string' do
+ it 'is not valid' do
+ expect(described_class.new("something/\0")).not_to be_valid
+ end
+ end
+
+ context 'when path is valid' do
+ it 'is valid' do
+ expect(described_class.new("some/file/path")).to be_valid
+ end
+ end
+ end
+
+ describe '#directory?' do
+ context 'when path ends with a directory indicator' do
+ it 'is a directory' do
+ expect(described_class.new("some/file/dir/")).to be_directory
+ end
+ end
+
+ context 'when path does not end with a directory indicator' do
+ it 'is not a directory' do
+ expect(described_class.new("some/file")).not_to be_directory
+ end
+ end
+ end
+
+ describe '#name' do
+ it 'returns a base name' do
+ expect(described_class.new("some/file").name).to eq 'file'
+ end
+ end
+
+ describe '#nodes' do
+ it 'returns number of path nodes' do
+ expect(described_class.new("some/dir/file").nodes).to eq 2
+ end
+ end
+
+ describe '#to_s' do
+ context 'when path is valid' do
+ it 'returns a string representation of a path' do
+ expect(described_class.new('some/path').to_s).to eq 'some/path'
+ end
+ end
+
+ context 'when path is invalid' do
+ it 'raises an error' do
+ expect { described_class.new("invalid/\0").to_s }
+ .to raise_error ArgumentError
+ end
+ end
+ end
+end