Commit e35843f3 authored by Thong Kuah's avatar Thong Kuah

Merge branch '9490-store-designs-in-lfs' into 'master'

Support storing design blobs using LFS

See merge request gitlab-org/gitlab-ee!13389
parents 4a43187b 6001c02c
...@@ -50,7 +50,7 @@ module DesignManagement ...@@ -50,7 +50,7 @@ module DesignManagement
{ {
action: new_file?(design) ? :create : :update, action: new_file?(design) ? :create : :update,
file_path: design.full_path, file_path: design.full_path,
content: file.to_io content: file_content(file, design.full_path)
} }
end end
...@@ -98,6 +98,13 @@ module DesignManagement ...@@ -98,6 +98,13 @@ module DesignManagement
design.new_design? && existing_metadata.none? { |blob| blob.path == design.full_path } design.new_design? && existing_metadata.none? { |blob| blob.path == design.full_path }
end end
def file_content(file, full_path)
return file.to_io if Feature.disabled?(:store_designs_in_lfs)
transformer = Lfs::FileTransformer.new(project, target_branch)
transformer.new_file(full_path, file.to_io).content
end
def existing_metadata def existing_metadata
@existing_metadata ||= begin @existing_metadata ||= begin
paths = updated_designs.map(&:full_path) paths = updated_designs.map(&:full_path)
......
---
title: Allow design blobs to be stored in Git LFS
merge_request: 13389
author:
type: added
...@@ -12,7 +12,7 @@ describe Projects::DesignsController do ...@@ -12,7 +12,7 @@ describe Projects::DesignsController do
end end
describe 'GET #show' do describe 'GET #show' do
it 'serves the file using workhorse' do subject do
get(:show, get(:show,
params: { params: {
namespace_id: project.namespace, namespace_id: project.namespace,
...@@ -20,11 +20,22 @@ describe Projects::DesignsController do ...@@ -20,11 +20,22 @@ describe Projects::DesignsController do
id: design.id, id: design.id,
ref: 'HEAD' ref: 'HEAD'
}) })
end
it 'serves the file using workhorse' do
subject
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Disposition']).to eq('inline') expect(response.header['Content-Disposition']).to eq('inline')
expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true" expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end end
it_behaves_like 'a controller that can serve LFS files' do
let(:design) { create(:design, :with_lfs_file, issue: issue) }
let(:lfs_oid) { project.design_repository.blob_at('HEAD', design.full_path).lfs_oid }
let(:filename) { design.filename }
let(:filepath) { design.full_path }
end
end end
end end
...@@ -6,6 +6,14 @@ FactoryBot.define do ...@@ -6,6 +6,14 @@ FactoryBot.define do
project { issue.project } project { issue.project }
sequence(:filename) { |n| "homescreen-#{n}.jpg" } sequence(:filename) { |n| "homescreen-#{n}.jpg" }
trait :with_lfs_file do
with_file
transient do
file Gitlab::Git::LfsPointerFile.new('').pointer
end
end
trait :with_file do trait :with_file do
transient do transient do
versions_count 1 versions_count 1
......
...@@ -131,6 +131,36 @@ describe DesignManagement::SaveDesignsService do ...@@ -131,6 +131,36 @@ describe DesignManagement::SaveDesignsService do
end end
end end
context 'when LFS is enabled' do
before do
allow(project).to receive(:lfs_enabled?).and_return(true)
end
context 'and the `store_designs_in_lfs` feature is enabled' do
before do
stub_feature_flags(store_designs_in_lfs: true)
expect_next_instance_of(Lfs::FileTransformer) do |transformer|
expect(transformer).to receive(:lfs_file?).and_return(true)
end
end
it 'saves the design to LFS' do
expect { service.execute }.to change { LfsObject.count }.by(1)
end
end
context 'and the `store_designs_in_lfs` feature is not enabled' do
before do
stub_feature_flags(store_designs_in_lfs: false)
end
it 'does not save the design to LFS' do
expect { service.execute }.not_to change { LfsObject.count }
end
end
end
context 'when the user is not allowed to upload designs' do context 'when the user is not allowed to upload designs' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
...@@ -39,7 +39,7 @@ describe Projects::AvatarsController do ...@@ -39,7 +39,7 @@ describe Projects::AvatarsController do
end end
context 'when the avatar is stored in lfs' do context 'when the avatar is stored in lfs' do
it_behaves_like 'repository lfs file load' do it_behaves_like 'a controller that can serve LFS files' do
let(:filename) { 'lfs_object.iso' } let(:filename) { 'lfs_object.iso' }
let(:filepath) { "files/lfs/#{filename}" } let(:filepath) { "files/lfs/#{filename}" }
end end
......
...@@ -42,7 +42,7 @@ describe Projects::RawController do ...@@ -42,7 +42,7 @@ describe Projects::RawController do
end end
end end
it_behaves_like 'repository lfs file load' do it_behaves_like 'a controller that can serve LFS files' do
let(:filename) { 'lfs_object.iso' } let(:filename) { 'lfs_object.iso' }
let(:filepath) { "be93687/files/lfs/#{filename}" } let(:filepath) { "be93687/files/lfs/#{filename}" }
end end
......
...@@ -9,8 +9,7 @@ ...@@ -9,8 +9,7 @@
# - `filepath`: path of the file (contains filename) # - `filepath`: path of the file (contains filename)
# - `subject`: the request to be made to the controller. Example: # - `subject`: the request to be made to the controller. Example:
# subject { get :show, namespace_id: project.namespace, project_id: project } # subject { get :show, namespace_id: project.namespace, project_id: project }
shared_examples 'repository lfs file load' do shared_examples 'a controller that can serve LFS files' do
context 'when file is stored in lfs' do
let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' } let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' }
let(:lfs_size) { '1575078' } let(:lfs_size) { '1575078' }
let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) } let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) }
...@@ -28,11 +27,13 @@ shared_examples 'repository lfs file load' do ...@@ -28,11 +27,13 @@ shared_examples 'repository lfs file load' do
end end
it 'serves the file' do it 'serves the file' do
lfs_uploader = LfsObjectUploader.new(lfs_object)
# Notice the filename= is omitted from the disposition; this is because # Notice the filename= is omitted from the disposition; this is because
# Rails 5 will append this header in send_file # Rails 5 will append this header in send_file
expect(controller).to receive(:send_file) expect(controller).to receive(:send_file)
.with( .with(
"#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", File.join(lfs_uploader.root, lfs_uploader.store_dir, lfs_uploader.filename),
filename: filename, filename: filename,
disposition: %Q(attachment; filename*=UTF-8''#{filename})) disposition: %Q(attachment; filename*=UTF-8''#{filename}))
...@@ -62,7 +63,7 @@ shared_examples 'repository lfs file load' do ...@@ -62,7 +63,7 @@ shared_examples 'repository lfs file load' do
file_uri = URI.parse(response.location) file_uri = URI.parse(response.location)
params = CGI.parse(file_uri.query) params = CGI.parse(file_uri.query)
expect(params["response-content-disposition"].first).to eq(%q(attachment; filename="lfs_object.iso"; filename*=UTF-8''lfs_object.iso)) expect(params["response-content-disposition"].first).to eq(%Q(attachment; filename="#{filename}"; filename*=UTF-8''#{filename}))
end end
end end
end end
...@@ -91,5 +92,4 @@ shared_examples 'repository lfs file load' do ...@@ -91,5 +92,4 @@ shared_examples 'repository lfs file load' do
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end end
end end
end
end end
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