From 84ee2ddbcd850d29ae852333c57e2e8381f5a535 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Fri, 30 Mar 2018 16:32:21 +0200 Subject: [PATCH 1/4] Export LFS Objects when exporting a project The LFS files will be included in the `lfs-objects` directory in the archive. --- .../projects/import_export/export_service.rb | 6 ++- app/views/projects/_export.html.haml | 2 +- doc/user/project/settings/import_export.md | 2 +- lib/gitlab/import_export/lfs_saver.rb | 48 +++++++++++++++++++ .../gitlab/import_export/lfs_saver_spec.rb | 39 +++++++++++++++ .../import_export/export_service_spec.rb | 43 +++++++++++++++++ 6 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 lib/gitlab/import_export/lfs_saver.rb create mode 100644 spec/lib/gitlab/import_export/lfs_saver_spec.rb diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index 402cddd3ec1..7bf0b90b491 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -28,7 +28,7 @@ module Projects end def save_services - [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save) + [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver, lfs_saver].all?(&:save) end def version_saver @@ -55,6 +55,10 @@ module Projects Gitlab::ImportExport::WikiRepoSaver.new(project: project, shared: @shared) end + def lfs_saver + Gitlab::ImportExport::LfsSaver.new(project: project, shared: @shared) + end + def cleanup_and_notify_error Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}") diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml index 825bfd0707f..1e7d9444986 100644 --- a/app/views/projects/_export.html.haml +++ b/app/views/projects/_export.html.haml @@ -21,11 +21,11 @@ %li Project uploads %li Project configuration including web hooks and services %li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities + %li LFS objects %p The following items will NOT be exported: %ul %li Job traces and artifacts - %li LFS objects %li Container registry images %li CI variables %li Any encrypted tokens diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index dedf102fc37..eb0ac221e30 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -57,11 +57,11 @@ The following items will be exported: - Project configuration including web hooks and services - Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities +- LFS objects The following items will NOT be exported: - Build traces and artifacts -- LFS objects - Container registry images - CI variables - Any encrypted tokens diff --git a/lib/gitlab/import_export/lfs_saver.rb b/lib/gitlab/import_export/lfs_saver.rb new file mode 100644 index 00000000000..bb7a070fe15 --- /dev/null +++ b/lib/gitlab/import_export/lfs_saver.rb @@ -0,0 +1,48 @@ +module Gitlab + module ImportExport + class LfsSaver + include Gitlab::ImportExport::CommandLineUtil + + def initialize(project:, shared:) + @project = project + @shared = shared + end + + def save + return true if @project.lfs_objects.empty? + + @project.lfs_objects.each do |lfs_object| + save_lfs_object(lfs_object) + end + + true + rescue => e + @shared.error(e) + + false + end + + private + + def save_lfs_object(lfs_object) + if lfs_object.local_store? + copy_file_for_lfs_object(lfs_object) + else + raise NotImplementedError.new "Exporting files from object storage is not yet supported" + end + end + + def copy_file_for_lfs_object(lfs_object) + copy_files(lfs_object.file.path, destination_path_for_object(lfs_object)) + end + + def destination_path_for_object(lfs_object) + File.join(lfs_export_path, lfs_object.oid) + end + + def lfs_export_path + File.join(@shared.export_path, 'lfs-objects') + end + end + end +end diff --git a/spec/lib/gitlab/import_export/lfs_saver_spec.rb b/spec/lib/gitlab/import_export/lfs_saver_spec.rb new file mode 100644 index 00000000000..e2237cd22cf --- /dev/null +++ b/spec/lib/gitlab/import_export/lfs_saver_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::LfsSaver do + let(:shared) { project.import_export_shared } + let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } + let(:project) { create(:project) } + + subject(:saver) { described_class.new(project: project, shared: shared) } + + before do + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + FileUtils.mkdir_p(shared.export_path) + end + + after do + FileUtils.rm_rf(shared.export_path) + end + + describe '#save' do + context 'when the project has LFS objects' do + let(:lfs_object) { create(:lfs_object, :with_file) } + before do + project.lfs_objects << lfs_object\ + end + + it 'does not cause errors' do + saver.save + + expect(shared.errors).to be_empty + end + + it 'copies the file in the correct location when there is an lfs object' do + saver.save + + expect(File).to exist("#{shared.export_path}/lfs-objects/#{lfs_object.oid}") + end + end + end +end diff --git a/spec/services/projects/import_export/export_service_spec.rb b/spec/services/projects/import_export/export_service_spec.rb index 51491c7d529..f9e5530bc9d 100644 --- a/spec/services/projects/import_export/export_service_spec.rb +++ b/spec/services/projects/import_export/export_service_spec.rb @@ -8,6 +8,49 @@ describe Projects::ImportExport::ExportService do let(:service) { described_class.new(project, user) } let!(:after_export_strategy) { Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy.new } + it 'saves the version' do + expect(Gitlab::ImportExport::VersionSaver).to receive(:new).and_call_original + + service.execute + end + + it 'saves the avatar' do + expect(Gitlab::ImportExport::AvatarSaver).to receive(:new).and_call_original + + service.execute + end + + it 'saves the models' do + expect(Gitlab::ImportExport::ProjectTreeSaver).to receive(:new).and_call_original + + service.execute + end + + it 'saves the uploads' do + expect(Gitlab::ImportExport::UploadsSaver).to receive(:new).and_call_original + + service.execute + end + + it 'saves the repo' do + # once for the normal repo, once for the wiki + expect(Gitlab::ImportExport::RepoSaver).to receive(:new).twice.and_call_original + + service.execute + end + + it 'saves the lfs objects' do + expect(Gitlab::ImportExport::LfsSaver).to receive(:new).and_call_original + + service.execute + end + + it 'saves the wiki repo' do + expect(Gitlab::ImportExport::WikiRepoSaver).to receive(:new).and_call_original + + service.execute + end + context 'when all saver services succeed' do before do allow(service).to receive(:save_services).and_return(true) From 79cb4d99c0e47bfd988788ff38871e668367dfbf Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Fri, 30 Mar 2018 19:45:58 +0200 Subject: [PATCH 2/4] Import projects with LFS objects If the LFS object already exist, we'll link it tot he existing one, if not we'll create it. --- lib/gitlab/import_export/importer.rb | 11 ++- lib/gitlab/import_export/lfs_restorer.rb | 43 ++++++++++ lib/gitlab/import_export/lfs_saver.rb | 2 +- spec/fixtures/exported-project.gz | Bin 0 -> 2306 bytes .../lib/gitlab/import_export/importer_spec.rb | 64 +++++++++++++++ .../gitlab/import_export/lfs_restorer_spec.rb | 75 ++++++++++++++++++ .../gitlab/import_export/lfs_saver_spec.rb | 3 +- 7 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 lib/gitlab/import_export/lfs_restorer.rb create mode 100644 spec/fixtures/exported-project.gz create mode 100644 spec/lib/gitlab/import_export/importer_spec.rb create mode 100644 spec/lib/gitlab/import_export/lfs_restorer_spec.rb diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb index c38df9102eb..c490bf059d2 100644 --- a/lib/gitlab/import_export/importer.rb +++ b/lib/gitlab/import_export/importer.rb @@ -13,7 +13,7 @@ module Gitlab end def execute - if import_file && check_version! && [repo_restorer, wiki_restorer, project_tree, avatar_restorer, uploads_restorer].all?(&:restore) + if import_file && check_version! && restorers.all?(&:restore) project_tree.restored_project else raise Projects::ImportService::Error.new(@shared.errors.join(', ')) @@ -24,6 +24,11 @@ module Gitlab private + def restorers + [repo_restorer, wiki_restorer, project_tree, avatar_restorer, + uploads_restorer, lfs_restorer] + end + def import_file Gitlab::ImportExport::FileImporter.import(archive_file: @archive_file, shared: @shared) @@ -60,6 +65,10 @@ module Gitlab Gitlab::ImportExport::UploadsRestorer.new(project: project_tree.restored_project, shared: @shared) end + def lfs_restorer + Gitlab::ImportExport::LfsRestorer.new(project: project_tree.restored_project, shared: @shared) + end + def path_with_namespace File.join(@project.namespace.full_path, @project.path) end diff --git a/lib/gitlab/import_export/lfs_restorer.rb b/lib/gitlab/import_export/lfs_restorer.rb new file mode 100644 index 00000000000..4f144d5c8e6 --- /dev/null +++ b/lib/gitlab/import_export/lfs_restorer.rb @@ -0,0 +1,43 @@ +module Gitlab + module ImportExport + class LfsRestorer + def initialize(project:, shared:) + @project = project + @shared = shared + end + + def restore + return true if lfs_file_paths.empty? + + lfs_file_paths.each do |file_path| + link_or_create_lfs_object!(file_path) + end + + true + rescue => e + @shared.error(e) + false + end + + private + + def link_or_create_lfs_object!(path) + size = File.size(path) + oid = LfsObject.calculate_oid(path) + + lfs_object = LfsObject.find_or_initialize_by(oid: oid, size: size) + lfs_object.file = File.open(path) unless lfs_object.file&.exists? + + @project.lfs_storage_project.lfs_objects << lfs_object + end + + def lfs_file_paths + @lfs_file_paths ||= Dir.glob("#{lfs_storage_path}/*") + end + + def lfs_storage_path + File.join(@shared.export_path, 'lfs-objects') + end + end + end +end diff --git a/lib/gitlab/import_export/lfs_saver.rb b/lib/gitlab/import_export/lfs_saver.rb index bb7a070fe15..d796440902b 100644 --- a/lib/gitlab/import_export/lfs_saver.rb +++ b/lib/gitlab/import_export/lfs_saver.rb @@ -11,7 +11,7 @@ module Gitlab def save return true if @project.lfs_objects.empty? - @project.lfs_objects.each do |lfs_object| + @project.lfs_storage_project.lfs_objects.each do |lfs_object| save_lfs_object(lfs_object) end diff --git a/spec/fixtures/exported-project.gz b/spec/fixtures/exported-project.gz new file mode 100644 index 0000000000000000000000000000000000000000..352384f16c83737e4c4bdfb55b8d413e0aeb3208 GIT binary patch literal 2306 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 literal 0 HcmV?d00001 diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb new file mode 100644 index 00000000000..d75416f2a62 --- /dev/null +++ b/spec/lib/gitlab/import_export/importer_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::Importer do + let(:test_path) { "#{Dir.tmpdir}/importer_spec" } + let(:shared) { project.import_export_shared } + let(:project) { create(:project, import_source: File.join(test_path, 'exported-project.gz')) } + + subject(:importer) { described_class.new(project) } + + before do + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path) + FileUtils.mkdir_p(shared.export_path) + FileUtils.cp(Rails.root.join('spec', 'fixtures', 'exported-project.gz'), test_path) + end + + after do + FileUtils.rm_rf(test_path) + end + + describe '#execute' do + it 'succeeds' do + importer.execute + + expect(shared.errors).to be_empty + end + + it 'extracts the archive' do + expect(Gitlab::ImportExport::FileImporter).to receive(:import).and_call_original + + importer.execute + end + + it 'checks the version' do + expect(Gitlab::ImportExport::VersionChecker).to receive(:check!).and_call_original + + importer.execute + end + + context 'all restores are executed' do + [ + Gitlab::ImportExport::AvatarRestorer, + Gitlab::ImportExport::RepoRestorer, + Gitlab::ImportExport::WikiRestorer, + Gitlab::ImportExport::UploadsRestorer, + Gitlab::ImportExport::LfsRestorer + ].each do |restorer| + it "calls the #{restorer}" do + fake_restorer = double(restorer.to_s) + + expect(fake_restorer).to receive(:restore).and_return(true).at_least(1) + expect(restorer).to receive(:new).and_return(fake_restorer).at_least(1) + + importer.execute + end + end + + it 'restores the ProjectTree' do + expect(Gitlab::ImportExport::ProjectTreeRestorer).to receive(:new).and_call_original + + importer.execute + end + end + end +end diff --git a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb new file mode 100644 index 00000000000..70eeb9ee66b --- /dev/null +++ b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::LfsRestorer do + include UploadHelpers + + let(:export_path) { "#{Dir.tmpdir}/lfs_object_restorer_spec" } + let(:project) { create(:project) } + let(:shared) { project.import_export_shared } + subject(:restorer) { described_class.new(project: project, shared: shared) } + + before do + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + FileUtils.mkdir_p(shared.export_path) + end + + after do + FileUtils.rm_rf(shared.export_path) + end + + describe '#restore' do + context 'when the archive contains lfs files' do + let(:dummy_lfs_file_path) { File.join(shared.export_path, 'lfs-objects', 'dummy') } + + def create_lfs_object_with_content(content) + dummy_lfs_file = Tempfile.new('existing') + File.write(dummy_lfs_file.path, content) + size = dummy_lfs_file.size + oid = LfsObject.calculate_oid(dummy_lfs_file.path) + LfsObject.create!(oid: oid, size: size, file: dummy_lfs_file) + end + + before do + FileUtils.mkdir_p(File.dirname(dummy_lfs_file_path)) + File.write(dummy_lfs_file_path, 'not very large') + allow(restorer).to receive(:lfs_file_paths).and_return([dummy_lfs_file_path]) + end + + it 'creates an lfs object for the project' do + expect { restorer.restore }.to change { project.reload.lfs_objects.size }.by(1) + end + + it 'assigns the file correctly' do + restorer.restore + + expect(project.lfs_objects.first.file.read).to eq('not very large') + end + + it 'links an existing LFS object if it existed' do + lfs_object = create_lfs_object_with_content('not very large') + + restorer.restore + + expect(project.lfs_objects).to include(lfs_object) + end + + it 'succeeds' do + expect(restorer.restore).to be_truthy + expect(shared.errors).to be_empty + end + + it 'stores the upload' do + expect_any_instance_of(LfsObjectUploader).to receive(:store!) + + restorer.restore + end + end + + context 'without any LFS-objects' do + it 'succeeds' do + expect(restorer.restore).to be_truthy + expect(shared.errors).to be_empty + end + end + end +end diff --git a/spec/lib/gitlab/import_export/lfs_saver_spec.rb b/spec/lib/gitlab/import_export/lfs_saver_spec.rb index e2237cd22cf..e62afac1c48 100644 --- a/spec/lib/gitlab/import_export/lfs_saver_spec.rb +++ b/spec/lib/gitlab/import_export/lfs_saver_spec.rb @@ -19,8 +19,9 @@ describe Gitlab::ImportExport::LfsSaver do describe '#save' do context 'when the project has LFS objects' do let(:lfs_object) { create(:lfs_object, :with_file) } + before do - project.lfs_objects << lfs_object\ + project.lfs_objects << lfs_object end it 'does not cause errors' do From 10d0f438d823418a4a4f4e4f52e8f35ed10f2a05 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Tue, 3 Apr 2018 18:54:42 +0200 Subject: [PATCH 3/4] Download LFS-files from object storage for exports Downloading the stream directly to the archive. In order to avoid conflicts with the cache. --- .../unreleased/bvl-export-import-lfs.yml | 5 ++++ lib/gitlab/import_export/lfs_saver.rb | 11 ++++++++- .../gitlab/import_export/lfs_saver_spec.rb | 24 ++++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/bvl-export-import-lfs.yml diff --git a/changelogs/unreleased/bvl-export-import-lfs.yml b/changelogs/unreleased/bvl-export-import-lfs.yml new file mode 100644 index 00000000000..dd1f499c3a3 --- /dev/null +++ b/changelogs/unreleased/bvl-export-import-lfs.yml @@ -0,0 +1,5 @@ +--- +title: Support LFS objects when importing/exporting GitLab project archives +merge_request: 18115 +author: +type: added diff --git a/lib/gitlab/import_export/lfs_saver.rb b/lib/gitlab/import_export/lfs_saver.rb index d796440902b..2e3b9ea200d 100644 --- a/lib/gitlab/import_export/lfs_saver.rb +++ b/lib/gitlab/import_export/lfs_saver.rb @@ -28,7 +28,16 @@ module Gitlab if lfs_object.local_store? copy_file_for_lfs_object(lfs_object) else - raise NotImplementedError.new "Exporting files from object storage is not yet supported" + download_file_for_lfs_object(lfs_object) + end + end + + def download_file_for_lfs_object(lfs_object) + destination = destination_path_for_object(lfs_object) + mkdir_p(File.dirname(destination)) + + File.open(destination, 'w') do |file| + IO.copy_stream(URI.parse(lfs_object.file.url).open, file) end end diff --git a/spec/lib/gitlab/import_export/lfs_saver_spec.rb b/spec/lib/gitlab/import_export/lfs_saver_spec.rb index e62afac1c48..9b0e21deb2e 100644 --- a/spec/lib/gitlab/import_export/lfs_saver_spec.rb +++ b/spec/lib/gitlab/import_export/lfs_saver_spec.rb @@ -17,7 +17,7 @@ describe Gitlab::ImportExport::LfsSaver do end describe '#save' do - context 'when the project has LFS objects' do + context 'when the project has LFS objects locally stored' do let(:lfs_object) { create(:lfs_object, :with_file) } before do @@ -36,5 +36,27 @@ describe Gitlab::ImportExport::LfsSaver do expect(File).to exist("#{shared.export_path}/lfs-objects/#{lfs_object.oid}") end end + + context 'when the LFS objects are stored in object storage' do + let(:lfs_object) { create(:lfs_object, :object_storage) } + + before do + allow(LfsObjectUploader).to receive(:object_store_enabled?).and_return(true) + allow(lfs_object.file).to receive(:url).and_return('http://my-object-storage.local') + project.lfs_objects << lfs_object + end + + it 'downloads the file to include in an archive' do + fake_uri = double + exported_file_path = "#{shared.export_path}/lfs-objects/#{lfs_object.oid}" + + expect(fake_uri).to receive(:open).and_return(StringIO.new('LFS file content')) + expect(URI).to receive(:parse).with('http://my-object-storage.local').and_return(fake_uri) + + saver.save + + expect(File.read(exported_file_path)).to eq('LFS file content') + end + end end end From 48b17e991a960c006da278d4c1f534b1db81d070 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Wed, 4 Apr 2018 16:40:58 +0200 Subject: [PATCH 4/4] Add helper for accessing lfs_objects for project This makes accessing LFS Objects for a project easier project.lfs_storage_project.lfs_objects` becomes project.all_lfs_objects This will make the refactor in https://gitlab.com/gitlab-org/gitlab-ce/issues/39769 easier to deal with. --- app/controllers/projects/lfs_api_controller.rb | 2 +- app/models/project.rb | 10 ++++++++++ lib/gitlab/checks/lfs_integrity.rb | 3 +-- lib/gitlab/import_export/lfs_restorer.rb | 2 +- lib/gitlab/import_export/lfs_saver.rb | 4 +--- spec/models/project_spec.rb | 16 ++++++++++++++++ 6 files changed, 30 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb index c77f10ef1dd..ee4ed674110 100644 --- a/app/controllers/projects/lfs_api_controller.rb +++ b/app/controllers/projects/lfs_api_controller.rb @@ -41,7 +41,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController def existing_oids @existing_oids ||= begin - storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid) + project.all_lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid) end end diff --git a/app/models/project.rb b/app/models/project.rb index 714a15ade9c..32289106f28 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1066,6 +1066,16 @@ class Project < ActiveRecord::Base end end + # This will return all `lfs_objects` that are accessible to the project. + # So this might be `self.lfs_objects` if the project is not part of a fork + # network, or it is the base of the fork network. + # + # TODO: refactor this to get the correct lfs objects when implementing + # https://gitlab.com/gitlab-org/gitlab-ce/issues/39769 + def all_lfs_objects + lfs_storage_project.lfs_objects + end + def personal? !group end diff --git a/lib/gitlab/checks/lfs_integrity.rb b/lib/gitlab/checks/lfs_integrity.rb index f7276a380dc..f0e5773ec3c 100644 --- a/lib/gitlab/checks/lfs_integrity.rb +++ b/lib/gitlab/checks/lfs_integrity.rb @@ -15,8 +15,7 @@ module Gitlab return false unless new_lfs_pointers.present? - existing_count = @project.lfs_storage_project - .lfs_objects + existing_count = @project.all_lfs_objects .where(oid: new_lfs_pointers.map(&:lfs_oid)) .count diff --git a/lib/gitlab/import_export/lfs_restorer.rb b/lib/gitlab/import_export/lfs_restorer.rb index 4f144d5c8e6..b28c3c161b7 100644 --- a/lib/gitlab/import_export/lfs_restorer.rb +++ b/lib/gitlab/import_export/lfs_restorer.rb @@ -28,7 +28,7 @@ module Gitlab lfs_object = LfsObject.find_or_initialize_by(oid: oid, size: size) lfs_object.file = File.open(path) unless lfs_object.file&.exists? - @project.lfs_storage_project.lfs_objects << lfs_object + @project.all_lfs_objects << lfs_object end def lfs_file_paths diff --git a/lib/gitlab/import_export/lfs_saver.rb b/lib/gitlab/import_export/lfs_saver.rb index 2e3b9ea200d..29410e2331c 100644 --- a/lib/gitlab/import_export/lfs_saver.rb +++ b/lib/gitlab/import_export/lfs_saver.rb @@ -9,9 +9,7 @@ module Gitlab end def save - return true if @project.lfs_objects.empty? - - @project.lfs_storage_project.lfs_objects.each do |lfs_object| + @project.all_lfs_objects.each do |lfs_object| save_lfs_object(lfs_object) end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 0e560be9eaa..8bd62dcdccb 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2022,6 +2022,22 @@ describe Project do expect(forked_project.lfs_storage_project).to eq forked_project end end + + describe '#all_lfs_objects' do + let(:lfs_object) { create(:lfs_object) } + + before do + project.lfs_objects << lfs_object + end + + it 'returns the lfs object for a project' do + expect(project.all_lfs_objects).to contain_exactly(lfs_object) + end + + it 'returns the lfs object for a fork' do + expect(forked_project.all_lfs_objects).to contain_exactly(lfs_object) + end + end end describe '#pushes_since_gc' do