Commit 6bdf064d authored by Douwe Maan's avatar Douwe Maan

Merge branch 'ee-dz-manifest-import' into 'master'

Add manifest import feature (EE port)

See merge request gitlab-org/gitlab-ee!6430
parents 3a9367a4 455a2f13
......@@ -30,7 +30,13 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception, prepend: true
helper_method :can?
helper_method :import_sources_enabled?, :github_import_enabled?, :gitea_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled?
helper_method :import_sources_enabled?, :github_import_enabled?,
:gitea_import_enabled?, :github_import_configured?,
:gitlab_import_enabled?, :gitlab_import_configured?,
:bitbucket_import_enabled?, :bitbucket_import_configured?,
:google_code_import_enabled?, :fogbugz_import_enabled?,
:git_import_enabled?, :gitlab_project_import_enabled?,
:manifest_import_enabled?
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
......@@ -359,6 +365,10 @@ class ApplicationController < ActionController::Base
Gitlab::CurrentSettings.import_sources.include?('gitlab_project')
end
def manifest_import_enabled?
Group.supports_nested_groups? && Gitlab::CurrentSettings.import_sources.include?('manifest')
end
# U2F (universal 2nd factor) devices need a unique identifier for the application
# to perform authentication.
# https://developers.yubico.com/U2F/App_ID.html
......
class Import::ManifestController < Import::BaseController
before_action :whitelist_query_limiting, only: [:create]
before_action :verify_import_enabled
before_action :ensure_import_vars, only: [:create, :status]
def new
end
def status
@already_added_projects = find_already_added_projects
already_added_import_urls = @already_added_projects.pluck(:import_url)
@pending_repositories = repositories.to_a.reject do |repository|
already_added_import_urls.include?(repository[:url])
end
end
def upload
group = Group.find(params[:group_id])
unless can?(current_user, :create_projects, group)
@errors = ["You don't have enough permissions to create projects in the selected group"]
render :new && return
end
manifest = Gitlab::ManifestImport::Manifest.new(params[:manifest].tempfile)
if manifest.valid?
session[:manifest_import_repositories] = manifest.projects
session[:manifest_import_group_id] = group.id
redirect_to status_import_manifest_path
else
@errors = manifest.errors
render :new
end
end
def jobs
render json: find_jobs
end
def create
repository = repositories.find do |project|
project[:id] == params[:repo_id].to_i
end
project = Gitlab::ManifestImport::ProjectCreator.new(repository, group, current_user).execute
if project.persisted?
render json: ProjectSerializer.new.represent(project)
else
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
end
private
def ensure_import_vars
unless group && repositories.present?
redirect_to(new_import_manifest_path)
end
end
def group
@group ||= Group.find_by(id: session[:manifest_import_group_id])
end
def repositories
@repositories ||= session[:manifest_import_repositories]
end
def find_jobs
find_already_added_projects.to_json(only: [:id], methods: [:import_status])
end
def find_already_added_projects
group.all_projects
.where(import_type: 'manifest')
.where(creator_id: current_user)
.includes(:import_state)
end
def verify_import_enabled
render_404 unless manifest_import_enabled?
end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/48939')
end
end
......@@ -5,7 +5,7 @@ module NamespacesHelper
params.dig(:project, :namespace_id) || params[:namespace_id]
end
def namespaces_options(selected = :current_user, display_path: false, extra_group: nil)
def namespaces_options(selected = :current_user, display_path: false, extra_group: nil, groups_only: false)
groups = current_user.manageable_groups
.joins(:route)
.includes(:route)
......@@ -22,10 +22,13 @@ module NamespacesHelper
options = []
options << options_for_group(groups, display_path: display_path, type: 'group')
options << options_for_group(users, display_path: display_path, type: 'user')
if selected == :current_user && current_user.namespace
selected = current_user.namespace.id
unless groups_only
options << options_for_group(users, display_path: display_path, type: 'user')
if selected == :current_user && current_user.namespace
selected = current_user.namespace.id
end
end
grouped_options_for_select(options, selected)
......
module Groups
class NestedCreateService < Groups::BaseService
attr_reader :group_path
attr_reader :group_path, :visibility_level
def initialize(user, params)
@current_user, @params = user, params.dup
@group_path = @params.delete(:group_path)
@visibility_level = @params.delete(:visibility_level) ||
Gitlab::CurrentSettings.current_application_settings.default_group_visibility
end
def execute
......@@ -36,11 +37,12 @@ module Groups
new_params = params.reverse_merge(
path: subgroup_name,
name: subgroup_name,
parent: last_group
parent: last_group,
visibility_level: visibility_level
)
new_params[:visibility_level] ||= Gitlab::CurrentSettings.current_application_settings.default_group_visibility
last_group = namespace_or_group(partial_path) || Groups::CreateService.new(current_user, new_params).execute
last_group = namespace_or_group(partial_path) ||
Groups::CreateService.new(current_user, new_params).execute
end
last_group
......
......@@ -21,23 +21,13 @@
%th= _('Status')
%tbody
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
%tr{ id: "project_#{project.id}", class: project_status_css_class(project.import_status) }
%td
= provider_project_link(provider, project.import_source)
%td
= link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
%i.fa.fa-check
= _('Done')
- elsif project.import_status == 'started'
%i.fa.fa-spinner.fa-spin
= _('Started')
- elsif project.import_status == 'failed'
= _('Failed')
- else
= project.human_import_status_name
= render 'import/project_status', project: project
- @repos.each do |repo|
%tr{ id: "repo_#{repo.id}", data: { qa: { repo_path: repo.full_name } } }
......@@ -61,6 +51,6 @@
= has_ci_cd_only_params? ? _('Connect') : _('Import')
= icon("spinner spin", class: "loading-icon")
.js-importer-status{ data: { jobs_import_path: "#{url_for([:jobs, :import, provider])}",
import_path: "#{url_for([:import, provider])}",
ci_cd_only: "#{has_ci_cd_only_params?}" } }
.js-importer-status{ data: { jobs_import_path: url_for([:jobs, :import, provider]),
import_path: url_for([:import, provider]),
ci_cd_only: has_ci_cd_only_params?.to_s } }
- case project.import_status
- when 'finished'
= icon('check')
= _('Done')
- when 'started'
= icon("spinner spin")
= _('Started')
- when 'failed'
= _('Failed')
- else
= project.human_import_status_name
= form_tag upload_import_manifest_path, multipart: true do
.form-group
= label_tag :group_id, nil, class: 'label-light' do
= _('Group')
.input-group
.input-group-prepend.has-tooltip{ title: root_url }
.input-group-text
= root_url
= select_tag :group_id, namespaces_options(nil, display_path: true, groups_only: true), { class: 'select2 js-select-namespace' }
.form-text.text-muted
= _('Choose the top-level group for your repository imports.')
.form-group
= label_tag :manifest, class: 'label-light' do
= _('Manifest')
= file_field_tag :manifest, class: 'form-control-file', required: true
.form-text.text-muted
= _('Import multiple repositories by uploading a manifest file.')
= link_to icon('question-circle'), help_page_path('user/project/import/manifest')
.append-bottom-10
= submit_tag _('List available repositories'), class: 'btn btn-success'
= link_to _('Cancel'), new_project_path, class: 'btn btn-cancel'
- page_title "Manifest file import"
- header_title "Projects", root_path
%h3.page-title
= _('Manifest file import')
- if @errors.present?
.alert.alert-danger
- @errors.each do |error|
= error
= render 'form'
- page_title "Manifest import"
- header_title "Projects", root_path
- provider = 'manifest'
%h3.page-title
= _('Manifest file import')
%p
= button_tag class: "btn btn-import btn-success js-import-all" do
= import_all_githubish_repositories_button_label
= icon("spinner spin", class: "loading-icon")
.table-responsive
%table.table.import-jobs
%thead
%tr
%th= _('Repository URL')
%th= _('To GitLab')
%th= _('Status')
%tbody
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: project_status_css_class(project.import_status) }
%td
= project.import_url
%td
= link_to_project project
%td.job-status
= render 'import/project_status', project: project
- @pending_repositories.each do |repository|
%tr{ id: "repo_#{repository[:id]}" }
%td
= repository[:url]
%td.import-target
= import_project_target(@group.full_path, repository[:path])
%td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do
= _('Import')
= icon("spinner spin", class: "loading-icon")
.js-importer-status{ data: { jobs_import_path: url_for([:jobs, :import, provider]),
import_path: url_for([:import, provider]) } }
- active_tab = local_assigns.fetch(:active_tab, 'blank')
- f = local_assigns.fetch(:f)
.project-import
.form-group.import-btn-container.clearfix
= f.label :visibility_level, class: 'label-light' do #the label here seems wrong
%h5
Import project from
.import-buttons
- if gitlab_project_import_enabled?
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export')
%div
- if github_import_enabled?
- if github_import_enabled?
%div
= link_to new_import_github_path, class: 'btn js-import-github' do
= icon('github', text: 'GitHub')
%div
- if bitbucket_import_enabled?
- if bitbucket_import_enabled?
%div
= link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do
= icon('bitbucket', text: 'Bitbucket')
- unless bitbucket_import_configured?
= render 'bitbucket_import_modal'
%div
- if gitlab_import_enabled?
- if gitlab_import_enabled?
%div
= link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do
= icon('gitlab', text: 'GitLab.com')
- unless gitlab_import_configured?
= render 'gitlab_import_modal'
%div
- if google_code_import_enabled?
- if google_code_import_enabled?
%div
= link_to new_import_google_code_path, class: 'btn import_google_code' do
= icon('google', text: 'Google Code')
%div
- if fogbugz_import_enabled?
- if fogbugz_import_enabled?
%div
= link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
= icon('bug', text: 'Fogbugz')
%div
- if gitea_import_enabled?
- if gitea_import_enabled?
%div
= link_to new_import_gitea_path, class: 'btn import_gitea' do
= custom_icon('go_logo')
Gitea
%div
- if git_import_enabled?
- if git_import_enabled?
%div
%button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' } }
= icon('git', text: 'Repo by URL')
- if manifest_import_enabled?
%div
= link_to new_import_manifest_path, class: 'btn import_manifest' do
= icon('file-text-o', text: 'Manifest file')
.js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') }
%hr
= form_for @project, html: { class: 'new_project' } do |f|
%hr
= render "shared/import_form", f: f
= render 'new_project_fields', f: f, project_name_id: "import-url-name"
......@@ -63,13 +63,12 @@
= render 'project_templates', f: f
.tab-pane.import-project-pane.js-toggle-container{ id: 'import-project-pane', class: active_when(active_tab == 'import'), role: 'tabpanel' }
= form_for @project, html: { class: 'new_project' } do |f|
- if import_sources_enabled?
= render 'import_project_pane', f: f, active_tab: active_tab
- else
.nothing-here-block
%h4 No import options available
%p Contact an administrator to enable options for importing your project.
- if import_sources_enabled?
= render 'import_project_pane', active_tab: active_tab
- else
.nothing-here-block
%h4 No import options available
%p Contact an administrator to enable options for importing your project.
-# EE-specific start
= render 'new_ci_cd_only_project_pane', active_tab: active_tab
......
---
title: Add ability to import multiple repositories by uploading a manifest file
merge_request: 20304
author:
type: added
......@@ -45,4 +45,10 @@ namespace :import do
resource :gitlab_project, only: [:create, :new] do
post :create
end
resource :manifest, only: [:create, :new], controller: :manifest do
get :status
get :jobs
post :upload
end
end
......@@ -118,7 +118,7 @@ PUT /application/settings
| `housekeeping_gc_period` | integer | no | Number of Git pushes after which 'git gc' is run. |
| `housekeeping_incremental_repack_period` | integer | no | Number of Git pushes after which an incremental 'git repack' is run. |
| `html_emails_enabled` | boolean | no | Enable HTML emails |
| `import_sources` | Array of strings | no | Sources to allow project import from, possible values: "github bitbucket gitlab google_code fogbugz git gitlab_project |
| `import_sources` | Array of strings | no | Sources to allow project import from, possible values: "github bitbucket gitlab google_code fogbugz git gitlab_project manifest |
| `koding_enabled` | boolean | no | Enable Koding integration. Default is `false`. |
| `koding_url` | string | yes (if `koding_enabled` is `true`) | The Koding instance URL for integration. |
| `max_artifacts_size` | integer | no | Maximum artifacts size in MB |
......
......@@ -12,6 +12,7 @@
1. [From TFS](tfs.md)
1. [From repo by URL](repo_by_url.md)
1. [From Gemnasium](gemnasium.md)
1. [By uploading a manifest file](manifest.md)
In addition to the specific migration documentation above, you can import any
Git repository via HTTP from the New Project page. Be aware that if the
......
# Import multiple repositories by uploading a manifest file
GitLab allows you to import all the required git repositories
based a manifest file like the one used by the Android repository.
>**Note:**
This feature requires [subgroups](../../group/subgroups/index.md) to be supported by your database.
You can do it by following next steps:
1. From your GitLab dashboard click **New project**
1. Switch to the **Import project** tab
1. Click on the **Manifest file** button
1. Provide GitLab with a manifest xml file
1. Select a group you want to import to (you need to create a group first if you don't have one)
1. Click **List available repositories**
1. You will be redirected to the import status page with projects list based on manifest file
1. Check the list and click 'Import all repositories' to start import.
![Manifest upload](img/manifest_upload.png)
![Manifest status](img/manifest_status.png)
### Manifest format
A manifest must be an XML file. There must be one `remote` tag with `review` attribute
that contains a URL to a git server. Each `project` tag must have `name` and `path` attribute.
GitLab will build URL to the repository by combining URL from `remote` tag with a project name.
A path attribute will be used to represent project path in GitLab system.
Below is a valid example of manifest file.
```xml
<manifest>
<remote review="https://android-review.googlesource.com/" />
<project path="build/make" name="platform/build" />
<project path="build/blueprint" name="platform/build/blueprint" />
</manifest>
```
As result next projects will be created:
| GitLab | Import URL |
|---|---|
| https://gitlab/YOUR_GROUP/build/make | https://android-review.googlesource.com/platform/build |
| https://gitlab/YOUR_GROUP/build/blueprint | https://android-review.googlesource.com/platform/build/blueprint |
......@@ -25,7 +25,7 @@ module API
optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility'
optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility'
optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.'
optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project],
optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project manifest],
desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com'
optional :disabled_oauth_sign_in_sources, type: Array[String], desc: 'Disable certain OAuth sign-in sources'
optional :enabled_git_access_protocol, type: String, values: %w[ssh http nil], desc: 'Allow only the selected protocols to be used for Git access.'
......
......@@ -16,7 +16,8 @@ module Gitlab
ImportSource.new('fogbugz', 'FogBugz', Gitlab::FogbugzImport::Importer),
ImportSource.new('git', 'Repo by URL', nil),
ImportSource.new('gitlab_project', 'GitLab export', Gitlab::ImportExport::Importer),
ImportSource.new('gitea', 'Gitea', Gitlab::LegacyGithubImport::Importer)
ImportSource.new('gitea', 'Gitea', Gitlab::LegacyGithubImport::Importer),
ImportSource.new('manifest', 'Manifest file', nil)
].freeze
class << self
......
# Class to parse manifest file and build a list of repositories for import
#
# <manifest>
# <remote review="https://android-review.googlesource.com/" />
# <project path="platform-common" name="platform" />
# <project path="platform/art" name="platform/art" />
# <project path="platform/device" name="platform/device" />
# </manifest>
#
# 1. Project path must be uniq and can't be part of other project path.
# For example, you can't have projects with 'foo' and 'foo/bar' paths.
# 2. Remote must be present with review attribute so GitLab knows
# where to fetch source code
module Gitlab
module ManifestImport
class Manifest
attr_reader :parsed_xml, :errors
def initialize(file)
@parsed_xml = Nokogiri::XML(file) { |config| config.strict }
@errors = []
rescue Nokogiri::XML::SyntaxError
@errors = ['The uploaded file is not a valid XML file.']
end
def projects
raw_projects.each_with_index.map do |project, i|
{
id: i,
name: project['name'],
path: project['path'],
url: repository_url(project['name'])
}
end
end
def valid?
return false if @errors.any?
unless validate_remote
@errors << 'Make sure a <remote> tag is present and is valid.'
end
unless validate_projects
@errors << 'Make sure every <project> tag has name and path attributes.'
end
@errors.empty?
end
private
def validate_remote
remote.present? && URI.parse(remote).host
rescue URI::Error
false
end
def validate_projects
raw_projects.all? do |project|
project['name'] && project['path']
end
end
def repository_url(name)
URI.join(remote, name).to_s
end
def remote
return @remote if defined?(@remote)
remote_tag = parsed_xml.css('manifest > remote').first
@remote = remote_tag['review'] if remote_tag
end
def raw_projects
@raw_projects ||= parsed_xml.css('manifest > project')
end
end
end
end
module Gitlab
module ManifestImport
class ProjectCreator
attr_reader :repository, :destination, :current_user
def initialize(repository, destination, current_user)
@repository = repository
@destination = destination
@current_user = current_user
end
def execute
group_full_path, _, project_path = repository[:path].rpartition('/')
group_full_path = File.join(destination.full_path, group_full_path) if destination
group = create_group_with_parents(group_full_path)
params = {
import_url: repository[:url],
import_type: 'manifest',
namespace_id: group.id,
path: project_path,
name: project_path,
visibility_level: destination.visibility_level
}
Projects::CreateService.new(current_user, params).execute
end
private
def create_group_with_parents(full_path)
params = {
group_path: full_path,
visibility_level: destination.visibility_level
}
Groups::NestedCreateService.new(current_user, params).execute
end
end
end
end
......@@ -1186,6 +1186,9 @@ msgstr ""
msgid "Choose file..."
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
msgid "Choose which groups you wish to synchronize to this secondary node."
msgstr ""
......@@ -3027,6 +3030,9 @@ msgstr ""
msgid "Graph"
msgstr ""
msgid "Group"
msgstr ""
msgid "Group CI/CD settings"
msgstr ""
......@@ -3278,6 +3284,9 @@ msgstr ""
msgid "Import in progress"
msgstr ""
msgid "Import multiple repositories by uploading a manifest file."
msgstr ""
msgid "Import repositories from GitHub"
msgstr ""
......@@ -3522,6 +3531,9 @@ msgstr ""
msgid "List"
msgstr ""
msgid "List available repositories"
msgstr ""
msgid "List your GitHub repositories"
msgstr ""
......@@ -3576,6 +3588,12 @@ msgstr ""
msgid "Manage your group’s membership while adding another level of security with SAML."
msgstr ""
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
msgid "Mar"
msgstr ""
......@@ -4829,6 +4847,9 @@ msgstr ""
msgid "Repository Settings"
msgstr ""
msgid "Repository URL"
msgstr ""
msgid "Repository has no locks."
msgstr ""
......
require 'spec_helper'
describe 'Import multiple repositories by uploading a manifest file', :js, :postgresql do
include Select2Helper
let(:user) { create(:admin) }
let(:group) { create(:group) }
before do
sign_in(user)
group.add_owner(user)
end
it 'parses manifest file and list repositories' do
visit new_import_manifest_path
attach_file('manifest', Rails.root.join('spec/fixtures/aosp_manifest.xml'))
click_on 'List available repositories'
expect(page).to have_button('Import all repositories')
expect(page).to have_content('https://android-review.googlesource.com/platform/build/blueprint')
end
it 'imports succesfully imports a project' do
visit new_import_manifest_path
attach_file('manifest', Rails.root.join('spec/fixtures/aosp_manifest.xml'))
click_on 'List available repositories'
page.within(first_row) do
click_on 'Import'
expect(page).to have_content 'Done'
expect(page).to have_content("#{group.full_path}/build/make")
end
end
it 'renders an error if invalid file was provided' do
visit new_import_manifest_path
attach_file('manifest', Rails.root.join('spec/fixtures/banana_sample.gif'))
click_on 'List available repositories'
expect(page).to have_content 'The uploaded file is not a valid XML file.'
end
def first_row
page.all('table.import-jobs tbody tr')[0]
end
end
......@@ -25,6 +25,22 @@ describe 'New project' do
expect(page).to have_link('GitLab export')
end
describe 'manifest import option' do
before do
visit new_project_path
find('#import-project-tab').click
end
context 'when using postgres', :postgresql do
it { expect(page).to have_link('Manifest file') }
end
context 'when using mysql', :mysql do
it { expect(page).not_to have_link('Manifest file') }
end
end
context 'Visibility level selector', :js do
Gitlab::VisibilityLevel.options.each do |key, level|
it "sets selector to #{key}" do
......@@ -201,5 +217,16 @@ describe 'New project' do
expect(current_path).to eq new_import_google_code_path
end
end
context 'from manifest file', :postgresql do
before do
first('.import_manifest').click
end
it 'shows import instructions' do
expect(page).to have_content('Manifest file import')
expect(current_path).to eq new_import_manifest_path
end
end
end
end
This diff is collapsed.
......@@ -28,6 +28,16 @@ describe NamespacesHelper do
expect(options).not_to include(admin_group.name)
expect(options).to include(user_group.name)
expect(options).to include(user.name)
end
it 'returns only groups if groups_only option is true' do
allow(helper).to receive(:current_user).and_return(user)
options = helper.namespaces_options(nil, groups_only: true)
expect(options).not_to include(user.name)
expect(options).to include(user_group.name)
end
context 'when nested groups are available', :nested_groups do
......
......@@ -12,7 +12,8 @@ describe Gitlab::ImportSources do
'FogBugz' => 'fogbugz',
'Repo by URL' => 'git',
'GitLab export' => 'gitlab_project',
'Gitea' => 'gitea'
'Gitea' => 'gitea',
'Manifest file' => 'manifest'
}
expect(described_class.options).to eq(expected)
......@@ -31,6 +32,7 @@ describe Gitlab::ImportSources do
git
gitlab_project
gitea
manifest
)
expect(described_class.values).to eq(expected)
......@@ -63,7 +65,8 @@ describe Gitlab::ImportSources do
'fogbugz' => Gitlab::FogbugzImport::Importer,
'git' => nil,
'gitlab_project' => Gitlab::ImportExport::Importer,
'gitea' => Gitlab::LegacyGithubImport::Importer
'gitea' => Gitlab::LegacyGithubImport::Importer,
'manifest' => nil
}
import_sources.each do |name, klass|
......@@ -82,7 +85,8 @@ describe Gitlab::ImportSources do
'fogbugz' => 'FogBugz',
'git' => 'Repo by URL',
'gitlab_project' => 'GitLab export',
'gitea' => 'Gitea'
'gitea' => 'Gitea',
'manifest' => 'Manifest file'
}
import_sources.each do |name, title|
......
require 'spec_helper'
describe Gitlab::ManifestImport::Manifest, :postgresql do
let(:file) { File.open(Rails.root.join('spec/fixtures/aosp_manifest.xml')) }
let(:manifest) { described_class.new(file) }
describe '#valid?' do
context 'valid file' do
it { expect(manifest.valid?).to be true }
end
context 'missing or invalid attributes' do
let(:file) { Tempfile.new('foo') }
before do
content = <<~EOS
<manifest>
<remote review="invalid-url" />
<project name="platform/build"/>
</manifest>
EOS
file.write(content)
file.rewind
end
it { expect(manifest.valid?).to be false }
describe 'errors' do
before do
manifest.valid?
end
it { expect(manifest.errors).to include('Make sure a <remote> tag is present and is valid.') }
it { expect(manifest.errors).to include('Make sure every <project> tag has name and path attributes.') }
end
end
end
describe '#projects' do
it { expect(manifest.projects.size).to eq(660) }
it { expect(manifest.projects[0][:name]).to eq('platform/build') }
it { expect(manifest.projects[0][:path]).to eq('build/make') }
it { expect(manifest.projects[0][:url]).to eq('https://android-review.googlesource.com/platform/build') }
end
end
require 'spec_helper'
describe Gitlab::ManifestImport::ProjectCreator, :postgresql do
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:repository) do
{
path: 'device/common',
url: 'https://android-review.googlesource.com/device/common'
}
end
before do
group.add_owner(user)
end
subject { described_class.new(repository, group, user) }
describe '#execute' do
it { expect(subject.execute).to be_a(Project) }
it { expect { subject.execute }.to change { Project.count }.by(1) }
it { expect { subject.execute }.to change { Group.count }.by(1) }
it 'creates project with valid full path and import url' do
subject.execute
project = Project.last
expect(project.full_path).to eq(File.join(group.path, 'device/common'))
expect(project.import_url).to eq('https://android-review.googlesource.com/device/common')
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment