summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Edwards-Jones <jedwardsjones@gitlab.com>2017-10-03 10:12:26 +0100
committerJames Edwards-Jones <jedwardsjones@gitlab.com>2017-11-06 18:02:17 +0000
commit5132405521902635ab5452956bf688c02042ff9b (patch)
tree125567e12efeb3a92255d7c689d459884e150578
parent17d6efee29e6548e09407d747988a5651d067e0e (diff)
downloadgitlab-ce-5132405521902635ab5452956bf688c02042ff9b.tar.gz
Lfs pruning improvements and review changes
Adds indices for LFS pruning LFS pruning removes LfsObjects LFS pruning only uses recent ref pushes in "rev-list --not" Duplicate LFS pointer removals don't delete the object Renamed ReferenceChange to RecentLfsPush UpdateLfsPointersWorker scans all objects on first run LFS cleanup is skipped until a project has 5MB of LFS files LfsCleanupService uses delete_all and updates storage statistics just once Created LfsObjectsProject.without_pointers scope Split RecentLfsPushes into UnprocessedLfsPushes and ProcessedLfsRefs Removed newrev from ProcessedLfsRef ProcessedLfsRefs sorted by updated_at Lfs pruning review changes
-rw-r--r--app/models/lfs_objects_project.rb6
-rw-r--r--app/models/processed_lfs_ref.rb6
-rw-r--r--app/models/project.rb3
-rw-r--r--app/models/reference_change.rb9
-rw-r--r--app/models/unprocessed_lfs_push.rb15
-rw-r--r--app/services/git_push_service.rb6
-rw-r--r--app/services/lfs_cleanup_service.rb28
-rw-r--r--app/workers/lfs_project_cleanup_worker.rb4
-rw-r--r--app/workers/update_lfs_pointers_worker.rb25
-rw-r--r--db/migrate/20170922164114_create_unprocessed_lfs_pushes.rb35
-rw-r--r--db/migrate/20170922171356_create_lfs_pointer.rb4
-rw-r--r--db/migrate/20171013174017_create_processed_lfs_refs.rb (renamed from db/migrate/20170922164114_create_reference_change.rb)11
-rw-r--r--db/schema.rb38
-rw-r--r--spec/factories/processed_lfs_ref.rb6
-rw-r--r--spec/factories/unprocessed_lfs_push.rb (renamed from spec/factories/reference_change.rb)3
-rw-r--r--spec/models/processed_lfs_ref_spec.rb8
-rw-r--r--spec/models/reference_change_spec.rb24
-rw-r--r--spec/models/unprocessed_lfs_push_spec.rb31
-rw-r--r--spec/services/lfs_cleanup_service_spec.rb76
-rw-r--r--spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb6
-rw-r--r--spec/workers/update_lfs_pointers_worker_spec.rb24
21 files changed, 261 insertions, 107 deletions
diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb
index b0625c52b62..12af8660d24 100644
--- a/app/models/lfs_objects_project.rb
+++ b/app/models/lfs_objects_project.rb
@@ -8,6 +8,12 @@ class LfsObjectsProject < ActiveRecord::Base
after_commit :update_project_statistics, on: [:create, :destroy]
+ def self.without_pointers
+ self.joins(:lfs_object)
+ .joins("LEFT JOIN lfs_pointers ON lfs_pointers.lfs_oid = lfs_objects.oid AND lfs_pointers.project_id = lfs_objects_projects.project_id")
+ .where(lfs_pointers: { lfs_oid: nil })
+ end
+
private
def update_project_statistics
diff --git a/app/models/processed_lfs_ref.rb b/app/models/processed_lfs_ref.rb
new file mode 100644
index 00000000000..dac7126b949
--- /dev/null
+++ b/app/models/processed_lfs_ref.rb
@@ -0,0 +1,6 @@
+class ProcessedLfsRef < ActiveRecord::Base
+ belongs_to :project
+
+ validates :project, presence: true
+ validates :ref, presence: true
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index d71f20244bb..6023cc24d96 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -178,7 +178,8 @@ class Project < ActiveRecord::Base
has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :lfs_objects, through: :lfs_objects_projects
has_many :lfs_pointers
- has_many :reference_changes
+ has_many :unprocessed_lfs_pushes
+ has_many :processed_lfs_refs
has_many :project_group_links
has_many :invited_groups, through: :project_group_links, source: :group
has_many :pages_domains
diff --git a/app/models/reference_change.rb b/app/models/reference_change.rb
deleted file mode 100644
index 341f883a769..00000000000
--- a/app/models/reference_change.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class ReferenceChange < ActiveRecord::Base
- belongs_to :project
-
- validates :project, presence: true
- validates :newrev, presence: true
-
- scope :processed, -> { where(processed: true) }
- scope :unprocessed, -> { where(processed: false) }
-end
diff --git a/app/models/unprocessed_lfs_push.rb b/app/models/unprocessed_lfs_push.rb
new file mode 100644
index 00000000000..09f5568d08a
--- /dev/null
+++ b/app/models/unprocessed_lfs_push.rb
@@ -0,0 +1,15 @@
+class UnprocessedLfsPush < ActiveRecord::Base
+ belongs_to :project
+
+ validates :project, presence: true
+ validates :newrev, presence: true
+ validates :ref, presence: true
+
+ def processed!
+ transaction do
+ project.processed_lfs_refs.find_or_create_by(ref: ref)
+
+ self.destroy!
+ end
+ end
+end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 77da8bb766c..315104fc599 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -123,10 +123,10 @@ class GitPushService < BaseService
return unless @project.lfs_enabled?
return unless @project.lfs_objects.exists?
- reference_change = ReferenceChange.create!(project: @project,
- newrev: params[:newrev])
+ unprocessed_lfs_push = @project.unprocessed_lfs_pushes.create!(ref: params[:ref],
+ newrev: params[:newrev])
- UpdateLfsPointersWorker.perform_async(reference_change.id)
+ UpdateLfsPointersWorker.perform_async(unprocessed_lfs_push.id)
end
protected
diff --git a/app/services/lfs_cleanup_service.rb b/app/services/lfs_cleanup_service.rb
index a0f7d8a0c0e..1c55551c098 100644
--- a/app/services/lfs_cleanup_service.rb
+++ b/app/services/lfs_cleanup_service.rb
@@ -1,26 +1,36 @@
class LfsCleanupService
attr_reader :project
+ MIN_LFS_USAGE = 5.megabytes
+
def initialize(project)
@project = project
end
def remove_unreferenced
return unless project.lfs_enabled?
- return unless project.lfs_pointers.exists? # LFS pointers were found
- return unless project.reference_changes.exists? # Has been scanned for lfs pointers
- return if project.reference_changes.unprocessed.exists? # No scans still in progress
+ return unless project.lfs_pointers.exists?
+ return if project.unprocessed_lfs_pushes.exists?
return unless project.lfs_objects.exists?
+ return if project.statistics.lfs_objects_size < MIN_LFS_USAGE
+
+ unreferenced_pointers.delete_all
- unreferenced_pointers.destroy_all
+ project.lfs_objects_projects.without_pointers.delete_all
+
+ ProjectCacheWorker.perform_async(project.id, [], [:lfs_objects_size])
end
def unreferenced_pointers
- pointer_oids = project.lfs_pointers.pluck(:blob_oid)
- removed_pointer_oids = Gitlab::Git::Blob.batch_blob_existance(project.repository,
- pointer_oids,
- existing: false)
-
project.lfs_pointers.where(blob_oid: removed_pointer_oids)
end
+
+ private
+
+ def removed_pointer_oids
+ @removed_pointer_oids ||= begin
+ pointer_oids = project.lfs_pointers.pluck(:blob_oid)
+ project.repository.batch_existence(pointer_oids, existing: false)
+ end
+ end
end
diff --git a/app/workers/lfs_project_cleanup_worker.rb b/app/workers/lfs_project_cleanup_worker.rb
index 982c0cae841..48a7a46ebbb 100644
--- a/app/workers/lfs_project_cleanup_worker.rb
+++ b/app/workers/lfs_project_cleanup_worker.rb
@@ -2,7 +2,9 @@ class LfsProjectCleanupWorker
include Sidekiq::Worker
def perform(project_id)
- project = Project.find(project_id)
+ project = Project.find_by(id: project_id)
+
+ return unless project
LfsCleanupService.new(project).remove_unreferenced
end
diff --git a/app/workers/update_lfs_pointers_worker.rb b/app/workers/update_lfs_pointers_worker.rb
index 812a026f8cd..3af045942aa 100644
--- a/app/workers/update_lfs_pointers_worker.rb
+++ b/app/workers/update_lfs_pointers_worker.rb
@@ -2,17 +2,16 @@ class UpdateLfsPointersWorker
include Sidekiq::Worker
include CronjobQueue
- attr_reader :reference_change
+ attr_reader :unprocessed_lfs_push
- def perform(reference_change_id)
- @reference_change = ReferenceChange.find(reference_change_id)
+ def perform(unprocessed_lfs_push_id)
+ @unprocessed_lfs_push = UnprocessedLfsPush.find(unprocessed_lfs_push_id)
return unless project.lfs_enabled?
- return if @reference_change.processed?
create_lfs_pointer_records
- reference_change.update!(processed: true)
+ unprocessed_lfs_push.processed!
end
private
@@ -24,13 +23,21 @@ class UpdateLfsPointersWorker
end
def new_lfs_pointers
- processed_references = project.reference_changes.processed.pluck(:newrev)
+ processed_references = project.processed_lfs_refs
+ .order(updated_at: :desc)
+ .limit(100)
+ .pluck(:ref)
- lfs_changes = Gitlab::Git::LfsChanges.new(project.repository, reference_change.newrev)
- lfs_changes.new_pointers(not_in: processed_references)
+ lfs_changes = Gitlab::Git::LfsChanges.new(project.repository, unprocessed_lfs_push.newrev)
+
+ if processed_references.present?
+ lfs_changes.new_pointers(not_in: processed_references)
+ else
+ lfs_changes.all_pointers
+ end
end
def project
- reference_change.project
+ unprocessed_lfs_push.project
end
end
diff --git a/db/migrate/20170922164114_create_unprocessed_lfs_pushes.rb b/db/migrate/20170922164114_create_unprocessed_lfs_pushes.rb
new file mode 100644
index 00000000000..479768adff9
--- /dev/null
+++ b/db/migrate/20170922164114_create_unprocessed_lfs_pushes.rb
@@ -0,0 +1,35 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreateUnprocessedLfsPushes < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # When a migration requires downtime you **must** uncomment the following
+ # constant and define a short and easy to understand explanation as to why the
+ # migration requires downtime.
+ # DOWNTIME_REASON = ''
+
+ # When using the methods "add_concurrent_index", "remove_concurrent_index" or
+ # "add_column_with_default" you must disable the use of transactions
+ # as these methods can not run in an existing transaction.
+ # When using "add_concurrent_index" or "remove_concurrent_index" methods make sure
+ # that either of them is the _only_ method called in the migration,
+ # any other changes should go in a separate migration.
+ # This ensures that upon failure _only_ the index creation or removing fails
+ # and can be retried or reverted easily.
+ #
+ # To disable transactions uncomment the following line and remove these
+ # comments:
+ # disable_ddl_transaction!
+
+ def change
+ create_table :unprocessed_lfs_pushes do |t|
+ t.references :project, null: false, index: true, foreign_key: { on_delete: :cascade }
+ t.string :ref, null: false
+ t.string :newrev, null: false #TODO: We can probably remove this and use the ref instead. Maybe only use this for deletions and force pushes?
+ end
+ end
+end
diff --git a/db/migrate/20170922171356_create_lfs_pointer.rb b/db/migrate/20170922171356_create_lfs_pointer.rb
index 3917df1af1f..95232f0b110 100644
--- a/db/migrate/20170922171356_create_lfs_pointer.rb
+++ b/db/migrate/20170922171356_create_lfs_pointer.rb
@@ -27,10 +27,10 @@ class CreateLfsPointer < ActiveRecord::Migration
def change
create_table :lfs_pointers do |t|
+ t.references :project, null: false, index: true, foreign_key: { on_delete: :cascade }
t.string :blob_oid, null: false
t.string :lfs_oid, null: false
- t.references :project, index: true, foreign_key: true
- t.string :timestamps
+ t.index :blob_oid # Used to filter on removed blobs
end
end
end
diff --git a/db/migrate/20170922164114_create_reference_change.rb b/db/migrate/20171013174017_create_processed_lfs_refs.rb
index 447042d49c7..f149ae00d97 100644
--- a/db/migrate/20170922164114_create_reference_change.rb
+++ b/db/migrate/20171013174017_create_processed_lfs_refs.rb
@@ -1,7 +1,7 @@
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
-class CreateReferenceChange < ActiveRecord::Migration
+class CreateProcessedLfsRefs < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
@@ -26,11 +26,10 @@ class CreateReferenceChange < ActiveRecord::Migration
# disable_ddl_transaction!
def change
- create_table :reference_changes do |t|
- t.references :project, index: true, foreign_key: true, null: false
- t.string :newrev
- t.boolean :processed, default: false, null: false
- t.string :timestamps
+ create_table :processed_lfs_refs do |t|
+ t.references :project, null: false, index: true, foreign_key: { on_delete: :cascade }
+ t.datetime_with_timezone :updated_at, null: false
+ t.string :ref, null: false
end
end
end
diff --git a/db/schema.rb b/db/schema.rb
index d8268473c04..1500c4ebe62 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -843,12 +843,12 @@ ActiveRecord::Schema.define(version: 20171026082505) do
add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree
create_table "lfs_pointers", force: :cascade do |t|
- t.string "blob_oid"
- t.string "lfs_oid"
- t.integer "project_id"
- t.string "timestamps"
+ t.integer "project_id", null: false
+ t.string "blob_oid", null: false
+ t.string "lfs_oid", null: false
end
+ add_index "lfs_pointers", ["blob_oid"], name: "index_lfs_pointers_on_blob_oid", using: :btree
add_index "lfs_pointers", ["project_id"], name: "index_lfs_pointers_on_project_id", using: :btree
create_table "lists", force: :cascade do |t|
@@ -1204,6 +1204,14 @@ ActiveRecord::Schema.define(version: 20171026082505) do
add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree
add_index "personal_access_tokens", ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree
+ create_table "processed_lfs_refs", force: :cascade do |t|
+ t.integer "project_id", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.string "ref", null: false
+ end
+
+ add_index "processed_lfs_refs", ["project_id"], name: "index_processed_lfs_refs_on_project_id", using: :btree
+
create_table "project_authorizations", id: false, force: :cascade do |t|
t.integer "user_id"
t.integer "project_id"
@@ -1413,15 +1421,6 @@ ActiveRecord::Schema.define(version: 20171026082505) do
add_index "redirect_routes", ["path"], name: "index_redirect_routes_on_path_text_pattern_ops", using: :btree, opclasses: {"path"=>"varchar_pattern_ops"}
add_index "redirect_routes", ["source_type", "source_id"], name: "index_redirect_routes_on_source_type_and_source_id", using: :btree
- create_table "reference_changes", force: :cascade do |t|
- t.integer "project_id", null: false
- t.string "newrev"
- t.boolean "processed", default: false, null: false
- t.string "timestamps"
- end
-
- add_index "reference_changes", ["project_id"], name: "index_reference_changes_on_project_id", using: :btree
-
create_table "releases", force: :cascade do |t|
t.string "tag"
t.text "description"
@@ -1624,6 +1623,14 @@ ActiveRecord::Schema.define(version: 20171026082505) do
add_index "u2f_registrations", ["key_handle"], name: "index_u2f_registrations_on_key_handle", using: :btree
add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree
+ create_table "unprocessed_lfs_pushes", force: :cascade do |t|
+ t.integer "project_id", null: false
+ t.string "ref", null: false
+ t.string "newrev", null: false
+ end
+
+ add_index "unprocessed_lfs_pushes", ["project_id"], name: "index_unprocessed_lfs_pushes_on_project_id", using: :btree
+
create_table "uploads", force: :cascade do |t|
t.integer "size", limit: 8, null: false
t.string "path", null: false
@@ -1856,7 +1863,7 @@ ActiveRecord::Schema.define(version: 20171026082505) do
add_foreign_key "label_priorities", "projects", on_delete: :cascade
add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "labels", "projects", name: "fk_7de4989a69", on_delete: :cascade
- add_foreign_key "lfs_pointers", "projects"
+ add_foreign_key "lfs_pointers", "projects", on_delete: :cascade
add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade
add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade
add_foreign_key "merge_request_diff_commits", "merge_request_diffs", on_delete: :cascade
@@ -1875,6 +1882,7 @@ ActiveRecord::Schema.define(version: 20171026082505) do
add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", name: "fk_oauth_openid_requests_oauth_access_grants_access_grant_id"
add_foreign_key "pages_domains", "projects", name: "fk_ea2f6dfc6f", on_delete: :cascade
add_foreign_key "personal_access_tokens", "users"
+ add_foreign_key "processed_lfs_refs", "projects", on_delete: :cascade
add_foreign_key "project_authorizations", "projects", on_delete: :cascade
add_foreign_key "project_authorizations", "users", on_delete: :cascade
add_foreign_key "project_auto_devops", "projects", on_delete: :cascade
@@ -1890,7 +1898,6 @@ ActiveRecord::Schema.define(version: 20171026082505) do
add_foreign_key "protected_tag_create_access_levels", "users"
add_foreign_key "protected_tags", "projects", name: "fk_8e4af87648", on_delete: :cascade
add_foreign_key "push_event_payloads", "events", name: "fk_36c74129da", on_delete: :cascade
- add_foreign_key "reference_changes", "projects"
add_foreign_key "releases", "projects", name: "fk_47fe2a0596", on_delete: :cascade
add_foreign_key "services", "projects", name: "fk_71cce407f9", on_delete: :cascade
add_foreign_key "snippets", "projects", name: "fk_be41fd4bb7", on_delete: :cascade
@@ -1901,6 +1908,7 @@ ActiveRecord::Schema.define(version: 20171026082505) do
add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade
add_foreign_key "u2f_registrations", "users"
+ add_foreign_key "unprocessed_lfs_pushes", "projects", on_delete: :cascade
add_foreign_key "user_custom_attributes", "users", on_delete: :cascade
add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade
add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
diff --git a/spec/factories/processed_lfs_ref.rb b/spec/factories/processed_lfs_ref.rb
new file mode 100644
index 00000000000..160ab047cc7
--- /dev/null
+++ b/spec/factories/processed_lfs_ref.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :processed_lfs_ref do
+ project
+ ref 'feature_branch'
+ end
+end
diff --git a/spec/factories/reference_change.rb b/spec/factories/unprocessed_lfs_push.rb
index 4f3117662a8..cbb389247e0 100644
--- a/spec/factories/reference_change.rb
+++ b/spec/factories/unprocessed_lfs_push.rb
@@ -1,6 +1,7 @@
FactoryGirl.define do
- factory :reference_change do
+ factory :unprocessed_lfs_push do
project
newrev '4b67e82213a2ce0f8b4ed1d65bef48ef8b8be1f3'
+ ref 'feature_branch'
end
end
diff --git a/spec/models/processed_lfs_ref_spec.rb b/spec/models/processed_lfs_ref_spec.rb
new file mode 100644
index 00000000000..ef60cfa6a73
--- /dev/null
+++ b/spec/models/processed_lfs_ref_spec.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+
+describe ProcessedLfsRef do
+ it { is_expected.to belong_to(:project) }
+
+ it { is_expected.to have_db_column(:newrev).of_type(:string).with_options(null: false) }
+ it { is_expected.to have_db_column(:ref).of_type(:string).with_options(null: false) }
+end
diff --git a/spec/models/reference_change_spec.rb b/spec/models/reference_change_spec.rb
deleted file mode 100644
index 338a0eecd53..00000000000
--- a/spec/models/reference_change_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'spec_helper'
-
-describe ReferenceChange do
- it { is_expected.to belong_to(:project) }
-
- it { is_expected.to have_db_column(:newrev).of_type(:string) }
- it { is_expected.to have_db_column(:processed).of_type(:boolean).with_options(default: false, null: false) }
-
- describe "scopes" do
- describe ".processed" do
- it 'excludes reference changes by default' do
- create(:reference_change)
-
- expect(described_class.processed.count).to eq 0
- end
-
- it 'includes processed reference changes' do
- create(:reference_change, processed: true)
-
- expect(described_class.processed.count).to eq 1
- end
- end
- end
-end
diff --git a/spec/models/unprocessed_lfs_push_spec.rb b/spec/models/unprocessed_lfs_push_spec.rb
new file mode 100644
index 00000000000..e315f620dcd
--- /dev/null
+++ b/spec/models/unprocessed_lfs_push_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe UnprocessedLfsPush do
+ it { is_expected.to belong_to(:project) }
+
+ it { is_expected.to have_db_column(:newrev).of_type(:string).with_options(null: false) }
+ it { is_expected.to have_db_column(:ref).of_type(:string).with_options(null: false) }
+
+ describe "#processed!" do
+ subject(:unprocessed_lfs_push) { create(:unprocessed_lfs_push) }
+ let(:project) { subject.project }
+
+ it 'deletes the record' do
+ subject.processed!
+
+ expect(subject).to be_destroyed
+ end
+
+ it 'creates a ProcessedLfsRef' do
+ expect { subject.processed! }.to change(ProcessedLfsRef, :count).by(1)
+ end
+
+ it 'updates an existing ProcessedLfsRef' do
+ create(:processed_lfs_ref, project: project, ref: subject.ref)
+
+ expect { subject.processed! }.to change { processed_lfs_push.reload.newrev }
+ end
+
+ #TODO: Could create ProcessedLfsRef with sha on force-push or deletion
+ end
+end
diff --git a/spec/services/lfs_cleanup_service_spec.rb b/spec/services/lfs_cleanup_service_spec.rb
index 58fdadd1474..561b64713aa 100644
--- a/spec/services/lfs_cleanup_service_spec.rb
+++ b/spec/services/lfs_cleanup_service_spec.rb
@@ -2,14 +2,18 @@ require 'spec_helper'
describe LfsCleanupService do
let(:project) { create(:project) }
- let!(:lfs_pointer) { create(:lfs_pointer, project: project) }
- let!(:lfs_objects_project) { create(:lfs_objects_project, project: project) }
- let!(:reference_change) { create(:reference_change, project: project, processed: true) }
+ let!(:lfs_object) { create(:lfs_object, size: 10.megabytes) }
+ let!(:lfs_pointer) { create(:lfs_pointer, project: project, lfs_oid: lfs_object.oid) }
+ let!(:lfs_objects_project) { create(:lfs_objects_project, project: project, lfs_object: lfs_object) }
+ let!(:processed_lfs_ref) { create(:processed_lfs_ref, project: project) }
subject(:service) { described_class.new(project) }
before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+
+ # ProjectCacheWorker skips this becuase no project.repository.exists?
+ project.statistics.refresh!(only: [:lfs_objects_size])
end
describe '#remove_unreferenced' do
@@ -29,28 +33,33 @@ describe LfsCleanupService do
subject.remove_unreferenced
end
- it "skips project if it hasn't been scanned for LFS pointers" do
- ReferenceChange.destroy_all
+ it 'skips project if newly pushed changes could contain LFS pointers' do
+ create(:unprocessed_lfs_push, project: project)
expect(subject).not_to receive(:unreferenced_pointers)
subject.remove_unreferenced
end
- it 'skips project if newly pushed changes could contain LFS pointers' do
- reference_change.update!(processed: false)
+ it 'skips project if no LfsObjects exist to clean up' do
+ LfsObject.destroy_all
expect(subject).not_to receive(:unreferenced_pointers)
subject.remove_unreferenced
end
- it 'skips project if no LfsObjects exist to clean up' do
- LfsObject.destroy_all
+ context 'with less than 5MB of LFS files in the project' do
+ before do
+ lfs_object.update!(size: 5.megabytes - 1)
+ project.statistics.refresh!(only: [:lfs_objects_size])
+ end
- expect(subject).not_to receive(:unreferenced_pointers)
+ it 'skips cleanup' do
+ expect(subject).not_to receive(:unreferenced_pointers)
- subject.remove_unreferenced
+ subject.remove_unreferenced
+ end
end
it 'removes LfsPointers which no longer exist in the project' do
@@ -59,18 +68,53 @@ describe LfsCleanupService do
expect { subject.remove_unreferenced }.to change(LfsPointer, :count).by(-1)
end
- it 'removes LfsObjectProjects/LfsObjects which are no longer referenced by pointers'
+ describe 'LfsObjectProjects' do
+ let!(:project) { create(:project, :repository) }
+
+ it 'are removed when no longer referenced by pointers' do
+ allow(subject).to receive(:removed_pointer_oids).and_return([lfs_pointer.blob_oid])
+
+ expect { subject.remove_unreferenced }.to change(LfsObjectsProject, :count).by(-1)
+ end
+
+ it 'cause LFS storage statistics to be updated' do
+ allow(subject).to receive(:removed_pointer_oids).and_return([lfs_pointer.blob_oid])
+
+ expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:lfs_objects_size])
+
+ subject.remove_unreferenced
+ end
+
+ it "don't cause LfsObjects to be removed" do
+ allow(subject).to receive(:removed_pointer_oids).and_return([lfs_pointer.blob_oid])
+
+ expect { subject.remove_unreferenced }.not_to change(LfsObject, :count)
+ end
+
+ it "are not removed when pointers haven't been removed" do
+ allow(subject).to receive(:removed_pointer_oids).and_return([])
+
+ expect { subject.remove_unreferenced }.not_to change(LfsObjectsProject, :count)
+ end
+
+ it 'are not removed if other pointers still reference them' do
+ create(:lfs_pointer, project: project,
+ lfs_oid: lfs_object.oid,
+ blob_oid: 'b7f094b759e6b023278323195812ce1019cb57ff')
- # xit 'skips projects with less than 5MB of LfsFiles'
+ allow(subject).to receive(:removed_pointer_oids).and_return([lfs_pointer.blob_oid])
+
+ expect { subject.remove_unreferenced }.not_to change(LfsObjectsProject, :count)
+ end
+ end
end
describe '#unreferenced_pointers' do
let(:removed_pointer_oids) { [lfs_pointer.blob_oid] }
before do
- allow(Gitlab::Git::Blob).to receive(:batch_blob_existance)
- .with(project.repository,
- [lfs_pointer.blob_oid],
+ allow(project.repository).to receive(:batch_existence)
+ .with([lfs_pointer.blob_oid],
existing: false)
.and_return(removed_pointer_oids)
end
diff --git a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
index 57f83c1dbe9..0b9830caedb 100644
--- a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
+++ b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
@@ -51,5 +51,11 @@ describe RemoveUnreferencedLfsObjectsWorker do
expect(referenced_lfs_object1.reload).to be_present
expect(LfsObject.where(id: referenced_lfs_object2.id)).to be_empty
end
+
+ it 'removes LFS files from disk storage' do
+ expect_any_instance_of(ActiveRecord::Relation).to receive(:destroy_all).and_call_original
+
+ worker.perform
+ end
end
end
diff --git a/spec/workers/update_lfs_pointers_worker_spec.rb b/spec/workers/update_lfs_pointers_worker_spec.rb
index ebec3caf061..b87cd209356 100644
--- a/spec/workers/update_lfs_pointers_worker_spec.rb
+++ b/spec/workers/update_lfs_pointers_worker_spec.rb
@@ -5,7 +5,7 @@ describe UpdateLfsPointersWorker do
describe '#perform' do
let(:project) { create(:project, :repository) }
- let(:reference_change) { create(:reference_change, project: project) }
+ let!(:unprocessed_lfs_push) { create(:unprocessed_lfs_push, project: project) }
let(:blob_object) { project.repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') }
before do
@@ -13,7 +13,7 @@ describe UpdateLfsPointersWorker do
end
def perform
- subject.perform(reference_change.id)
+ subject.perform(unprocessed_lfs_push.id)
end
context 'with LFS not enabled' do
@@ -39,8 +39,12 @@ describe UpdateLfsPointersWorker do
perform
end
- it 'marks the updated reference as processed' do
- expect { perform }.to change { reference_change.reload.processed }.from(false).to(true)
+ it 'removes the updated reference from unprocessed_lfs_pushes' do
+ expect { perform }.to change { project.reload.unprocessed_lfs_pushes.count }.by(-1)
+ end
+
+ it 'creates a ProcessedLfsRef for the reference' do
+ expect { perform }.to change { project.processed_lfs_refs.count }.by(1)
end
it 'creates a LfsPointer record for each new blob' do
@@ -48,24 +52,22 @@ describe UpdateLfsPointersWorker do
end
it 'looks up LFS pointers for the new ref' do
- expect(Gitlab::Git::RevList).to receive(:new).with(hash_including(newrev: reference_change.newrev)).and_call_original
+ expect(Gitlab::Git::RevList).to receive(:new).with(hash_including(newrev: unprocessed_lfs_push.newrev)).and_call_original
perform
end
- it 'scans all objects in the given ref on first run' do
- expect_any_instance_of(Gitlab::Git::LfsChanges).to receive(:new_pointers).with(not_in: []).and_call_original
+ it 'scans all objects in the project on first run' do
+ expect_any_instance_of(Gitlab::Git::LfsChanges).to receive(:all_pointers).and_call_original
perform
end
context 'with processed refrences' do
- let(:processed_reference) { 'fa15ebeefacce55ed4dec0dea5decafbeefba115' }
+ let(:processed_reference) { 'some_feature' }
before do
- create(:reference_change, project: project,
- newrev: processed_reference,
- processed: true)
+ create(:processed_lfs_ref, project: project, ref: processed_reference)
end
it 'ignores objects reachable from processed refs' do