Commit 2f83794d authored by Douwe Maan's avatar Douwe Maan

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

Add manifest import

See merge request gitlab-org/gitlab-ce!20304
parents 94627fd7 751e1786
......@@ -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)
......@@ -351,6 +357,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
......@@ -3,7 +3,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)
......@@ -20,10 +20,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"
......@@ -55,13 +55,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.
.save-project-loader.d-none
.center
......
---
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
......@@ -105,7 +105,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 |
......
......@@ -11,6 +11,7 @@
1. [From SVN](svn.md)
1. [From TFS](tfs.md)
1. [From repo by URL](repo_by_url.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
......@@ -981,6 +981,9 @@ msgstr ""
msgid "Choose file..."
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
msgid "Choose which repositories you want to import."
msgstr ""
......@@ -2447,6 +2450,9 @@ msgstr ""
msgid "Graph"
msgstr ""
msgid "Group"
msgstr ""
msgid "Group CI/CD settings"
msgstr ""
......@@ -2650,6 +2656,9 @@ msgstr ""
msgid "Import in progress"
msgstr ""
msgid "Import multiple repositories by uploading a manifest file."
msgstr ""
msgid "Import repositories from GitHub"
msgstr ""
......@@ -2859,6 +2868,9 @@ msgstr ""
msgid "List"
msgstr ""
msgid "List available repositories"
msgstr ""
msgid "List your GitHub repositories"
msgstr ""
......@@ -2898,6 +2910,12 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
msgid "Manifest"
msgstr ""
msgid "Manifest file import"
msgstr ""
msgid "Mar"
msgstr ""
......@@ -3920,6 +3938,9 @@ msgstr ""
msgid "Repository Settings"
msgstr ""
msgid "Repository URL"
msgstr ""
msgid "Repository maintenance"
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