Commit 7b3590e7 authored by Nick Thomas's avatar Nick Thomas

Correctly preserve LFS objects in design or wiki repositories

The gitlab:cleanup:orphan_lfs_file_references rake task forgets to take
account of design and wiki repositories, both of which contain LFS
objects. Add them to the list of repositories we get OIDs from.
parent 9e7619fa
...@@ -19,6 +19,7 @@ class LfsObjectsProject < ApplicationRecord ...@@ -19,6 +19,7 @@ class LfsObjectsProject < ApplicationRecord
} }
scope :project_id_in, ->(ids) { where(project_id: ids) } scope :project_id_in, ->(ids) { where(project_id: ids) }
scope :lfs_object_in, -> (lfs_objects) { where(lfs_object: lfs_objects) }
private private
......
...@@ -2702,9 +2702,11 @@ class Project < ApplicationRecord ...@@ -2702,9 +2702,11 @@ class Project < ApplicationRecord
end end
def oids(objects, oids: []) def oids(objects, oids: [])
collection = oids.any? ? objects.where(oid: oids) : objects objects = objects.where(oid: oids) if oids.any?
collection.pluck(:oid) [].tap do |out|
objects.each_batch { |relation| out.concat(relation.pluck(:oid)) }
end
end end
end end
......
---
title: Correctly preserve LFS objects in design or wiki repositories
merge_request: 41352
author:
type: fixed
...@@ -25,7 +25,7 @@ module Gitlab ...@@ -25,7 +25,7 @@ module Gitlab
private private
def remove_orphan_references def remove_orphan_references
invalid_references = project.lfs_objects_projects.where(lfs_object: orphan_objects) # rubocop:disable CodeReuse/ActiveRecord invalid_references = project.lfs_objects_projects.lfs_object_in(orphan_objects)
if dry_run if dry_run
log_info("Found invalid references: #{invalid_references.count}") log_info("Found invalid references: #{invalid_references.count}")
...@@ -41,26 +41,22 @@ module Gitlab ...@@ -41,26 +41,22 @@ module Gitlab
end end
end end
def lfs_oids_from_repository def orphan_objects
project.repository.gitaly_blob_client.get_all_lfs_pointers.map(&:lfs_oid) # Get these first so racing with a git push can't remove any LFS objects
end oids = project.lfs_objects_oids
def orphan_oids
lfs_oids_from_database - lfs_oids_from_repository
end
def lfs_oids_from_database repos = [
oids = [] project.repository,
project.design_repository,
project.wiki.repository
].select(&:exists?)
project.lfs_objects.each_batch do |relation| repos.flat_map do |repo|
oids += relation.pluck(:oid) # rubocop:disable CodeReuse/ActiveRecord oids -= repo.gitaly_blob_client.get_all_lfs_pointers.map(&:lfs_oid)
end end
oids # The remaining OIDs are not used by any repository, so are orphans
end LfsObject.for_oids(oids)
def orphan_objects
LfsObject.where(oid: orphan_oids) # rubocop:disable CodeReuse/ActiveRecord
end end
def log_info(msg) def log_info(msg)
......
...@@ -9,6 +9,8 @@ RSpec.describe Gitlab::Cleanup::OrphanLfsFileReferences do ...@@ -9,6 +9,8 @@ RSpec.describe Gitlab::Cleanup::OrphanLfsFileReferences do
let!(:invalid_reference) { create(:lfs_objects_project, project: project, lfs_object: lfs_object) } let!(:invalid_reference) { create(:lfs_objects_project, project: project, lfs_object: lfs_object) }
subject(:service) { described_class.new(project, logger: null_logger, dry_run: dry_run) }
before do before do
allow(null_logger).to receive(:info) allow(null_logger).to receive(:info)
...@@ -21,25 +23,66 @@ RSpec.describe Gitlab::Cleanup::OrphanLfsFileReferences do ...@@ -21,25 +23,66 @@ RSpec.describe Gitlab::Cleanup::OrphanLfsFileReferences do
end end
context 'dry run' do context 'dry run' do
let(:dry_run) { true }
it 'prints messages and does not delete references' do it 'prints messages and does not delete references' do
expect(null_logger).to receive(:info).with("[DRY RUN] Looking for orphan LFS files for project #{project.name_with_namespace}") expect(null_logger).to receive(:info).with("[DRY RUN] Looking for orphan LFS files for project #{project.name_with_namespace}")
expect(null_logger).to receive(:info).with("[DRY RUN] Found invalid references: 1") expect(null_logger).to receive(:info).with("[DRY RUN] Found invalid references: 1")
expect { described_class.new(project, logger: null_logger).run! } expect { service.run! }.not_to change { project.lfs_objects.count }
.not_to change { project.lfs_objects.count }
end end
end end
context 'regular run' do context 'regular run' do
let(:dry_run) { false }
it 'prints messages and deletes invalid reference' do it 'prints messages and deletes invalid reference' do
expect(null_logger).to receive(:info).with("Looking for orphan LFS files for project #{project.name_with_namespace}") expect(null_logger).to receive(:info).with("Looking for orphan LFS files for project #{project.name_with_namespace}")
expect(null_logger).to receive(:info).with("Removed invalid references: 1") expect(null_logger).to receive(:info).with("Removed invalid references: 1")
expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:lfs_objects_size]) expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:lfs_objects_size])
expect { described_class.new(project, logger: null_logger, dry_run: false).run! } expect { service.run! }.to change { project.lfs_objects.count }.from(2).to(1)
.to change { project.lfs_objects.count }.from(2).to(1)
expect(LfsObjectsProject.exists?(invalid_reference.id)).to be_falsey expect(LfsObjectsProject.exists?(invalid_reference.id)).to be_falsey
end end
context 'LFS object is in design repository' do
before do
expect(project.design_repository).to receive(:exists?).and_return(true)
stub_lfs_pointers(project.design_repository, lfs_object.oid)
end
it 'is not removed' do
expect { service.run! }.not_to change { project.lfs_objects.count }
end
end
context 'LFS object is in wiki repository' do
before do
expect(project.wiki.repository).to receive(:exists?).and_return(true)
stub_lfs_pointers(project.wiki.repository, lfs_object.oid)
end
it 'is not removed' do
expect { service.run! }.not_to change { project.lfs_objects.count }
end
end
end
context 'LFS for project snippets' do
let(:snippet) { create(:project_snippet) }
it 'is disabled' do
# Support project snippets here before enabling LFS for them
expect(snippet.repository.lfs_enabled?).to be_falsy
end
end
def stub_lfs_pointers(repo, *oids)
expect(repo.gitaly_blob_client)
.to receive(:get_all_lfs_pointers)
.and_return(oids.map { |oid| OpenStruct.new(lfs_oid: oid) })
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