diff options
author | Marin Jankovski <maxlazio@gmail.com> | 2018-04-05 11:22:52 +0200 |
---|---|---|
committer | Marin Jankovski <maxlazio@gmail.com> | 2018-04-05 11:22:52 +0200 |
commit | 1bca36b66f65efd0ff1d358e3e569bc84806e959 (patch) | |
tree | 40b500bbda184cbe588e0a5f4835e3957c14407b | |
parent | c48a9c73ebbf1e48852d42651382ff4b174b3728 (diff) | |
parent | 32d2206b01b97cdbd6cdc13b25d98c3d3db048c4 (diff) | |
download | gitlab-ce-1bca36b66f65efd0ff1d358e3e569bc84806e959.tar.gz |
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce
26 files changed, 563 insertions, 16 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 86bdb7a4643..90aabf5034a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -78,6 +78,19 @@ stages: - mysql:latest - redis:alpine +.rails5-variables: &rails5-variables + script: + - export RAILS5=${RAILS5} + - export BUNDLE_GEMFILE=${BUNDLE_GEMFILE} + +.rails5: &rails5 + allow_failure: true + only: + - /rails5/ + variables: + BUNDLE_GEMFILE: "Gemfile.rails5" + RAILS5: "true" + # Skip all jobs except the ones that begin with 'docs/'. # Used for commits including ONLY documentation changes. # https://docs.gitlab.com/ce/development/writing_documentation.html#testing @@ -118,6 +131,7 @@ stages: <<: *dedicated-runner <<: *except-docs-and-qa <<: *pull-cache + <<: *rails5-variables stage: test script: - JOB_NAME=( $CI_JOB_NAME ) @@ -148,14 +162,23 @@ stages: <<: *rspec-metadata <<: *use-pg +.rspec-metadata-pg-rails5: &rspec-metadata-pg-rails5 + <<: *rspec-metadata-pg + <<: *rails5 + .rspec-metadata-mysql: &rspec-metadata-mysql <<: *rspec-metadata <<: *use-mysql +.rspec-metadata-mysql-rails5: &rspec-metadata-mysql-rails5 + <<: *rspec-metadata-mysql + <<: *rails5 + .spinach-metadata: &spinach-metadata <<: *dedicated-runner <<: *except-docs-and-qa <<: *pull-cache + <<: *rails5-variables stage: test script: - JOB_NAME=( $CI_JOB_NAME ) @@ -179,10 +202,18 @@ stages: <<: *spinach-metadata <<: *use-pg +.spinach-metadata-pg-rails5: &spinach-metadata-pg-rails5 + <<: *spinach-metadata-pg + <<: *rails5 + .spinach-metadata-mysql: &spinach-metadata-mysql <<: *spinach-metadata <<: *use-mysql +.spinach-metadata-mysql-rails5: &spinach-metadata-mysql-rails5 + <<: *spinach-metadata-mysql + <<: *rails5 + .only-canonical-masters: &only-canonical-masters only: - master@gitlab-org/gitlab-ce @@ -468,6 +499,70 @@ spinach-pg 1 2: *spinach-metadata-pg spinach-mysql 0 2: *spinach-metadata-mysql spinach-mysql 1 2: *spinach-metadata-mysql +rspec-pg-rails5 0 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 1 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 2 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 3 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 4 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 5 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 6 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 7 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 8 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 9 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 10 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 11 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 12 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 13 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 14 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 15 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 16 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 17 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 18 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 19 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 20 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 21 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 22 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 23 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 24 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 25 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 26 28: *rspec-metadata-pg-rails5 +rspec-pg-rails5 27 28: *rspec-metadata-pg-rails5 + +rspec-mysql-rails5 0 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 1 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 2 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 3 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 4 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 5 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 6 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 7 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 8 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 9 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 10 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 11 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 12 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 13 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 14 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 15 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 16 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 17 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 18 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 19 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 20 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 21 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 22 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 23 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 24 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 25 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 26 28: *rspec-metadata-mysql-rails5 +rspec-mysql-rails5 27 28: *rspec-metadata-mysql-rails5 + +spinach-pg-rails5 0 2: *spinach-metadata-pg-rails5 +spinach-pg-rails5 1 2: *spinach-metadata-pg-rails5 + +spinach-mysql-rails5 0 2: *spinach-metadata-mysql-rails5 +spinach-mysql-rails5 1 2: *spinach-metadata-mysql-rails5 + static-analysis: <<: *dedicated-no-docs-no-db-pull-cache-job dependencies: diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js index d78d4701974..7c90597f77c 100644 --- a/app/assets/javascripts/boards/services/board_service.js +++ b/app/assets/javascripts/boards/services/board_service.js @@ -19,7 +19,7 @@ export default class BoardService { } static generateIssuePath(boardId, id) { - return `${gon.relative_url_root}/-/boards/${boardId ? `/${boardId}` : ''}/issues${id ? `/${id}` : ''}`; + return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${id ? `/${id}` : ''}`; } all() { 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/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 15fed7d17c1..3ceab209f3f 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -42,7 +42,10 @@ module Boards ) end - attrs[:move_between_ids] = move_between_ids if move_between_ids + if move_between_ids + attrs[:move_between_ids] = move_between_ids + attrs[:board_group_id] = board.group&.id + end attrs end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index d7aa7e2347e..4161932ad2a 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -55,9 +55,10 @@ module Issues return unless params[:move_between_ids] after_id, before_id = params.delete(:move_between_ids) + board_group_id = params.delete(:board_group_id) - issue_before = get_issue_if_allowed(issue.project, before_id) if before_id - issue_after = get_issue_if_allowed(issue.project, after_id) if after_id + issue_before = get_issue_if_allowed(before_id, board_group_id) + issue_after = get_issue_if_allowed(after_id, board_group_id) issue.move_between(issue_before, issue_after) end @@ -84,8 +85,16 @@ module Issues private - def get_issue_if_allowed(project, id) - issue = project.issues.find(id) + def get_issue_if_allowed(id, board_group_id = nil) + return unless id + + issue = + if board_group_id + IssuesFinder.new(current_user, group_id: board_group_id).find_by(id: id) + else + project.issues.find(id) + end + issue if can?(current_user, :update_issue, issue) end 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/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/changelogs/unreleased/issue_44551.yml b/changelogs/unreleased/issue_44551.yml new file mode 100644 index 00000000000..d5265667b00 --- /dev/null +++ b/changelogs/unreleased/issue_44551.yml @@ -0,0 +1,5 @@ +--- +title: Fix 404 in group boards when moving issue between lists +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/zj-opt-out-list-commits-by-oid.yml b/changelogs/unreleased/zj-opt-out-list-commits-by-oid.yml new file mode 100644 index 00000000000..3871293ee04 --- /dev/null +++ b/changelogs/unreleased/zj-opt-out-list-commits-by-oid.yml @@ -0,0 +1,5 @@ +--- +title: ListCommitsByOid is executed by Gitaly by default +merge_request: +author: +type: performance 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/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/git/commit.rb b/lib/gitlab/git/commit.rb index 93037ed8d90..0fb82441bf8 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -231,7 +231,8 @@ module Gitlab # relation to each other. The last 10 commits for a branch for example, # should go through .where def batch_by_oid(repo, oids) - repo.gitaly_migrate(:list_commits_by_oid) do |is_enabled| + repo.gitaly_migrate(:list_commits_by_oid, + status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled repo.gitaly_commit_client.list_commits_by_oid(oids) else 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..b28c3c161b7 --- /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.all_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 new file mode 100644 index 00000000000..29410e2331c --- /dev/null +++ b/lib/gitlab/import_export/lfs_saver.rb @@ -0,0 +1,55 @@ +module Gitlab + module ImportExport + class LfsSaver + include Gitlab::ImportExport::CommandLineUtil + + def initialize(project:, shared:) + @project = project + @shared = shared + end + + def save + @project.all_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 + 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 + + 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/fixtures/exported-project.gz b/spec/fixtures/exported-project.gz Binary files differnew file mode 100644 index 00000000000..352384f16c8 --- /dev/null +++ b/spec/fixtures/exported-project.gz 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 new file mode 100644 index 00000000000..9b0e21deb2e --- /dev/null +++ b/spec/lib/gitlab/import_export/lfs_saver_spec.rb @@ -0,0 +1,62 @@ +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 locally stored' 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 + + 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 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 diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 0a6b6d880d3..dd0ad5f11bd 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -48,7 +48,7 @@ describe Boards::Issues::MoveService do parent.add_developer(user) end - it_behaves_like 'issues move service' + it_behaves_like 'issues move service', true end end end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 41237dd7160..f95474208f3 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -97,6 +97,37 @@ describe Issues::UpdateService, :mailer do expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) end + context 'when moving issue between issues from different projects' do + let(:group) { create(:group) } + let(:project_1) { create(:project, namespace: group) } + let(:project_2) { create(:project, namespace: group) } + let(:project_3) { create(:project, namespace: group) } + + let(:issue_1) { create(:issue, project: project_1) } + let(:issue_2) { create(:issue, project: project_2) } + let(:issue_3) { create(:issue, project: project_3) } + + before do + group.add_developer(user) + end + + it 'sorts issues as specified by parameters' do + # Moving all issues to end here like the last example won't work since + # all projects only have the same issue count + # so their relative_position will be the same. + issue_1.move_to_end + issue_2.move_after(issue_1) + issue_3.move_after(issue_2) + [issue_1, issue_2, issue_3].map(&:save) + + opts[:move_between_ids] = [issue_1.id, issue_2.id] + opts[:board_group_id] = group.id + + described_class.new(issue_3.project, user, opts).execute(issue_3) + expect(issue_2.relative_position).to be_between(issue_1.relative_position, issue_2.relative_position) + end + end + context 'when current user cannot admin issues in the project' do let(:guest) { create(:user) } before do 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) diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb index 4a4fbaa3a0e..737863ea411 100644 --- a/spec/support/shared_examples/services/boards/issues_move_service.rb +++ b/spec/support/shared_examples/services/boards/issues_move_service.rb @@ -1,4 +1,4 @@ -shared_examples 'issues move service' do +shared_examples 'issues move service' do |group| context 'when moving an issue between lists' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } } @@ -83,5 +83,18 @@ shared_examples 'issues move service' do expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) end + + if group + context 'when on a group board' do + it 'sends the board_group_id parameter' do + params.merge!(move_after_id: issue1.id, move_before_id: issue2.id) + + match_params = { move_between_ids: [issue1.id, issue2.id], board_group_id: parent.id } + expect(Issues::UpdateService).to receive(:new).with(issue.project, user, match_params).and_return(double(execute: build(:issue))) + + described_class.new(parent, user, params).execute(issue) + end + end + end end end |