Commit 1b6cff9f authored by Patrick Bajao's avatar Patrick Bajao

Mark existing LFS object for upload for forks

When source project already has the LFS object to be uploaded, it
should still be linked to the fork project. That way, forks has
access to their own LFS objects.
parent f7a78f8a
...@@ -116,6 +116,10 @@ module LfsRequest ...@@ -116,6 +116,10 @@ module LfsRequest
@objects ||= (params[:objects] || []).to_a @objects ||= (params[:objects] || []).to_a
end end
def objects_oids
objects.map { |o| o['oid'].to_s }
end
def has_authentication_ability?(capability) def has_authentication_ability?(capability)
(authentication_abilities || []).include?(capability) (authentication_abilities || []).include?(capability)
end end
......
...@@ -45,15 +45,9 @@ module Repositories ...@@ -45,15 +45,9 @@ module Repositories
params[:operation] == 'upload' params[:operation] == 'upload'
end end
# rubocop: disable CodeReuse/ActiveRecord
def existing_oids
@existing_oids ||= begin
project.all_lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
end
end
# rubocop: enable CodeReuse/ActiveRecord
def download_objects! def download_objects!
existing_oids = project.all_lfs_objects_oids(oids: objects_oids)
objects.each do |object| objects.each do |object|
if existing_oids.include?(object[:oid]) if existing_oids.include?(object[:oid])
object[:actions] = download_actions(object) object[:actions] = download_actions(object)
...@@ -68,13 +62,17 @@ module Repositories ...@@ -68,13 +62,17 @@ module Repositories
} }
end end
end end
objects objects
end end
def upload_objects! def upload_objects!
existing_oids = project.lfs_objects_oids(oids: objects_oids)
objects.each do |object| objects.each do |object|
object[:actions] = upload_actions(object) unless existing_oids.include?(object[:oid]) object[:actions] = upload_actions(object) unless existing_oids.include?(object[:oid])
end end
objects objects
end end
......
...@@ -1398,12 +1398,17 @@ class Project < ApplicationRecord ...@@ -1398,12 +1398,17 @@ class Project < ApplicationRecord
.where(lfs_objects_projects: { project_id: [self, lfs_storage_project] }) .where(lfs_objects_projects: { project_id: [self, lfs_storage_project] })
end end
# TODO: Call `#lfs_objects` instead once all LfsObjectsProject records are # TODO: Remove this method once all LfsObjectsProject records are backfilled
# backfilled. At that point, projects can look at their own `lfs_objects`. # for forks. At that point, projects can look at their own `lfs_objects` so
# `lfs_objects_oids` can be used instead.
# #
# See https://gitlab.com/gitlab-org/gitlab/issues/122002 for more info. # See https://gitlab.com/gitlab-org/gitlab/issues/122002 for more info.
def lfs_objects_oids def all_lfs_objects_oids(oids: [])
all_lfs_objects.pluck(:oid) oids(all_lfs_objects, oids: oids)
end
def lfs_objects_oids(oids: [])
oids(lfs_objects, oids: oids)
end end
def personal? def personal?
...@@ -2515,6 +2520,12 @@ class Project < ApplicationRecord ...@@ -2515,6 +2520,12 @@ class Project < ApplicationRecord
reset reset
retry retry
end end
def oids(objects, oids: [])
collection = oids.any? ? objects.where(oid: oids) : objects
collection.pluck(:oid)
end
end end
Project.prepend_if_ee('EE::Project') Project.prepend_if_ee('EE::Project')
...@@ -52,7 +52,7 @@ class RepositoryForkWorker # rubocop:disable Scalability/IdempotentWorker ...@@ -52,7 +52,7 @@ class RepositoryForkWorker # rubocop:disable Scalability/IdempotentWorker
def link_lfs_objects(source_project, target_project) def link_lfs_objects(source_project, target_project)
Projects::LfsPointers::LfsLinkService Projects::LfsPointers::LfsLinkService
.new(target_project) .new(target_project)
.execute(source_project.lfs_objects_oids) .execute(source_project.all_lfs_objects_oids)
rescue Projects::LfsPointers::LfsLinkService::TooManyOidsError rescue Projects::LfsPointers::LfsLinkService::TooManyOidsError
raise_fork_failure( raise_fork_failure(
source_project, source_project,
......
---
title: Mark existing LFS object for upload for forks
merge_request: 26344
author:
type: fixed
...@@ -5660,6 +5660,53 @@ describe Project do ...@@ -5660,6 +5660,53 @@ describe Project do
end end
end end
describe '#all_lfs_objects_oids' do
let(:project) { create(:project) }
let(:lfs_object) { create(:lfs_object) }
let(:another_lfs_object) { create(:lfs_object) }
subject { project.all_lfs_objects_oids }
context 'when project has associated LFS objects' do
before do
create(:lfs_objects_project, lfs_object: lfs_object, project: project)
create(:lfs_objects_project, lfs_object: another_lfs_object, project: project)
end
it 'returns OIDs of LFS objects' do
expect(subject).to match_array([lfs_object.oid, another_lfs_object.oid])
end
context 'and there are specified oids' do
subject { project.all_lfs_objects_oids(oids: [lfs_object.oid]) }
it 'returns OIDs of LFS objects that match specified oids' do
expect(subject).to eq([lfs_object.oid])
end
end
end
context 'when fork has associated LFS objects to itself and source' do
let(:source) { create(:project) }
let(:project) { fork_project(source) }
before do
create(:lfs_objects_project, lfs_object: lfs_object, project: source)
create(:lfs_objects_project, lfs_object: another_lfs_object, project: project)
end
it 'returns OIDs of LFS objects' do
expect(subject).to match_array([lfs_object.oid, another_lfs_object.oid])
end
end
context 'when project has no associated LFS objects' do
it 'returns empty array' do
expect(subject).to be_empty
end
end
end
describe '#lfs_objects_oids' do describe '#lfs_objects_oids' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:lfs_object) { create(:lfs_object) } let(:lfs_object) { create(:lfs_object) }
...@@ -5676,6 +5723,14 @@ describe Project do ...@@ -5676,6 +5723,14 @@ describe Project do
it 'returns OIDs of LFS objects' do it 'returns OIDs of LFS objects' do
expect(subject).to match_array([lfs_object.oid, another_lfs_object.oid]) expect(subject).to match_array([lfs_object.oid, another_lfs_object.oid])
end end
context 'and there are specified oids' do
subject { project.lfs_objects_oids(oids: [lfs_object.oid]) }
it 'returns OIDs of LFS objects that match specified oids' do
expect(subject).to eq([lfs_object.oid])
end
end
end end
context 'when project has no associated LFS objects' do context 'when project has no associated LFS objects' do
......
...@@ -690,22 +690,34 @@ describe 'Git LFS API and storage' do ...@@ -690,22 +690,34 @@ describe 'Git LFS API and storage' do
end end
context 'when pushing an LFS object that already exists' do context 'when pushing an LFS object that already exists' do
shared_examples_for 'batch upload with existing LFS object' do
it_behaves_like 'LFS http 200 response'
it 'responds with links the object to the project' do
expect(json_response['objects']).to be_kind_of(Array)
expect(json_response['objects'].first).to include(sample_object)
expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
expect(lfs_object.projects.pluck(:id)).to include(other_project.id)
expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size))
expect(json_response['objects'].first['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream')
end
it_behaves_like 'process authorization header', renew_authorization: true
end
let(:update_lfs_permissions) do let(:update_lfs_permissions) do
other_project.lfs_objects << lfs_object other_project.lfs_objects << lfs_object
end end
it_behaves_like 'LFS http 200 response' context 'in another project' do
it_behaves_like 'batch upload with existing LFS object'
it 'responds with links the object to the project' do
expect(json_response['objects']).to be_kind_of(Array)
expect(json_response['objects'].first).to include(sample_object)
expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
expect(lfs_object.projects.pluck(:id)).to include(other_project.id)
expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size))
expect(json_response['objects'].first['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream')
end end
it_behaves_like 'process authorization header', renew_authorization: true context 'in source of fork project' do
let(:project) { fork_project(other_project) }
it_behaves_like 'batch upload with existing LFS object'
end
end end
context 'when pushing a LFS object that does not exist' do context 'when pushing a LFS object that does not exist' do
......
...@@ -83,7 +83,7 @@ describe RepositoryForkWorker do ...@@ -83,7 +83,7 @@ describe RepositoryForkWorker do
it 'calls Projects::LfsPointers::LfsLinkService#execute with OIDs of source project LFS objects' do it 'calls Projects::LfsPointers::LfsLinkService#execute with OIDs of source project LFS objects' do
expect_fork_repository.and_return(true) expect_fork_repository.and_return(true)
expect_next_instance_of(Projects::LfsPointers::LfsLinkService) do |service| expect_next_instance_of(Projects::LfsPointers::LfsLinkService) do |service|
expect(service).to receive(:execute).with(project.lfs_objects_oids) expect(service).to receive(:execute).with(project.all_lfs_objects_oids)
end end
perform! perform!
......
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