diff options
38 files changed, 430 insertions, 171 deletions
diff --git a/CHANGELOG b/CHANGELOG index 364690286e1..e7f38c93be9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -176,6 +176,8 @@ v 8.8.0 - API support for the 'since' and 'until' operators on commit requests (Paco Guzman) - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) - Expire repository exists? and has_visible_content? caches after a push if necessary + - Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi) + - GitLab project import and export functionality - Fix unintentional filtering bug in Issue/MR sorted by milestone due (Takuya Noguchi) - Fix adding a todo for private group members (Ahmad Sherif) - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3 @@ -218,7 +218,7 @@ gem 'jquery-turbolinks', '~> 2.1.0' gem 'addressable', '~> 2.3.8' gem 'bootstrap-sass', '~> 3.3.0' -gem 'font-awesome-rails', '~> 4.2' +gem 'font-awesome-rails', '~> 4.6.1' gem 'gitlab_emoji', '~> 0.3.0' gem 'gon', '~> 6.0.1' gem 'jquery-atwho-rails', '~> 1.3.2' diff --git a/Gemfile.lock b/Gemfile.lock index dfc15700494..fe6785249fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -253,7 +253,7 @@ GEM fog-xml (0.1.2) fog-core nokogiri (~> 1.5, >= 1.5.11) - font-awesome-rails (4.5.0.1) + font-awesome-rails (4.6.1.0) railties (>= 3.2, < 5.1) foreman (0.78.0) thor (~> 0.19.1) @@ -881,7 +881,7 @@ DEPENDENCIES fog-google (~> 0.3) fog-local (~> 0.3) fog-openstack (~> 0.1) - font-awesome-rails (~> 4.2) + font-awesome-rails (~> 4.6.1) foreman fuubar (~> 2.0.0) gemnasium-gitlab-service (~> 0.2) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index cd6ae507cf1..726acbdb3ed 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -24,7 +24,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception helper_method :abilities, :can?, :current_application_settings - helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled? + helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled? rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -326,6 +326,10 @@ class ApplicationController < ActionController::Base current_application_settings.import_sources.include?('git') end + def gitlab_project_import_enabled? + current_application_settings.import_sources.include?('gitlab_project') + end + def two_factor_authentication_required? current_application_settings.require_two_factor_authentication end diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb new file mode 100644 index 00000000000..d4063747da4 --- /dev/null +++ b/app/controllers/import/gitlab_projects_controller.rb @@ -0,0 +1,57 @@ +class Import::GitlabProjectsController < Import::BaseController + before_action :verify_gitlab_project_import_enabled + before_action :verify_project_and_namespace_access + + def new + @namespace_id = project_params[:namespace_id] + @path = project_params[:path] + end + + def create + unless file_is_valid? + return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." }) + end + + @project = Gitlab::ImportExport::ProjectCreator.new(Namespace.find(project_params[:namespace_id]), + current_user, + File.expand_path(params[:file].path), + project_params[:path]).execute + + flash[:notice] = "The project import has been started." + + if @project.saved? + redirect_to( + project_path(@project), + notice: "Project '#{@project.name}' is being imported." + ) + else + render 'new' + end + end + + private + + def file_is_valid? + params[:file].respond_to?(:read) && params[:file].content_type == 'application/x-gzip' + end + + def verify_project_and_namespace_access + unless namespace_access? + render_403 + end + end + + def namespace_access? + can?(current_user, :create_projects, Namespace.find(project_params[:namespace_id])) + end + + def verify_gitlab_project_import_enabled + render_404 unless gitlab_project_import_enabled? + end + + def project_params + params.permit( + :path, :namespace_id, + ) + end +end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index a6479c42d94..9a6d4be2fc8 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -7,7 +7,7 @@ class ProjectsController < Projects::ApplicationController before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists? # Authorize - before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping] + before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export] before_action :event_filter, only: [:show, :activity] layout :determine_layout @@ -185,6 +185,23 @@ class ProjectsController < Projects::ApplicationController ) end + def export + @project.add_export_job(current_user_id: current_user.id) + + redirect_to( + edit_project_path(@project), + notice: "Project export started." + ) + end + + def download_export + if export_project_path + send_file export_project_path, disposition: 'attachment' + else + render_404 + end + end + def toggle_star current_user.toggle_star(@project) @project.reload @@ -247,4 +264,10 @@ class ProjectsController < Projects::ApplicationController def get_id project.repository.root_ref end + + def export_project_path + # TODO: move this, probably to ImportExport and refactor + folder = File.join(Settings.shared['path'], 'tmp/project_exports', @project.path_with_namespace) + Dir.glob("#{folder}/*export.tar.gz").max_by {|f| File.ctime(f)} + end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 5e5d170a9f3..aeebdf90ba6 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -348,4 +348,10 @@ module ProjectsHelper message.strip.gsub(Gitlab.config.gitlab_shell.repos_path.chomp('/'), "[REPOS PATH]") end + + def db_export_list + YAML.load_file(Gitlab::ImportExport.config_file)['project_tree'].map do |relation| + relation.is_a?(Hash) ? relation.keys.first.to_s : relation.to_s + end + ['notes', 'merge_request_diffs'] + end end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index fdf1e9f5afc..0d9558db68c 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -50,6 +50,19 @@ module Emails subject: subject("Invitation declined")) end + def project_was_exported_email(current_user, project) + @project = project + mail(to: current_user.notification_email, + subject: subject("Project was exported")) + end + + def project_was_not_exported_email(current_user, project, errors) + @project = project + @errors = errors + mail(to: current_user.notification_email, + subject: subject("Project export error")) + end + def project_was_moved_email(project_id, user_id, old_path_with_namespace) @current_user = @user = User.find user_id @project = Project.find project_id diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index a744f937918..b2fb54ba5e7 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -123,7 +123,7 @@ class ApplicationSetting < ActiveRecord::Base default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], restricted_signup_domains: Settings.gitlab['restricted_signup_domains'], - import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'], + import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git', 'gitlab_project'], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], max_artifacts_size: Settings.artifacts['max_size'], require_two_factor_authentication: false, diff --git a/app/models/project.rb b/app/models/project.rb index ab7947a0880..323bde5d5c4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -348,16 +348,6 @@ class Project < ActiveRecord::Base joins(join_body).reorder('join_note_counts.amount DESC') end - - def create_from_import_job(current_user_id:, tmp_file:, namespace_id:, project_path:) - job_id = ProjectImportWorker.perform_async(current_user_id, tmp_file, namespace_id, project_path) - - if job_id - Rails.logger.info "Import job started for export #{tmp_file} with job ID #{job_id}" - else - Rails.logger.error "Import job failed to start for #{tmp_file}" - end - end end def team @@ -492,6 +482,10 @@ class Project < ActiveRecord::Base Gitlab::UrlSanitizer.new(import_url).masked_url end + def gitlab_project_import? + import_type == 'gitlab_project' + end + def check_limit unless creator.can_create_project? or namespace.kind == 'group' projects_limit = creator.projects_limit diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 91ca82ed3b7..79534a06a1d 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -246,6 +246,14 @@ class NotificationService end end + def project_exported(project, current_user) + mailer.project_was_exported_email(current_user, project).deliver_later + end + + def project_not_exported(project, current_user, errors) + mailer.project_was_not_exported_email(current_user, project, errors).deliver_later + end + protected # Get project users with WATCH notification level diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 61cac5419ad..ffac357e91d 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -54,7 +54,7 @@ module Projects @project.import_start if @project.import? - after_create_actions if @project.persisted? + after_create_actions if @project.persisted? && !@project.gitlab_project_import? if @project.errors.empty? @project.add_import_job if @project.import? diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index 25524c1c060..d6752377ce5 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -12,8 +12,9 @@ module Projects def save_all if [version_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save) Gitlab::ImportExport::Saver.save(shared: @shared) + notify_success else - cleanup_and_notify_worker + cleanup_and_notify end end @@ -37,10 +38,20 @@ module Projects Gitlab::ImportExport::WikiRepoSaver.new(project: project, shared: @shared) end - def cleanup_and_notify_worker + def cleanup_and_notify FileUtils.rm_rf(@shared.export_path) + + notify_error raise Gitlab::ImportExport::Error.new(@shared.errors.join(', ')) end + + def notify_success + notification_service.project_exported(@project, @current_user) + end + + def notify_error + notification_service.project_not_exported(@project, @current_user, @shared.errors.join(', ')) + end end end end diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index c4838d31f2f..b4b4c34b012 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -9,7 +9,8 @@ module Projects 'fogbugz', 'gitlab', 'github', - 'google_code' + 'google_code', + 'gitlab_project' ] def execute @@ -37,7 +38,7 @@ module Projects def import_repository begin - gitlab_shell.import_repository(project.path_with_namespace, project.import_url) + gitlab_shell.import_repository(project.path_with_namespace, project.import_url) unless @project.gitlab_project_import? rescue Gitlab::Shell::Error => e raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}" end @@ -58,6 +59,8 @@ module Projects end def importer + return Gitlab::ImportExport::Importer if @project.gitlab_project_import? + class_name = "Gitlab::#{project.import_type.camelize}Import::Importer" class_name.constantize.new(project) end diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml new file mode 100644 index 00000000000..1e7a65715b0 --- /dev/null +++ b/app/views/import/gitlab_projects/new.html.haml @@ -0,0 +1,23 @@ +- page_title "GitLab Import" +- header_title "Projects", root_path +%h3.page-title + = icon('gitlab') + Import projects from GitLab +%hr + += form_tag import_gitlab_project_path, class: 'form-horizontal', multipart: true do + %p + Project will be imported to path #{@path} + + %p + To get started add your exported project file below: + .form-group + = hidden_field_tag :namespace_id, @namespace_id + = hidden_field_tag :path, @path + = label_tag :file, class: 'control-label' do + %span GitLab project export + .col-sm-10 + = file_field_tag :file, class: '' + + .form-actions + = submit_tag 'Continue to the next step', class: 'btn btn-create' diff --git a/app/views/notify/project_was_exported_email.html.haml b/app/views/notify/project_was_exported_email.html.haml new file mode 100644 index 00000000000..fb6f4ac3efc --- /dev/null +++ b/app/views/notify/project_was_exported_email.html.haml @@ -0,0 +1,8 @@ +%p + Project #{@project.name} was exported succesfully +%p + The project export can be downloaded from: + = link_to download_export_namespace_project_url(@project.namespace, @project) do + = @project.name_with_namespace + " export" +%p + The download link will expire in 24 hours. diff --git a/app/views/notify/project_was_exported_email.text.erb b/app/views/notify/project_was_exported_email.text.erb new file mode 100644 index 00000000000..fcd801c237c --- /dev/null +++ b/app/views/notify/project_was_exported_email.text.erb @@ -0,0 +1,6 @@ +Project <%= @project.name %> was exported succesfully + +The project export can be downloaded from: +<%= download_export_namespace_project_url(@project.namespace, @project) %> + +The download link will expire in 24 hours. diff --git a/app/views/notify/project_was_not_exported_email.html.haml b/app/views/notify/project_was_not_exported_email.html.haml new file mode 100644 index 00000000000..78d4751b64a --- /dev/null +++ b/app/views/notify/project_was_not_exported_email.html.haml @@ -0,0 +1,7 @@ +%p + Project #{@project.name} couldn't be exported. +%p + The errors we encountered were: + + %h3{style: "background: black; color: red;"} + #{@errors} diff --git a/app/views/notify/project_was_not_exported_email.text.erb b/app/views/notify/project_was_not_exported_email.text.erb new file mode 100644 index 00000000000..ad730d2162b --- /dev/null +++ b/app/views/notify/project_was_not_exported_email.text.erb @@ -0,0 +1,5 @@ +Project <%= @project.name %> couldn't be exported. + +The errors we encountered were: + +<%= @errors %> diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 8449fe1e4e0..2441e71a5d2 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -120,6 +120,37 @@ = link_to 'Housekeeping', housekeeping_namespace_project_path(@project.namespace, @project), method: :post, class: "btn btn-save" %hr + .row.prepend-top-default + .col-lg-3 + %h4.prepend-top-0 + Export project + %p.append-bottom-0 + %p + Generates a compressed export file of the project and sends a link to download the export. + .col-lg-9 + + = link_to 'Export project', export_namespace_project_path(@project.namespace, @project), + method: :post, class: "btn btn-default" + + %p.append-bottom-0 + %p + .row.prepend-top-default + Clicking on Export project, will produce a compressed file that will be sent as a link to your registered e-mail address. + + .bs-callout.bs-callout-info + %p.append-bottom-0 + %p + The following items will be exported: + %ul + %li Project and wiki repository + %li Project uploads + %li DB items, including configuration + %ul + - db_export_list.each do |export_relation| + %li + %code #{export_relation} + + %hr - if can? current_user, :archive_project, @project .row.prepend-top-default .col-lg-3 diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index f9ac16b32f3..79ef9284268 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -88,7 +88,12 @@ - if git_import_enabled? = link_to "#", class: 'btn js-toggle-button import_git' do %i.fa.fa-git - %span Any repo by URL + %span Repo by URL + + - if gitlab_project_import_enabled? + = link_to new_import_gitlab_project_path, class: 'btn import_gitlab_project project-submit' do + %i.fa.fa-gitlab + %span GitLab project .js-toggle-content.hide = render "shared/import_form", f: f @@ -122,3 +127,18 @@ $('.modal-header .close').bind('click', function() { $(".modal").hide(); }); + $('.import_gitlab_project').bind('click', function() { + var _href = $("a.import_gitlab_project").attr("href"); + $(".import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val()); + }); + $('.import_gitlab_project').attr('disabled',true) + $('.import_gitlab_project').attr('title', 'Project path required.'); + $('#project_path').keyup(function(){ + if($(this).val().length !=0) { + $('.import_gitlab_project').attr('disabled', false); + $('.import_gitlab_project').attr('title',''); + } else { + $('.import_gitlab_project').attr('disabled',true); + $('.import_gitlab_project').attr('title', 'Project path required.'); + } + }) diff --git a/app/workers/project_export_worker.rb b/app/workers/project_export_worker.rb index 3616b37d2ad..d2dcdb46219 100644 --- a/app/workers/project_export_worker.rb +++ b/app/workers/project_export_worker.rb @@ -7,6 +7,7 @@ class ProjectExportWorker def perform(current_user_id, project_id) current_user = User.find(current_user_id) project = Project.find(project_id) + ::Projects::ImportExport::ExportService.new(project, current_user).execute end end diff --git a/app/workers/project_import_worker.rb b/app/workers/project_import_worker.rb index b2902c3278e..b18d453702e 100644 --- a/app/workers/project_import_worker.rb +++ b/app/workers/project_import_worker.rb @@ -7,10 +7,10 @@ class ProjectImportWorker def perform(current_user_id, tmp_file, namespace_id, path) current_user = User.find(current_user_id) - project = Gitlab::ImportExport::ImportService.execute(archive_file: tmp_file, - owner: current_user, - namespace_id: namespace_id, - project_path: path) + project = Gitlab::ImportExport::Importer.execute(archive_file: tmp_file, + owner: current_user, + namespace_id: namespace_id, + project_path: path) if project project.repository.after_import else diff --git a/config/routes.rb b/config/routes.rb index e2492fbab46..bc1112de0f5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -454,6 +454,8 @@ Rails.application.routes.draw do post :housekeeping post :toggle_star post :markdown_preview + post :export + post :download_export get :autocomplete_sources get :activity end diff --git a/features/dashboard/new_project.feature b/features/dashboard/new_project.feature index 76392068357..56b4a639c01 100644 --- a/features/dashboard/new_project.feature +++ b/features/dashboard/new_project.feature @@ -14,7 +14,7 @@ Background: @javascript Scenario: I should see instructions on how to import from Git URL Given I see "New Project" page - When I click on "Any repo by URL" + When I click on "Repo by URL" Then I see instructions on how to import from Git URL @javascript diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb index a0aad66184d..ad20201c2c5 100644 --- a/features/steps/dashboard/new_project.rb +++ b/features/steps/dashboard/new_project.rb @@ -19,7 +19,8 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps expect(page).to have_link('GitLab.com') expect(page).to have_link('Gitorious.org') expect(page).to have_link('Google Code') - expect(page).to have_link('Any repo by URL') + expect(page).to have_link('Repo by URL') + expect(page).to have_link('GitLab project') end step 'I click on "Import project from GitHub"' do @@ -36,7 +37,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps end end - step 'I click on "Any repo by URL"' do + step 'I click on "Repo by URL"' do first('.import_git').click end diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 5e7532f57ae..38c0f1aba47 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -36,7 +36,7 @@ module Gitlab default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], restricted_signup_domains: Settings.gitlab['restricted_signup_domains'], - import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'], + import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git', 'gitlab_project'], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], max_artifacts_size: Settings.artifacts['max_size'], require_two_factor_authentication: false, diff --git a/lib/gitlab/gitlab_import/project_creator.rb b/lib/gitlab/gitlab_import/project_creator.rb index 77c33db4b59..3d0418261bb 100644 --- a/lib/gitlab/gitlab_import/project_creator.rb +++ b/lib/gitlab/gitlab_import/project_creator.rb @@ -11,7 +11,7 @@ module Gitlab end def execute - project = ::Projects::CreateService.new( + ::Projects::CreateService.new( current_user, name: repo["name"], path: repo["path"], @@ -22,8 +22,6 @@ module Gitlab import_source: repo["path_with_namespace"], import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{@session_data[:gitlab_access_token]}@") ).execute - - project end end end diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb new file mode 100644 index 00000000000..0e70d9282d5 --- /dev/null +++ b/lib/gitlab/import_export/file_importer.rb @@ -0,0 +1,30 @@ +module Gitlab + module ImportExport + class FileImporter + include Gitlab::ImportExport::CommandLineUtil + + def self.import(*args) + new(*args).import + end + + def initialize(archive_file:, shared:) + @archive_file = archive_file + @shared = shared + end + + def import + FileUtils.mkdir_p(@shared.export_path) + decompress_archive + rescue => e + @shared.error(e) + false + end + + private + + def decompress_archive + untar_zxf(archive: @archive_file, dir: @shared.export_path) + end + end + end +end diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 3796fc8cd02..164ab6238c4 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -30,8 +30,6 @@ project_tree: # Only include the following attributes for the models specified. included_attributes: project: - - :name - - :path - :description - :issues_enabled - :merge_requests_enabled diff --git a/lib/gitlab/import_export/import_service.rb b/lib/gitlab/import_export/import_service.rb deleted file mode 100644 index db71f72efec..00000000000 --- a/lib/gitlab/import_export/import_service.rb +++ /dev/null @@ -1,69 +0,0 @@ -module Gitlab - module ImportExport - class ImportService - - def self.execute(*args) - new(*args).execute - end - - def initialize(archive_file:, owner:, namespace_id:, project_path:) - @archive_file = archive_file - @current_user = owner - @namespace = Namespace.find(namespace_id) - @shared = Gitlab::ImportExport::Shared.new(relative_path: path_with_namespace(project_path), project_path: project_path) - end - - def execute - Gitlab::ImportExport::Importer.import(archive_file: @archive_file, - shared: @shared) - if check_version! && [project_tree, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore) - project_tree.project - else - project_tree.project.destroy if project_tree.project - nil - end - end - - private - - def check_version! - Gitlab::ImportExport::VersionChecker.check!(shared: @shared) - end - - def project_tree - @project_tree ||= Gitlab::ImportExport::ProjectTreeRestorer.new(user: @current_user, - shared: @shared, - namespace_id: @namespace.id) - end - - def repo_restorer - Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: repo_path, - shared: @shared, - project: project_tree.project) - end - - def wiki_restorer - Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: wiki_repo_path, - shared: @shared, - project: ProjectWiki.new(project_tree.project), - wiki: true) - end - - def uploads_restorer - Gitlab::ImportExport::UploadsRestorer.new(project: project_tree.project, shared: @shared) - end - - def path_with_namespace(project_path) - File.join(@namespace.path, project_path) - end - - def repo_path - File.join(@shared.export_path, 'project.bundle') - end - - def wiki_repo_path - File.join(@shared.export_path, 'project.wiki.bundle') - end - end - end -end diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb index 8020aab3da9..d096e17bdf0 100644 --- a/lib/gitlab/import_export/importer.rb +++ b/lib/gitlab/import_export/importer.rb @@ -1,29 +1,79 @@ module Gitlab module ImportExport class Importer - include Gitlab::ImportExport::CommandLineUtil - def self.import(*args) - new(*args).import + def self.execute(*args) + new(*args).execute end - def initialize(archive_file:, shared:) - @archive_file = archive_file - @shared = shared + def initialize(project) + @archive_file = project.import_source + @current_user = project.creator + @shared = Gitlab::ImportExport::Shared.new(relative_path: path_with_namespace(@project.path)) end - def import - FileUtils.mkdir_p(@shared.export_path) - decompress_archive - rescue => e - @shared.error(e) - false + def execute + Gitlab::ImportExport::FileImporter.import(archive_file: @archive_file, + shared: @shared) + if check_version! && [project_tree, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore) + project_tree.project + else + project_tree.project.destroy if project_tree.project + nil + end end private - def decompress_archive - untar_zxf(archive: @archive_file, dir: @shared.export_path) + def check_version! + Gitlab::ImportExport::VersionChecker.check!(shared: @shared) + end + + def project_tree + @project_tree ||= Gitlab::ImportExport::ProjectTreeRestorer.new(user: @current_user, + shared: @shared, + project: @project) + end + + def repo_restorer + Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: repo_path, + shared: @shared, + project: project_tree.project) + end + + def wiki_restorer + Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: wiki_repo_path, + shared: @shared, + project: ProjectWiki.new(project_tree.project), + wiki: true) + end + + def uploads_restorer + Gitlab::ImportExport::UploadsRestorer.new(project: project_tree.project, shared: @shared) + end + + def path_with_namespace(project_path) + File.join(@namespace.path, project_path) + end + + def repo_path + File.join(@shared.export_path, 'project.bundle') + end + + def wiki_repo_path + File.join(@shared.export_path, 'project.wiki.bundle') + end + + def attributes_for_todo + { user_id: @current_user.id, + project_id: project_tree.project.id, + target_type: 'Project', + target: project_tree.project, + action: Todo::IMPORTED, + author_id: @current_user.id, + state: :pending, + target_id: project_tree.project.id + } end end end diff --git a/lib/gitlab/import_export/project_creator.rb b/lib/gitlab/import_export/project_creator.rb new file mode 100644 index 00000000000..6f1e3867efb --- /dev/null +++ b/lib/gitlab/import_export/project_creator.rb @@ -0,0 +1,24 @@ +module Gitlab + module ImportExport + class ProjectCreator + + def initialize(namespace_id, current_user, file, project_path) + @namespace_id = namespace_id + @current_user = current_user + @file = file + @project_path = project_path + end + + def execute + ::Projects::CreateService.new( + current_user, + name: @project_path, + path: @project_path, + namespace_id: namespace_id, + import_type: "gitlab_project", + import_source: @file + ).execute + end + end + end +end diff --git a/lib/gitlab/import_export/project_factory.rb b/lib/gitlab/import_export/project_factory.rb deleted file mode 100644 index 6cd4736649b..00000000000 --- a/lib/gitlab/import_export/project_factory.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Gitlab - module ImportExport - module ProjectFactory - extend self - - def create(project_params:, user:, namespace_id:) - project = Project.new(project_params.except('id')) - project.creator = user - check_namespace(namespace_id, project, user) - end - - def check_namespace(namespace_id, project, user) - if namespace_id - # Find matching namespace and check if it allowed - # for current user if namespace_id passed. - if allowed_namespace?(user, namespace_id) - project.namespace_id = namespace_id - else - project.namespace_id = nil - deny_namespace(project) - end - else - # Set current user namespace if namespace_id is nil - project.namespace_id = user.namespace_id - end - project - end - - private - - def allowed_namespace?(user, namespace_id) - namespace = Namespace.find_by(id: namespace_id) - user.can?(:create_projects, namespace) - end - - def deny_namespace(project) - project.errors.add(:namespace, "is not valid") - end - end - end -end diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index 75a261bb121..92727528d01 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -2,12 +2,11 @@ module Gitlab module ImportExport class ProjectTreeRestorer - def initialize(user:, shared:, namespace_id:) + def initialize(user:, shared:, project:) @path = File.join(shared.export_path, 'project.json') @user = user - @project_path = shared.opts[:project_path] - @namespace_id = namespace_id @shared = shared + @project = project end def restore @@ -21,7 +20,7 @@ module Gitlab end def project - @project ||= create_project + @restored_project ||= restore_project end private @@ -57,14 +56,10 @@ module Gitlab end end - def create_project + def restore_project project_params = @tree_hash.reject { |_key, value| value.is_a?(Array) } - project = Gitlab::ImportExport::ProjectFactory.create( - project_params: project_params, user: @user, namespace_id: @namespace_id) - project.path = @project_path - project.name = @project_path - project.save! - project + @project.update(project_params) + @project end # Given a relation hash containing one or more models and its relationships, diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb index ccfdfbe73e8..4cae819d356 100644 --- a/lib/gitlab/import_sources.rb +++ b/lib/gitlab/import_sources.rb @@ -20,7 +20,8 @@ module Gitlab 'Gitorious.org' => 'gitorious', 'Google Code' => 'google_code', 'FogBugz' => 'fogbugz', - 'Any repo by URL' => 'git', + 'Repo by URL' => 'git', + 'GitLab project' => 'gitlab_project' } end diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb new file mode 100644 index 00000000000..03298149b60 --- /dev/null +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +feature 'project import', feature: true, js: true do + include Select2Helper + + let(:user) { create(:admin) } + let!(:namespace) { create(:namespace, name: "asd", owner: user) } + let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') } + let(:export_path) { "#{Dir::tmpdir}/import_file_spec" } + let(:project) { Project.last } + + background do + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + login_as(user) + end + + after(:each) do + FileUtils.rm_rf(export_path, secure: true) + end + + scenario 'user imports an exported project successfully' do + expect(Project.all.count).to be_zero + + visit new_project_path + + select2('2', from: '#project_namespace_id') + fill_in :project_path, with:'test-project-path', visible: true + click_link 'GitLab project' + + expect(page).to have_content('GitLab project export') + expect(URI.parse(current_url).query).to eq('namespace_id=2&path=test-project-path') + + attach_file('file', file) + + click_on 'Continue to the next step' # import starts + + expect(project).not_to be_nil + expect(project.issues).not_to be_empty + expect(project.merge_requests).not_to be_empty + expect(project.repo_exists?).to be true + expect(wiki_exists?).to be true + end + + def wiki_exists? + wiki = ProjectWiki.new(project) + File.exists?(wiki.repository.path_to_repo) && !wiki.repository.empty? + end +end diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz Binary files differnew file mode 100644 index 00000000000..b85055070d8 --- /dev/null +++ b/spec/features/projects/import_export/test_project_export.tar.gz |