summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarin Jankovski <maxlazio@gmail.com>2018-04-05 11:22:52 +0200
committerMarin Jankovski <maxlazio@gmail.com>2018-04-05 11:22:52 +0200
commit1bca36b66f65efd0ff1d358e3e569bc84806e959 (patch)
tree40b500bbda184cbe588e0a5f4835e3957c14407b
parentc48a9c73ebbf1e48852d42651382ff4b174b3728 (diff)
parent32d2206b01b97cdbd6cdc13b25d98c3d3db048c4 (diff)
downloadgitlab-ce-1bca36b66f65efd0ff1d358e3e569bc84806e959.tar.gz
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce
-rw-r--r--.gitlab-ci.yml95
-rw-r--r--app/assets/javascripts/boards/services/board_service.js2
-rw-r--r--app/controllers/projects/lfs_api_controller.rb2
-rw-r--r--app/models/project.rb10
-rw-r--r--app/services/boards/issues/move_service.rb5
-rw-r--r--app/services/issues/update_service.rb17
-rw-r--r--app/services/projects/import_export/export_service.rb6
-rw-r--r--app/views/projects/_export.html.haml2
-rw-r--r--changelogs/unreleased/bvl-export-import-lfs.yml5
-rw-r--r--changelogs/unreleased/issue_44551.yml5
-rw-r--r--changelogs/unreleased/zj-opt-out-list-commits-by-oid.yml5
-rw-r--r--doc/user/project/settings/import_export.md2
-rw-r--r--lib/gitlab/checks/lfs_integrity.rb3
-rw-r--r--lib/gitlab/git/commit.rb3
-rw-r--r--lib/gitlab/import_export/importer.rb11
-rw-r--r--lib/gitlab/import_export/lfs_restorer.rb43
-rw-r--r--lib/gitlab/import_export/lfs_saver.rb55
-rw-r--r--spec/fixtures/exported-project.gzbin0 -> 2306 bytes
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb64
-rw-r--r--spec/lib/gitlab/import_export/lfs_restorer_spec.rb75
-rw-r--r--spec/lib/gitlab/import_export/lfs_saver_spec.rb62
-rw-r--r--spec/models/project_spec.rb16
-rw-r--r--spec/services/boards/issues/move_service_spec.rb2
-rw-r--r--spec/services/issues/update_service_spec.rb31
-rw-r--r--spec/services/projects/import_export/export_service_spec.rb43
-rw-r--r--spec/support/shared_examples/services/boards/issues_move_service.rb15
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
new file mode 100644
index 00000000000..352384f16c8
--- /dev/null
+++ b/spec/fixtures/exported-project.gz
Binary files differ
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