2018-07-25 05:30:33 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2018-09-06 05:48:57 -04:00
|
|
|
# Blob is a Rails-specific wrapper around Gitlab::Git::Blob, SnippetBlob and Ci::ArtifactBlob
|
2016-02-18 18:19:43 -05:00
|
|
|
class Blob < SimpleDelegator
|
2021-04-12 08:09:15 -04:00
|
|
|
include GlobalID::Identification
|
2018-09-06 05:48:57 -04:00
|
|
|
include Presentable
|
2018-10-04 05:16:51 -04:00
|
|
|
include BlobLanguageFromGitAttributes
|
2019-12-17 10:08:15 -05:00
|
|
|
include BlobActiveModel
|
2018-09-06 05:48:57 -04:00
|
|
|
|
2020-08-10 17:09:44 -04:00
|
|
|
MODE_SYMLINK = '120000' # The STRING 120000 is the git-reported octal filemode for a symlink
|
2022-04-04 14:08:38 -04:00
|
|
|
MODE_EXECUTABLE = '100755' # The STRING 100755 is the git-reported octal filemode for an executable file
|
2020-08-10 17:09:44 -04:00
|
|
|
|
2016-03-07 08:27:53 -05:00
|
|
|
CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute
|
|
|
|
CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour
|
|
|
|
|
2017-04-26 16:16:38 -04:00
|
|
|
# Finding a viewer for a blob happens based only on extension and whether the
|
|
|
|
# blob is binary or text, which means 1 blob should only be matched by 1 viewer,
|
|
|
|
# and the order of these viewers doesn't really matter.
|
|
|
|
#
|
|
|
|
# However, when the blob is an LFS pointer, we cannot know for sure whether the
|
|
|
|
# file being pointed to is binary or text. In this case, we match only on
|
|
|
|
# extension, preferring binary viewers over text ones if both exist, since the
|
|
|
|
# large files referred to in "Large File Storage" are much more likely to be
|
|
|
|
# binary than text.
|
|
|
|
#
|
|
|
|
# `.stl` files, for example, exist in both binary and text forms, and are
|
|
|
|
# handled by different viewers (`BinarySTL` and `TextSTL`) depending on blob
|
|
|
|
# type. LFS pointers to `.stl` files are assumed to always be the binary kind,
|
|
|
|
# and use the `BinarySTL` viewer.
|
2017-04-13 13:14:08 -04:00
|
|
|
RICH_VIEWERS = [
|
2021-07-07 20:17:29 -04:00
|
|
|
BlobViewer::CSV,
|
2017-04-26 16:16:38 -04:00
|
|
|
BlobViewer::Markup,
|
|
|
|
BlobViewer::Notebook,
|
|
|
|
BlobViewer::SVG,
|
2019-12-16 07:07:43 -05:00
|
|
|
BlobViewer::OpenApi,
|
2017-04-26 16:16:38 -04:00
|
|
|
|
2017-04-13 13:21:07 -04:00
|
|
|
BlobViewer::Image,
|
|
|
|
BlobViewer::Sketch,
|
2017-04-26 16:16:38 -04:00
|
|
|
|
2017-04-13 12:48:37 -04:00
|
|
|
BlobViewer::Video,
|
2019-10-16 11:06:17 -04:00
|
|
|
BlobViewer::Audio,
|
2017-05-02 18:45:50 -04:00
|
|
|
|
2017-04-26 16:16:38 -04:00
|
|
|
BlobViewer::PDF,
|
|
|
|
|
2017-04-13 13:21:07 -04:00
|
|
|
BlobViewer::BinarySTL,
|
2017-05-03 07:22:03 -04:00
|
|
|
BlobViewer::TextSTL
|
2017-05-08 19:50:23 -04:00
|
|
|
].sort_by { |v| v.binary? ? 0 : 1 }.freeze
|
2016-08-12 11:19:17 -04:00
|
|
|
|
2017-05-08 19:50:23 -04:00
|
|
|
AUXILIARY_VIEWERS = [
|
|
|
|
BlobViewer::GitlabCiYml,
|
|
|
|
BlobViewer::RouteMap,
|
2017-05-13 13:06:51 -04:00
|
|
|
|
2017-05-14 15:01:12 -04:00
|
|
|
BlobViewer::Readme,
|
2017-05-13 13:06:51 -04:00
|
|
|
BlobViewer::License,
|
2017-05-17 14:00:13 -04:00
|
|
|
BlobViewer::Contributing,
|
2017-05-17 12:27:30 -04:00
|
|
|
BlobViewer::Changelog,
|
2020-06-03 05:08:47 -04:00
|
|
|
BlobViewer::MetricsDashboardYml,
|
2017-05-17 12:27:30 -04:00
|
|
|
|
2019-12-18 07:07:48 -05:00
|
|
|
BlobViewer::CargoToml,
|
2017-05-17 12:27:30 -04:00
|
|
|
BlobViewer::Cartfile,
|
|
|
|
BlobViewer::ComposerJson,
|
|
|
|
BlobViewer::Gemfile,
|
|
|
|
BlobViewer::Gemspec,
|
|
|
|
BlobViewer::GodepsJson,
|
2020-06-10 05:08:35 -04:00
|
|
|
BlobViewer::GoMod,
|
2017-05-17 12:27:30 -04:00
|
|
|
BlobViewer::PackageJson,
|
|
|
|
BlobViewer::Podfile,
|
|
|
|
BlobViewer::Podspec,
|
|
|
|
BlobViewer::PodspecJson,
|
|
|
|
BlobViewer::RequirementsTxt,
|
|
|
|
BlobViewer::YarnLock
|
2017-05-08 19:50:23 -04:00
|
|
|
].freeze
|
2017-04-26 16:16:38 -04:00
|
|
|
|
2020-02-04 10:08:40 -05:00
|
|
|
attr_reader :container
|
|
|
|
|
|
|
|
delegate :repository, to: :container, allow_nil: true
|
|
|
|
delegate :project, to: :repository, allow_nil: true
|
2017-04-13 12:59:52 -04:00
|
|
|
|
2016-02-18 18:19:43 -05:00
|
|
|
# Wrap a Gitlab::Git::Blob object, or return nil when given nil
|
|
|
|
#
|
|
|
|
# This method prevents the decorated object from evaluating to "truthy" when
|
|
|
|
# given a nil value. For example:
|
|
|
|
#
|
|
|
|
# blob = Blob.new(nil)
|
|
|
|
# puts "truthy" if blob # => "truthy"
|
|
|
|
#
|
|
|
|
# blob = Blob.decorate(nil)
|
|
|
|
# puts "truthy" if blob # No output
|
2020-02-04 10:08:40 -05:00
|
|
|
def self.decorate(blob, container = nil)
|
2016-02-18 18:19:43 -05:00
|
|
|
return if blob.nil?
|
|
|
|
|
2020-02-04 10:08:40 -05:00
|
|
|
new(blob, container)
|
2017-04-13 12:59:52 -04:00
|
|
|
end
|
|
|
|
|
2020-05-18 14:08:22 -04:00
|
|
|
def self.lazy(repository, commit_id, path, blob_size_limit: Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
|
|
|
|
BatchLoader.for([commit_id, path]).batch(key: repository) do |items, loader, args|
|
2020-02-02 01:08:56 -05:00
|
|
|
args[:key].blobs_at(items, blob_size_limit: blob_size_limit).each do |blob|
|
2018-11-23 06:26:17 -05:00
|
|
|
loader.call([blob.commit_id, blob.path], blob) if blob
|
2017-11-03 09:16:43 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-04 10:08:40 -05:00
|
|
|
def initialize(blob, container = nil)
|
|
|
|
@container = container
|
2017-04-13 12:59:52 -04:00
|
|
|
|
|
|
|
super(blob)
|
2016-02-18 18:19:43 -05:00
|
|
|
end
|
|
|
|
|
2017-11-03 09:16:43 -04:00
|
|
|
def inspect
|
|
|
|
"#<#{self.class.name} oid:#{id[0..8]} commit:#{commit_id[0..8]} path:#{path}>"
|
|
|
|
end
|
|
|
|
|
2016-09-12 11:25:35 -04:00
|
|
|
# Returns the data of the blob.
|
|
|
|
#
|
|
|
|
# If the blob is a text based blob the content is converted to UTF-8 and any
|
|
|
|
# invalid byte sequences are replaced.
|
|
|
|
def data
|
2018-12-13 12:49:05 -05:00
|
|
|
if binary_in_repo?
|
2016-09-12 11:25:35 -04:00
|
|
|
super
|
|
|
|
else
|
|
|
|
@data ||= super.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-06 17:20:24 -04:00
|
|
|
def load_all_data!
|
2019-04-09 11:21:16 -04:00
|
|
|
# Endpoint needed: https://gitlab.com/gitlab-org/gitaly/issues/756
|
2017-11-03 09:16:43 -04:00
|
|
|
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
2020-02-04 10:08:40 -05:00
|
|
|
super(repository) if container
|
2017-11-03 09:16:43 -04:00
|
|
|
end
|
2017-06-06 17:20:24 -04:00
|
|
|
end
|
|
|
|
|
2017-05-02 18:45:50 -04:00
|
|
|
def empty?
|
|
|
|
raw_size == 0
|
2016-04-14 06:44:35 -04:00
|
|
|
end
|
|
|
|
|
2017-05-02 18:45:50 -04:00
|
|
|
def external_storage_error?
|
|
|
|
if external_storage == :lfs
|
2020-04-22 08:09:29 -04:00
|
|
|
!repository.lfs_enabled?
|
2017-05-02 18:45:50 -04:00
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def stored_externally?
|
|
|
|
return @stored_externally if defined?(@stored_externally)
|
|
|
|
|
|
|
|
@stored_externally = external_storage && !external_storage_error?
|
|
|
|
end
|
|
|
|
|
2017-04-24 17:27:43 -04:00
|
|
|
# Returns the size of the file that this blob represents. If this blob is an
|
|
|
|
# LFS pointer, this is the size of the file stored in LFS. Otherwise, this is
|
|
|
|
# the size of the blob itself.
|
2017-04-13 13:14:08 -04:00
|
|
|
def raw_size
|
2017-05-02 18:45:50 -04:00
|
|
|
if stored_externally?
|
|
|
|
external_size
|
2017-04-13 13:14:08 -04:00
|
|
|
else
|
|
|
|
size
|
|
|
|
end
|
2017-04-10 17:23:19 -04:00
|
|
|
end
|
|
|
|
|
2017-04-24 17:27:43 -04:00
|
|
|
# Returns whether the file that this blob represents is binary. If this blob is
|
|
|
|
# an LFS pointer, we assume the file stored in LFS is binary, unless a
|
|
|
|
# text-based rich blob viewer matched on the file's extension. Otherwise, this
|
|
|
|
# depends on the type of the blob itself.
|
2018-12-13 12:49:05 -05:00
|
|
|
def binary?
|
2017-05-02 18:45:50 -04:00
|
|
|
if stored_externally?
|
2017-04-24 10:27:19 -04:00
|
|
|
if rich_viewer
|
|
|
|
rich_viewer.binary?
|
2018-08-03 09:24:26 -04:00
|
|
|
elsif known_extension?
|
2017-05-02 18:45:50 -04:00
|
|
|
false
|
|
|
|
elsif _mime_type
|
|
|
|
_mime_type.binary?
|
2017-04-24 10:27:19 -04:00
|
|
|
else
|
|
|
|
true
|
|
|
|
end
|
2017-04-13 13:14:08 -04:00
|
|
|
else
|
2018-12-13 12:49:05 -05:00
|
|
|
binary_in_repo?
|
2017-04-13 13:14:08 -04:00
|
|
|
end
|
2016-02-18 18:19:43 -05:00
|
|
|
end
|
|
|
|
|
2022-02-01 10:18:50 -05:00
|
|
|
def symlink?
|
|
|
|
mode == MODE_SYMLINK
|
|
|
|
end
|
|
|
|
|
2022-04-04 14:08:38 -04:00
|
|
|
def executable?
|
|
|
|
mode == MODE_EXECUTABLE
|
|
|
|
end
|
|
|
|
|
2017-04-13 13:14:08 -04:00
|
|
|
def extension
|
|
|
|
@extension ||= extname.downcase.delete('.')
|
2017-04-03 14:39:50 -04:00
|
|
|
end
|
|
|
|
|
2017-06-06 17:26:31 -04:00
|
|
|
def file_type
|
2017-10-11 11:47:03 -04:00
|
|
|
name = File.basename(path)
|
|
|
|
|
|
|
|
Gitlab::FileDetector.type_of(path) || Gitlab::FileDetector.type_of(name)
|
2017-06-06 17:26:31 -04:00
|
|
|
end
|
|
|
|
|
2017-04-13 13:14:08 -04:00
|
|
|
def video?
|
2019-09-23 14:06:14 -04:00
|
|
|
UploaderHelper::SAFE_VIDEO_EXT.include?(extension)
|
2017-03-16 13:04:58 -04:00
|
|
|
end
|
|
|
|
|
2019-10-09 08:06:13 -04:00
|
|
|
def audio?
|
|
|
|
UploaderHelper::SAFE_AUDIO_EXT.include?(extension)
|
|
|
|
end
|
|
|
|
|
2017-04-13 13:14:08 -04:00
|
|
|
def readable_text?
|
2018-12-13 12:49:05 -05:00
|
|
|
text_in_repo? && !stored_externally? && !truncated?
|
2017-04-06 06:02:24 -04:00
|
|
|
end
|
|
|
|
|
2017-04-13 13:14:08 -04:00
|
|
|
def simple_viewer
|
|
|
|
@simple_viewer ||= simple_viewer_class.new(self)
|
|
|
|
end
|
|
|
|
|
|
|
|
def rich_viewer
|
|
|
|
return @rich_viewer if defined?(@rich_viewer)
|
|
|
|
|
2017-04-24 17:27:43 -04:00
|
|
|
@rich_viewer = rich_viewer_class&.new(self)
|
2017-04-13 13:14:08 -04:00
|
|
|
end
|
|
|
|
|
2017-05-08 19:50:23 -04:00
|
|
|
def auxiliary_viewer
|
|
|
|
return @auxiliary_viewer if defined?(@auxiliary_viewer)
|
|
|
|
|
|
|
|
@auxiliary_viewer = auxiliary_viewer_class&.new(self)
|
|
|
|
end
|
|
|
|
|
2017-04-21 14:59:34 -04:00
|
|
|
def rendered_as_text?(ignore_errors: true)
|
2017-06-06 17:22:28 -04:00
|
|
|
simple_viewer.is_a?(BlobViewer::Text) && (ignore_errors || simple_viewer.render_error.nil?)
|
2017-04-13 13:14:08 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def show_viewer_switcher?
|
2017-04-21 14:59:34 -04:00
|
|
|
rendered_as_text? && rich_viewer
|
|
|
|
end
|
|
|
|
|
2017-06-07 17:07:57 -04:00
|
|
|
def expanded?
|
|
|
|
!!@expanded
|
|
|
|
end
|
|
|
|
|
2017-05-26 19:27:30 -04:00
|
|
|
def expand!
|
2017-06-07 17:07:57 -04:00
|
|
|
@expanded = true
|
2017-04-13 13:14:08 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2017-04-24 10:27:19 -04:00
|
|
|
def simple_viewer_class
|
|
|
|
if empty?
|
|
|
|
BlobViewer::Empty
|
2018-12-13 12:49:05 -05:00
|
|
|
elsif binary?
|
2017-04-24 10:27:19 -04:00
|
|
|
BlobViewer::Download
|
|
|
|
else # text
|
|
|
|
BlobViewer::Text
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def rich_viewer_class
|
2017-05-08 19:50:23 -04:00
|
|
|
viewer_class_from(RICH_VIEWERS)
|
|
|
|
end
|
|
|
|
|
|
|
|
def auxiliary_viewer_class
|
|
|
|
viewer_class_from(AUXILIARY_VIEWERS)
|
|
|
|
end
|
|
|
|
|
|
|
|
def viewer_class_from(classes)
|
2017-05-02 18:45:50 -04:00
|
|
|
return if empty? || external_storage_error?
|
2017-04-24 10:27:19 -04:00
|
|
|
|
2017-05-08 19:50:23 -04:00
|
|
|
verify_binary = !stored_externally?
|
2017-04-24 17:27:43 -04:00
|
|
|
|
2017-05-08 19:50:23 -04:00
|
|
|
classes.find { |viewer_class| viewer_class.can_render?(self, verify_binary: verify_binary) }
|
2017-04-24 10:27:19 -04:00
|
|
|
end
|
2016-02-18 18:19:43 -05:00
|
|
|
end
|