Commit 5a3e6fdf authored by Francisco Javier López's avatar Francisco Javier López 🔴

Fixing image lfs bug and also displaying text lfs

This commit, introduced in https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23812,
fixes a problem creating a displaying image diff notes when the image
is stored in LFS. The main problem was that `Gitlab::Diff::File` was
returning an invalid valid in `text?` for this kind of files.

It also fixes a rendering problem with other LFS files, like text
ones. They LFS pointer shouldn't be shown when LFS is enabled
for the project, but they were.
parent 77909a88
Pipeline #41464310 passed with stages
in 50 minutes and 49 seconds
...@@ -45,6 +45,9 @@ export default { ...@@ -45,6 +45,9 @@ export default {
isTextFile() { isTextFile() {
return this.diffFile.viewer.name === 'text'; return this.diffFile.viewer.name === 'text';
}, },
errorMessage() {
return this.diffFile.viewer.error;
},
diffFileCommentForm() { diffFileCommentForm() {
return this.getCommentFormForDiffFile(this.diffFile.file_hash); return this.getCommentFormForDiffFile(this.diffFile.file_hash);
}, },
...@@ -75,7 +78,7 @@ export default { ...@@ -75,7 +78,7 @@ export default {
<template> <template>
<div class="diff-content"> <div class="diff-content">
<div class="diff-viewer"> <div v-if="!errorMessage" class="diff-viewer">
<template v-if="isTextFile"> <template v-if="isTextFile">
<empty-file-viewer v-if="diffFile.empty" /> <empty-file-viewer v-if="diffFile.empty" />
<inline-diff-view <inline-diff-view
...@@ -129,5 +132,8 @@ export default { ...@@ -129,5 +132,8 @@ export default {
</div> </div>
</diff-viewer> </diff-viewer>
</div> </div>
<div v-else class="diff-viewer">
<div class="nothing-here-block" v-html="errorMessage"></div>
</div>
</div> </div>
</template> </template>
...@@ -260,7 +260,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -260,7 +260,7 @@ class Projects::BlobController < Projects::ApplicationController
extension: blob.extension, extension: blob.extension,
size: blob.raw_size, size: blob.raw_size,
mime_type: blob.mime_type, mime_type: blob.mime_type,
binary: blob.raw_binary?, binary: blob.binary?,
simple_viewer: blob.simple_viewer&.class&.partial_name, simple_viewer: blob.simple_viewer&.class&.partial_name,
rich_viewer: blob.rich_viewer&.class&.partial_name, rich_viewer: blob.rich_viewer&.class&.partial_name,
show_viewer_switcher: !!blob.show_viewer_switcher?, show_viewer_switcher: !!blob.show_viewer_switcher?,
......
...@@ -193,7 +193,7 @@ module BlobHelper ...@@ -193,7 +193,7 @@ module BlobHelper
def open_raw_blob_button(blob) def open_raw_blob_button(blob)
return if blob.empty? return if blob.empty?
return if blob.raw_binary? || blob.stored_externally? return if blob.binary? || blob.stored_externally?
title = 'Open raw' title = 'Open raw'
link_to icon('file-code-o'), blob_raw_path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: title, data: { container: 'body' } link_to icon('file-code-o'), blob_raw_path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: title, data: { container: 'body' }
......
...@@ -138,30 +138,6 @@ module DiffHelper ...@@ -138,30 +138,6 @@ module DiffHelper
!diff_file.deleted_file? && @merge_request && @merge_request.source_project !diff_file.deleted_file? && @merge_request && @merge_request.source_project
end end
def diff_render_error_reason(viewer)
case viewer.render_error
when :too_large
"it is too large"
when :server_side_but_stored_externally
case viewer.diff_file.external_storage
when :lfs
'it is stored in LFS'
else
'it is stored externally'
end
end
end
def diff_render_error_options(viewer)
diff_file = viewer.diff_file
options = []
blob_url = project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.file_path))
options << link_to('view the blob', blob_url)
options
end
def diff_file_changed_icon(diff_file) def diff_file_changed_icon(diff_file)
if diff_file.deleted_file? if diff_file.deleted_file?
"file-deletion" "file-deletion"
......
...@@ -110,7 +110,7 @@ module SnippetsHelper ...@@ -110,7 +110,7 @@ module SnippetsHelper
def embedded_snippet_raw_button def embedded_snippet_raw_button
blob = @snippet.blob blob = @snippet.blob
return if blob.empty? || blob.raw_binary? || blob.stored_externally? return if blob.empty? || blob.binary? || blob.stored_externally?
snippet_raw_url = if @snippet.is_a?(PersonalSnippet) snippet_raw_url = if @snippet.is_a?(PersonalSnippet)
raw_snippet_url(@snippet) raw_snippet_url(@snippet)
......
...@@ -102,7 +102,7 @@ class Blob < SimpleDelegator ...@@ -102,7 +102,7 @@ class Blob < SimpleDelegator
# If the blob is a text based blob the content is converted to UTF-8 and any # If the blob is a text based blob the content is converted to UTF-8 and any
# invalid byte sequences are replaced. # invalid byte sequences are replaced.
def data def data
if binary? if binary_in_repo?
super super
else else
@data ||= super.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) @data ||= super.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
...@@ -149,7 +149,7 @@ class Blob < SimpleDelegator ...@@ -149,7 +149,7 @@ class Blob < SimpleDelegator
# an LFS pointer, we assume the file stored in LFS is binary, unless a # 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 # text-based rich blob viewer matched on the file's extension. Otherwise, this
# depends on the type of the blob itself. # depends on the type of the blob itself.
def raw_binary? def binary?
if stored_externally? if stored_externally?
if rich_viewer if rich_viewer
rich_viewer.binary? rich_viewer.binary?
...@@ -161,7 +161,7 @@ class Blob < SimpleDelegator ...@@ -161,7 +161,7 @@ class Blob < SimpleDelegator
true true
end end
else else
binary? binary_in_repo?
end end
end end
...@@ -180,7 +180,7 @@ class Blob < SimpleDelegator ...@@ -180,7 +180,7 @@ class Blob < SimpleDelegator
end end
def readable_text? def readable_text?
text? && !stored_externally? && !truncated? text_in_repo? && !stored_externally? && !truncated?
end end
def simple_viewer def simple_viewer
...@@ -220,7 +220,7 @@ class Blob < SimpleDelegator ...@@ -220,7 +220,7 @@ class Blob < SimpleDelegator
def simple_viewer_class def simple_viewer_class
if empty? if empty?
BlobViewer::Empty BlobViewer::Empty
elsif raw_binary? elsif binary?
BlobViewer::Download BlobViewer::Download
else # text else # text
BlobViewer::Text BlobViewer::Text
......
...@@ -16,7 +16,7 @@ module BlobViewer ...@@ -16,7 +16,7 @@ module BlobViewer
def initialize(blob) def initialize(blob)
@blob = blob @blob = blob
@initially_binary = blob.binary? @initially_binary = blob.binary_in_repo?
end end
def self.partial_path def self.partial_path
...@@ -52,7 +52,7 @@ module BlobViewer ...@@ -52,7 +52,7 @@ module BlobViewer
end end
def self.can_render?(blob, verify_binary: true) def self.can_render?(blob, verify_binary: true)
return false if verify_binary && binary? != blob.binary? return false if verify_binary && binary? != blob.binary_in_repo?
return true if extensions&.include?(blob.extension) return true if extensions&.include?(blob.extension)
return true if file_types&.include?(blob.file_type) return true if file_types&.include?(blob.file_type)
...@@ -72,7 +72,7 @@ module BlobViewer ...@@ -72,7 +72,7 @@ module BlobViewer
end end
def binary_detected_after_load? def binary_detected_after_load?
!@initially_binary && blob.binary? !@initially_binary && blob.binary_in_repo?
end end
# This method is used on the server side to check whether we can attempt to # This method is used on the server side to check whether we can attempt to
......
...@@ -28,7 +28,7 @@ module BlobLike ...@@ -28,7 +28,7 @@ module BlobLike
nil nil
end end
def binary? def binary_in_repo?
false false
end end
......
...@@ -18,7 +18,7 @@ module DiffViewer ...@@ -18,7 +18,7 @@ module DiffViewer
def initialize(diff_file) def initialize(diff_file)
@diff_file = diff_file @diff_file = diff_file
@initially_binary = diff_file.binary? @initially_binary = diff_file.binary_in_repo?
end end
def self.partial_path def self.partial_path
...@@ -48,7 +48,7 @@ module DiffViewer ...@@ -48,7 +48,7 @@ module DiffViewer
def self.can_render_blob?(blob, verify_binary: true) def self.can_render_blob?(blob, verify_binary: true)
return true if blob.nil? return true if blob.nil?
return false if verify_binary && binary? != blob.binary? return false if verify_binary && binary? != blob.binary_in_repo?
return true if extensions&.include?(blob.extension) return true if extensions&.include?(blob.extension)
return true if file_types&.include?(blob.file_type) return true if file_types&.include?(blob.file_type)
...@@ -70,20 +70,49 @@ module DiffViewer ...@@ -70,20 +70,49 @@ module DiffViewer
end end
def binary_detected_after_load? def binary_detected_after_load?
!@initially_binary && diff_file.binary? !@initially_binary && diff_file.binary_in_repo?
end end
# This method is used on the server side to check whether we can attempt to # This method is used on the server side to check whether we can attempt to
# render the diff_file at all. Human-readable error messages are found in the # render the diff_file at all. The human-readable error message can be
# `BlobHelper#diff_render_error_reason` helper. # retrieved by #render_error_message.
def render_error def render_error
if too_large? if too_large?
:too_large :too_large
end end
end end
def render_error_message
return unless render_error
_("This %{viewer} could not be displayed because %{reason}. You can %{options} instead.") %
{
viewer: switcher_title,
reason: render_error_reason,
options: render_error_options.to_sentence(two_words_connector: _(' or '), last_word_connector: _(', or '))
}
end
def prepare! def prepare!
# To be overridden by subclasses # To be overridden by subclasses
end end
private
def render_error_options
options = []
blob_url = Gitlab::Routing.url_helpers.project_blob_path(diff_file.repository.project,
File.join(diff_file.content_sha, diff_file.file_path))
options << ActionController::Base.helpers.link_to(_('view the blob'), blob_url)
options
end
def render_error_reason
if render_error == :too_large
_("it is too large")
end
end
end end
end end
...@@ -9,6 +9,6 @@ module DiffViewer ...@@ -9,6 +9,6 @@ module DiffViewer
self.extensions = UploaderHelper::IMAGE_EXT self.extensions = UploaderHelper::IMAGE_EXT
self.binary = true self.binary = true
self.switcher_icon = 'picture-o' self.switcher_icon = 'picture-o'
self.switcher_title = 'image diff' self.switcher_title = _('image diff')
end end
end end
...@@ -7,7 +7,7 @@ module DiffViewer ...@@ -7,7 +7,7 @@ module DiffViewer
included do included do
self.type = :rich self.type = :rich
self.switcher_icon = 'file-text-o' self.switcher_icon = 'file-text-o'
self.switcher_title = 'rendered diff' self.switcher_title = _('rendered diff')
end end
end end
end end
...@@ -24,5 +24,17 @@ module DiffViewer ...@@ -24,5 +24,17 @@ module DiffViewer
super super
end end
private
def render_error_reason
return super unless render_error == :server_side_but_stored_externally
if diff_file.external_storage == :lfs
_('it is stored in LFS')
else
_('it is stored externally')
end
end
end end
end end
...@@ -7,7 +7,7 @@ module DiffViewer ...@@ -7,7 +7,7 @@ module DiffViewer
included do included do
self.type = :simple self.type = :simple
self.switcher_icon = 'code' self.switcher_icon = 'code'
self.switcher_title = 'source diff' self.switcher_title = _('source diff')
end end
end end
end end
...@@ -4,4 +4,7 @@ class DiffViewerEntity < Grape::Entity ...@@ -4,4 +4,7 @@ class DiffViewerEntity < Grape::Entity
# Partial name refers directly to a Rails feature, let's avoid # Partial name refers directly to a Rails feature, let's avoid
# using this on the frontend. # using this on the frontend.
expose :partial_name, as: :name expose :partial_name, as: :name
expose :error do |diff_viewer|
diff_viewer.render_error_message
end
end end
.nothing-here-block .nothing-here-block
= _("This %{viewer} could not be displayed because %{reason}.") % { viewer: viewer.switcher_title, reason: diff_render_error_reason(viewer) } = viewer.render_error_message.html_safe
You can
= diff_render_error_options(viewer).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ').html_safe
instead.
---
title: Fix bug commenting on LFS images
merge_request: 23812
author:
type: fixed
...@@ -12,7 +12,7 @@ module Gitlab ...@@ -12,7 +12,7 @@ module Gitlab
end end
def viewable? def viewable?
!large? && text? !large? && text_in_repo?
end end
MEGABYTE = 1024 * 1024 MEGABYTE = 1024 * 1024
...@@ -21,7 +21,7 @@ module Gitlab ...@@ -21,7 +21,7 @@ module Gitlab
size.to_i > MEGABYTE size.to_i > MEGABYTE
end end
def binary? def binary_in_repo?
# Large blobs aren't even loaded into memory # Large blobs aren't even loaded into memory
if data.nil? if data.nil?
true true
...@@ -40,8 +40,8 @@ module Gitlab ...@@ -40,8 +40,8 @@ module Gitlab
end end
end end
def text? def text_in_repo?
!binary? !binary_in_repo?
end end
def image? def image?
...@@ -113,7 +113,7 @@ module Gitlab ...@@ -113,7 +113,7 @@ module Gitlab
def content_type def content_type
# rubocop:disable Style/MultilineTernaryOperator # rubocop:disable Style/MultilineTernaryOperator
# rubocop:disable Style/NestedTernaryOperator # rubocop:disable Style/NestedTernaryOperator
@content_type ||= binary_mime_type? || binary? ? mime_type : @content_type ||= binary_mime_type? || binary_in_repo? ? mime_type :
(encoding ? "text/plain; charset=#{encoding.downcase}" : "text/plain") (encoding ? "text/plain; charset=#{encoding.downcase}" : "text/plain")
# rubocop:enable Style/NestedTernaryOperator # rubocop:enable Style/NestedTernaryOperator
# rubocop:enable Style/MultilineTernaryOperator # rubocop:enable Style/MultilineTernaryOperator
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
module Gitlab module Gitlab
module Diff module Diff
class File class File
include Gitlab::Utils::StrongMemoize
attr_reader :diff, :repository, :diff_refs, :fallback_diff_refs, :unique_identifier attr_reader :diff, :repository, :diff_refs, :fallback_diff_refs, :unique_identifier
delegate :new_file?, :deleted_file?, :renamed_file?, delegate :new_file?, :deleted_file?, :renamed_file?,
...@@ -232,12 +234,12 @@ module Gitlab ...@@ -232,12 +234,12 @@ module Gitlab
repository.attributes(file_path).fetch('diff') { true } repository.attributes(file_path).fetch('diff') { true }
end end
def binary? def binary_in_repo?
has_binary_notice? || try_blobs(:binary?) has_binary_notice? || try_blobs(:binary_in_repo?)
end end
def text? def text_in_repo?
!binary? !binary_in_repo?
end end
def external_storage_error? def external_storage_error?
...@@ -279,12 +281,16 @@ module Gitlab ...@@ -279,12 +281,16 @@ module Gitlab
valid_blobs.map(&:empty?).all? valid_blobs.map(&:empty?).all?
end end
def raw_binary? def binary?
try_blobs(:raw_binary?) strong_memoize(:is_binary) do
try_blobs(:binary?)
end
end end
def raw_text? def text?
!raw_binary? && !different_type? strong_memoize(:is_text) do
!binary? && !different_type?
end
end end
def simple_viewer def simple_viewer
...@@ -367,19 +373,19 @@ module Gitlab ...@@ -367,19 +373,19 @@ module Gitlab
return DiffViewer::NotDiffable unless diffable? return DiffViewer::NotDiffable unless diffable?
if content_changed? if content_changed?
if raw_text? if text?
DiffViewer::Text DiffViewer::Text
else else
DiffViewer::NoPreview DiffViewer::NoPreview
end end
elsif new_file? elsif new_file?
if raw_text? if text?
DiffViewer::Text DiffViewer::Text
else else
DiffViewer::Added DiffViewer::Added
end end
elsif deleted_file? elsif deleted_file?
if raw_text? if text?
DiffViewer::Text DiffViewer::Text
else else
DiffViewer::Deleted DiffViewer::Deleted
......
...@@ -100,7 +100,7 @@ module Gitlab ...@@ -100,7 +100,7 @@ module Gitlab
@loaded_all_data = @loaded_size == size @loaded_all_data = @loaded_size == size
end end
def binary? def binary_in_repo?
@binary.nil? ? super : @binary == true @binary.nil? ? super : @binary == true
end end
...@@ -174,7 +174,7 @@ module Gitlab ...@@ -174,7 +174,7 @@ module Gitlab
private private
def has_lfs_version_key? def has_lfs_version_key?
!empty? && text? && data.start_with?("version https://git-lfs.github.com/spec") !empty? && text_in_repo? && data.start_with?("version https://git-lfs.github.com/spec")
end end
end end
end end
......
...@@ -19,6 +19,9 @@ msgstr "" ...@@ -19,6 +19,9 @@ msgstr ""
msgid " Status" msgid " Status"
msgstr "" msgstr ""
msgid " or "
msgstr ""
msgid "%d addition" msgid "%d addition"
msgid_plural "%d additions" msgid_plural "%d additions"
msgstr[0] "" msgstr[0] ""
...@@ -185,6 +188,9 @@ msgstr "" ...@@ -185,6 +188,9 @@ msgstr ""
msgid "+ %{moreCount} more" msgid "+ %{moreCount} more"
msgstr "" msgstr ""
msgid ", or "
msgstr ""
msgid "- Runner is active and can process any new jobs" msgid "- Runner is active and can process any new jobs"
msgstr "" msgstr ""
...@@ -6698,7 +6704,7 @@ msgstr "" ...@@ -6698,7 +6704,7 @@ msgstr ""
msgid "Third party offers" msgid "Third party offers"
msgstr "" msgstr ""
msgid "This %{viewer} could not be displayed because %{reason}." msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr "" msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area." msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
...@@ -7888,6 +7894,9 @@ msgstr "" ...@@ -7888,6 +7894,9 @@ msgstr ""
msgid "https://your-bitbucket-server" msgid "https://your-bitbucket-server"
msgstr "" msgstr ""
msgid "image diff"
msgstr ""
msgid "import flow" msgid "import flow"
msgstr "" msgstr ""
...@@ -7900,6 +7909,15 @@ msgstr "" ...@@ -7900,6 +7909,15 @@ msgstr ""
msgid "issue boards" msgid "issue boards"
msgstr "" msgstr ""
msgid "it is stored externally"
msgstr ""
msgid "it is stored in LFS"
msgstr ""
msgid "it is too large"
msgstr ""
msgid "latest deployment" msgid "latest deployment"
msgstr "" msgstr ""
...@@ -8140,6 +8158,9 @@ msgstr "" ...@@ -8140,6 +8158,9 @@ msgstr ""
msgid "remove due date" msgid "remove due date"
msgstr "" msgstr ""
msgid "rendered diff"
msgstr ""
msgid "reply" msgid "reply"
msgid_plural "replies" msgid_plural "replies"
msgstr[0] "" msgstr[0] ""
...@@ -8151,6 +8172,9 @@ msgstr "" ...@@ -8151,6 +8172,9 @@ msgstr ""
msgid "source" msgid "source"
msgstr "" msgstr ""
msgid "source diff"
msgstr ""
msgid "spendCommand|%{slash_command} will update the sum of the time spent." msgid "spendCommand|%{slash_command} will update the sum of the time spent."
msgstr "" msgstr ""
...@@ -8172,6 +8196,9 @@ msgstr "" ...@@ -8172,6 +8196,9 @@ msgstr ""
msgid "view it on GitLab" msgid "view it on GitLab"
msgstr "" msgstr ""
msgid "view the blob"
msgstr ""
msgid "with %{additions} additions, %{deletions} deletions." msgid "with %{additions} additions, %{deletions} deletions."
msgstr "" msgstr ""
......
...@@ -90,9 +90,6 @@ describe 'Merge request > User creates image diff notes', :js do ...@@ -90,9 +90,6 @@ describe 'Merge request > User creates image diff notes', :js do
%w(inline parallel).each do |view| %w(inline parallel).each do |view|
context "#{view} view" do context "#{view} view" do
let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user) }
let(:path) { "files/images/ee_repo_logo.png" }
let(:position) do let(:position) do