Commit 72188f8d authored by Rémy Coutable's avatar Rémy Coutable Committed by Douglas Barbosa Alexandre

Merge branch '22348-gitea-importer' into 'master'

It adds a brand new importer for Gitea!

This is a continuation of !6945 started by @bkc.

Gitea aims to be 100% GitHub-compatible but there's a few differences:

- Gitea is not an OAuth provider (yet): https://github.com/go-gitea/gitea/issues/27
  - This means we cannot map Gitea users given an assignee ID => assignees are not set on imported issues and merge requests
- No releases API for now: https://github.com/go-gitea/gitea/issues/330
- API version is `v1` (GitHub is `v3`)
- The IID field for milestones is `id` compared to `number` in GitHub.
- Issues, PRs, milestones, labels don't have a `url` field (the importer now fallback to `''` in that case)

**Known issues:**

- Comments are not imported because comments JSON always have a blank `html_url`/`issue_url`/`pull_request_url`, so the IID cannot be extracted and the issuable cannot be found... :( This is tracked in https://github.com/go-gitea/gitea/issues/401, and solved by https://github.com/gogits/gogs/pull/3624 but this needs to be submitted / merged in Gitea.

This is noted in the documentation.

## Are there points in the code the reviewer needs to double check?

1. I've made `Import::GiteaController` inherit from `Import::GithubController` since both controllers should be identical in the long-term and their current differences are small.
1. I've added a base `IssuableFormatter` class from which `IssueFormatter` & `PullRequestFormatter` inherit
1. I've added shared examples for GitHub/Gitea importer classes
1. I've made `Gitlab::ImportSources` more robust and tested! 🎄 
1. I've added routing specs for import routes! 🎄 

Closes #22348

See merge request !8116
parent 136be3e4
...@@ -25,7 +25,7 @@ class ApplicationController < ActionController::Base ...@@ -25,7 +25,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception protect_from_forgery with: :exception
helper_method :can?, :current_application_settings helper_method :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?, :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?
rescue_from Encoding::CompatibilityError do |exception| rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception) log_exception(exception)
...@@ -245,6 +245,10 @@ class ApplicationController < ActionController::Base ...@@ -245,6 +245,10 @@ class ApplicationController < ActionController::Base
current_application_settings.import_sources.include?('github') current_application_settings.import_sources.include?('github')
end end
def gitea_import_enabled?
current_application_settings.import_sources.include?('gitea')
end
def github_import_configured? def github_import_configured?
Gitlab::OAuth::Provider.enabled?(:github) Gitlab::OAuth::Provider.enabled?(:github)
end end
......
class Import::GiteaController < Import::GithubController
def new
if session[access_token_key].present? && session[host_key].present?
redirect_to status_import_url
end
end
def personal_access_token
session[host_key] = params[host_key]
super
end
def status
@gitea_host_url = session[host_key]
super
end
private
def host_key
:"#{provider}_host_url"
end
# Overriden methods
def provider
:gitea
end
# Gitea is not yet an OAuth provider
# See https://github.com/go-gitea/gitea/issues/27
def logged_in_with_provider?
false
end
def provider_auth
if session[access_token_key].blank? || session[host_key].blank?
redirect_to new_import_gitea_url,
alert: 'You need to specify both an Access Token and a Host URL.'
end
end
def client_options
{ host: session[host_key], api_version: 'v1' }
end
end
class Import::GithubController < Import::BaseController class Import::GithubController < Import::BaseController
before_action :verify_github_import_enabled before_action :verify_import_enabled
before_action :github_auth, only: [:status, :jobs, :create] before_action :provider_auth, only: [:status, :jobs, :create]
rescue_from Octokit::Unauthorized, with: :github_unauthorized rescue_from Octokit::Unauthorized, with: :provider_unauthorized
helper_method :logged_in_with_github?
def new def new
if logged_in_with_github? if logged_in_with_provider?
go_to_github_for_permissions go_to_provider_for_permissions
elsif session[:github_access_token] elsif session[access_token_key]
redirect_to status_import_github_url redirect_to status_import_url
end end
end end
def callback def callback
session[:github_access_token] = client.get_token(params[:code]) session[access_token_key] = client.get_token(params[:code])
redirect_to status_import_github_url redirect_to status_import_url
end end
def personal_access_token def personal_access_token
session[:github_access_token] = params[:personal_access_token] session[access_token_key] = params[:personal_access_token]
redirect_to status_import_github_url redirect_to status_import_url
end end
def status def status
@repos = client.repos @repos = client.repos
@already_added_projects = current_user.created_projects.where(import_type: "github") @already_added_projects = current_user.created_projects.where(import_type: provider)
already_added_projects_names = @already_added_projects.pluck(:import_source) already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject!{ |repo| already_added_projects_names.include? repo.full_name } @repos.reject! { |repo| already_added_projects_names.include? repo.full_name }
end end
def jobs def jobs
jobs = current_user.created_projects.where(import_type: "github").to_json(only: [:id, :import_status]) jobs = current_user.created_projects.where(import_type: provider).to_json(only: [:id, :import_status])
render json: jobs render json: jobs
end end
...@@ -44,8 +42,8 @@ class Import::GithubController < Import::BaseController ...@@ -44,8 +42,8 @@ class Import::GithubController < Import::BaseController
namespace_path = params[:target_namespace].presence || current_user.namespace_path namespace_path = params[:target_namespace].presence || current_user.namespace_path
@target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path) @target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path)
if current_user.can?(:create_projects, @target_namespace) if can?(current_user, :create_projects, @target_namespace)
@project = Gitlab::GithubImport::ProjectCreator.new(repo, @project_name, @target_namespace, current_user, access_params).execute @project = Gitlab::GithubImport::ProjectCreator.new(repo, @project_name, @target_namespace, current_user, access_params, type: provider).execute
else else
render 'unauthorized' render 'unauthorized'
end end
...@@ -54,34 +52,63 @@ class Import::GithubController < Import::BaseController ...@@ -54,34 +52,63 @@ class Import::GithubController < Import::BaseController
private private
def client def client
@client ||= Gitlab::GithubImport::Client.new(session[:github_access_token]) @client ||= Gitlab::GithubImport::Client.new(session[access_token_key], client_options)
end
def verify_import_enabled
render_404 unless import_enabled?
end
def go_to_provider_for_permissions
redirect_to client.authorize_url(callback_import_url)
end end
def verify_github_import_enabled def import_enabled?
render_404 unless github_import_enabled? __send__("#{provider}_import_enabled?")
end end
def github_auth def new_import_url
if session[:github_access_token].blank? public_send("new_import_#{provider}_url")
go_to_github_for_permissions
end end
def status_import_url
public_send("status_import_#{provider}_url")
end end
def go_to_github_for_permissions def callback_import_url
redirect_to client.authorize_url(callback_import_github_url) public_send("callback_import_#{provider}_url")
end end
def github_unauthorized def provider_unauthorized
session[:github_access_token] = nil session[access_token_key] = nil
redirect_to new_import_github_url, redirect_to new_import_url,
alert: 'Access denied to your GitHub account.' alert: "Access denied to your #{Gitlab::ImportSources.title(provider.to_s)} account."
end end
def logged_in_with_github? def access_token_key
current_user.identities.exists?(provider: 'github') :"#{provider}_access_token"
end end
def access_params def access_params
{ github_access_token: session[:github_access_token] } { github_access_token: session[access_token_key] }
end
# The following methods are overriden in subclasses
def provider
:github
end
def logged_in_with_provider?
current_user.identities.exists?(provider: provider)
end
def provider_auth
if session[access_token_key].blank?
go_to_provider_for_permissions
end
end
def client_options
{}
end end
end end
...@@ -4,8 +4,10 @@ module ImportHelper ...@@ -4,8 +4,10 @@ module ImportHelper
"#{namespace}/#{name}" "#{namespace}/#{name}"
end end
def github_project_link(path_with_namespace) def provider_project_link(provider, path_with_namespace)
link_to path_with_namespace, github_project_url(path_with_namespace), target: '_blank' url = __send__("#{provider}_project_url", path_with_namespace)
link_to path_with_namespace, url, target: '_blank'
end end
private private
...@@ -20,4 +22,8 @@ module ImportHelper ...@@ -20,4 +22,8 @@ module ImportHelper
provider = Gitlab.config.omniauth.providers.find { |p| p.name == 'github' } provider = Gitlab.config.omniauth.providers.find { |p| p.name == 'github' }
@github_url = provider.fetch('url', 'https://github.com') if provider @github_url = provider.fetch('url', 'https://github.com') if provider
end end
def gitea_project_url(path_with_namespace)
"#{@gitea_host_url.sub(%r{/+\z}, '')}/#{path_with_namespace}"
end
end end
...@@ -533,6 +533,10 @@ class Project < ActiveRecord::Base ...@@ -533,6 +533,10 @@ class Project < ActiveRecord::Base
import_type == 'gitlab_project' import_type == 'gitlab_project'
end end
def gitea_import?
import_type == 'gitea'
end
def check_limit def check_limit
unless creator.can_create_project? or namespace.kind == 'group' unless creator.can_create_project? or namespace.kind == 'group'
projects_limit = creator.projects_limit projects_limit = creator.projects_limit
......
...@@ -4,15 +4,6 @@ module Projects ...@@ -4,15 +4,6 @@ module Projects
class Error < StandardError; end class Error < StandardError; end
ALLOWED_TYPES = [
'bitbucket',
'fogbugz',
'gitlab',
'github',
'google_code',
'gitlab_project'
]
def execute def execute
add_repository_to_project unless project.gitlab_project_import? add_repository_to_project unless project.gitlab_project_import?
...@@ -64,14 +55,11 @@ module Projects ...@@ -64,14 +55,11 @@ module Projects
end end
def has_importer? def has_importer?
ALLOWED_TYPES.include?(project.import_type) Gitlab::ImportSources.importer_names.include?(project.import_type)
end end
def importer def importer
return Gitlab::ImportExport::Importer.new(project) if @project.gitlab_project_import? Gitlab::ImportSources.importer(project.import_type).new(project)
class_name = "Gitlab::#{project.import_type.camelize}Import::Importer"
class_name.constantize.new(project)
end end
def unknown_url? def unknown_url?
......
- provider = local_assigns.fetch(:provider)
- provider_title = Gitlab::ImportSources.title(provider)
%p.light
Select projects you want to import.
%hr
%p
= button_tag class: "btn btn-import btn-success js-import-all" do
Import all projects
= icon("spinner spin", class: "loading-icon")
.table-responsive
%table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead
%tr
%th= "From #{provider_title}"
%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
= provider_project_link(provider, project.import_source)
%td
= link_to project.path_with_namespace, [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
- else
= project.human_import_status_name
- @repos.each do |repo|
%tr{id: "repo_#{repo.id}"}
%td
= provider_project_link(provider, repo.full_name)
%td.import-target
%fieldset.row
.input-group
.project-path.input-group-btn
- if current_user.can_select_namespace?
- selected = params[:namespace_id] || :current_user
- opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner.login, path: repo.owner.login) } : {}
= select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 }
- else
= text_field_tag :path, current_user.namespace_path, class: "input-large form-control", tabindex: 1, disabled: true
%span.input-group-addon /
= text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true
%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])}" } }
- page_title "Gitea Import"
- header_title "Projects", root_path
%h3.page-title
= custom_icon('go_logo')
Import Projects from Gitea
%p
To get started, please enter your Gitea Host URL and a
= succeed '.' do
= link_to 'Personal Access Token', 'https://github.com/gogits/go-gogs-client/wiki#access-token'
= form_tag personal_access_token_import_gitea_path, class: 'form-horizontal' do
.form-group
= label_tag :gitea_host_url, 'Gitea Host URL', class: 'control-label'
.col-sm-4
= text_field_tag :gitea_host_url, nil, placeholder: 'https://try.gitea.io', class: 'form-control'
.form-group
= label_tag :personal_access_token, 'Personal Access Token', class: 'control-label'
.col-sm-4
= text_field_tag :personal_access_token, nil, class: 'form-control'
.form-actions
= submit_tag 'List Your Gitea Repositories', class: 'btn btn-create'
- page_title "Gitea Import"
- header_title "Projects", root_path
%h3.page-title
= custom_icon('go_logo')
Import Projects from Gitea
= render 'import/githubish_status', provider: 'gitea'
- page_title "GitHub import" - page_title "GitHub Import"
- header_title "Projects", root_path - header_title "Projects", root_path
%h3.page-title %h3.page-title
%i.fa.fa-github = icon 'github', text: 'Import Projects from GitHub'
Import projects from GitHub
%p.light = render 'import/githubish_status', provider: 'github'
Select projects you want to import.
%hr
%p
= button_tag class: "btn btn-import btn-success js-import-all" do
Import all projects
= icon("spinner spin", class: "loading-icon")
.table-responsive
%table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead
%tr
%th From GitHub
%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
= github_project_link(project.import_source)
%td
= link_to project.path_with_namespace, [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
- else
= project.human_import_status_name
- @repos.each do |repo|
%tr{id: "repo_#{repo.id}"}
%td
= github_project_link(repo.full_name)
%td.import-target
%fieldset.row
.input-group
.project-path.input-group-btn
- if current_user.can_select_namespace?
- selected = params[:namespace_id] || :current_user
- opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner.login, path: repo.owner.login) } : {}
= select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 }
- else
= text_field_tag :path, current_user.namespace_path, class: "input-large form-control", tabindex: 1, disabled: true
%span.input-group-addon /
= text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true
%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: "#{jobs_import_github_path}", import_path: "#{import_github_path}" } }
...@@ -68,6 +68,11 @@ ...@@ -68,6 +68,11 @@
- if fogbugz_import_enabled? - if fogbugz_import_enabled?
= link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
= icon('bug', text: 'Fogbugz') = icon('bug', text: 'Fogbugz')
%div
- if gitea_import_enabled?
= link_to new_import_gitea_url, class: 'btn import_gitea' do
= custom_icon('go_logo')
Gitea
%div %div
- if git_import_enabled? - if git_import_enabled?
= link_to "#", class: 'btn js-toggle-button import_git' do = link_to "#", class: 'btn js-toggle-button import_git' do
......
<svg xmlns="http://www.w3.org/2000/svg" width="<%= size %>" height="<%= size %>" viewBox="0 0 16 16"><g fill-rule="evenodd" transform="translate(0 1)"><path d="m14 15.01h1v-8.02c0-3.862-3.134-6.991-7-6.991-3.858 0-7 3.13-7 6.991v8.02h1v-8.02c0-3.306 2.691-5.991 6-5.991 3.314 0 6 2.682 6 5.991v8.02m-10.52-13.354c-.366-.402-.894-.655-1.48-.655-1.105 0-2 .895-2 2 0 .868.552 1.606 1.325 1.883.102-.321.226-.631.371-.93-.403-.129-.695-.507-.695-.953 0-.552.448-1 1-1 .306 0 .58.138.764.354.222-.25.461-.483.717-.699m9.04-.002c.366-.401.893-.653 1.479-.653 1.105 0 2 .895 2 2 0 .867-.552 1.606-1.324 1.883-.101-.321-.225-.632-.37-.931.403-.129.694-.507.694-.952 0-.552-.448-1-1-1-.305 0-.579.137-.762.353-.222-.25-.461-.483-.717-.699"/><path d="m5.726 7.04h1.557v.124c0 .283-.033.534-.1.752-.065.202-.175.391-.33.566-.35.394-.795.591-1.335.591-.527 0-.979-.19-1.355-.571-.376-.382-.564-.841-.564-1.377 0-.547.191-1.01.574-1.391.382-.382.848-.574 1.396-.574.295 0 .57.06.825.181.244.12.484.316.72.586l-.405.388c-.309-.412-.686-.618-1.13-.618-.399 0-.733.138-1 .413-.27.27-.405.609-.405 1.015 0 .42.151.766.452 1.037.282.252.587.378.915.378.28 0 .531-.094.754-.283.223-.19.347-.418.373-.683h-.94v-.535m2.884.061c0-.53.194-.986.583-1.367.387-.381.853-.571 1.396-.571.537 0 .998.192 1.382.576.386.384.578.845.578 1.384 0 .542-.194 1-.581 1.379-.389.379-.858.569-1.408.569-.487 0-.923-.168-1.311-.505-.426-.373-.64-.861-.64-1.465m.574.007c0 .417.14.759.42 1.028.278.269.6.403.964.403.395 0 .729-.137 1-.41.272-.277.408-.613.408-1.01 0-.402-.134-.739-.403-1.01-.267-.273-.597-.41-.991-.41-.392 0-.723.137-.993.41-.27.27-.405.604-.405 1m-.184 3.918c.525.026.812.063.812.063.271.025.324-.096.116-.273 0 0-.775-.813-1.933-.813-1.159 0-1.923.813-1.923.813-.211.174-.153.3.12.273 0 0 .286-.037.81-.063v.477c0 .268.224.5.5.5.268 0 .5-.223.5-.498v-.252.25c0 .268.224.5.5.5.268 0 .5-.223.5-.498v-.478m-1-1.023c.552 0 1-.224 1-.5 0-.276-.448-.5-1-.5-.552 0-1 .224-1 .5 0 .276.448.5 1 .5"/></g></svg>
---
title: New Gitea importer
merge_request: 8116
author:
...@@ -213,7 +213,7 @@ Settings.gitlab.default_projects_features['builds'] = true if Settin ...@@ -213,7 +213,7 @@ Settings.gitlab.default_projects_features['builds'] = true if Settin
Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil? Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil?
Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
Settings.gitlab['domain_whitelist'] ||= [] Settings.gitlab['domain_whitelist'] ||= []
Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab google_code fogbugz git gitlab_project] Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab google_code fogbugz git gitlab_project gitea]
Settings.gitlab['trusted_proxies'] ||= [] Settings.gitlab['trusted_proxies'] ||= []
Settings.gitlab['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config', 'no_todos_messages.yml')) Settings.gitlab['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config', 'no_todos_messages.yml'))
......
...@@ -6,6 +6,12 @@ namespace :import do ...@@ -6,6 +6,12 @@ namespace :import do
get :jobs get :jobs
end end
resource :gitea, only: [:create, :new], controller: :gitea do
post :personal_access_token
get :status
get :jobs
end
resource :gitlab, only: [:create], controller: :gitlab do resource :gitlab, only: [:create], controller: :gitlab do
get :status get :status
get :callback get :callback
......
...@@ -45,7 +45,7 @@ module Gitlab ...@@ -45,7 +45,7 @@ module Gitlab
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
domain_whitelist: Settings.gitlab['domain_whitelist'], domain_whitelist: Settings.gitlab['domain_whitelist'],
import_sources: %w[github bitbucket gitlab google_code fogbugz git gitlab_project], import_sources: %w[gitea github bitbucket gitlab google_code fogbugz git gitlab_project],
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'], max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false, require_two_factor_authentication: false,
......
...@@ -15,6 +15,10 @@ module Gitlab ...@@ -15,6 +15,10 @@ module Gitlab
end end
end end
def url
raw_data.url || ''
end
private private
def gitlab_user_id(github_id) def gitlab_user_id(github_id)
......
...@@ -4,10 +4,12 @@ module Gitlab ...@@ -4,10 +4,12 @@ module Gitlab
GITHUB_SAFE_REMAINING_REQUESTS = 100 GITHUB_SAFE_REMAINING_REQUESTS = 100
GITHUB_SAFE_SLEEP_TIME = 500 GITHUB_SAFE_SLEEP_TIME = 500
attr_reader :access_token attr_reader :access_token, :host, :api_version
def initialize(access_token) def initialize(access_token, host: nil, api_version: 'v3')
@access_token = access_token @access_token = access_token
@host = host.to_s.sub(%r{/+\z}, '')
@api_version = api_version
if access_token if access_token
::Octokit.auto_paginate = false ::Octokit.auto_paginate = false
...@@ -17,7 +19,7 @@ module Gitlab ...@@ -17,7 +19,7 @@ module Gitlab
def api def api
@api ||= ::Octokit::Client.new( @api ||= ::Octokit::Client.new(
access_token: access_token, access_token: access_token,
api_endpoint: github_options[:site], api_endpoint: api_endpoint,
# If there is no config, we're connecting to github.com and we # If there is no config, we're connecting to github.com and we
# should verify ssl. # should verify ssl.
connection_options: { connection_options: {
...@@ -64,6 +66,14 @@ module Gitlab ...@@ -64,6 +66,14 @@ module Gitlab
private private
def api_endpoint
if host.present? && api_version.present?
"#{host}/api/#{api_version}"
else
github_options[:site]
end
end
def config def config
Gitlab.config.omniauth.providers.find { |provider| provider.name == "github" } Gitlab.config.omniauth.providers.find { |provider| provider.name == "github" }
end end
......
...@@ -3,7 +3,7 @@ module Gitlab ...@@ -3,7 +3,7 @@ module Gitlab
class Importer class Importer
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
attr_reader :client, :errors, :project, :repo, :repo_url attr_reader :errors, :project, :repo, :repo_url
def initialize(project) def initialize(project)
@project = project @project = project
...@@ -11,12 +11,27 @@ module Gitlab ...@@ -11,12 +11,27 @@ module Gitlab
@repo_url = project.import_url @repo_url = project.import_url
@errors = [] @errors = []
@labels = {} @labels = {}
end
if credentials def client
@client = Client.new(credentials[:user]) return @client if defined?(@client)
else unless credentials
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}" raise Projects::ImportService::Error,
"Unable to find project import data credentials for project ID: #{@project.id}"
end
opts = {}
# Gitea plan to be GitHub compliant
if project.gitea_import?
uri = URI.parse(project.import_url)
host = "#{uri.scheme}://#{uri.host}:#{uri.port}#{uri.path}".sub(%r{/?[\w-]+/[\w-]+\.git\z}, '')
opts = {
host: host,
api_version: 'v1'
}
end end
@client = Client.new(credentials[:user], opts)
end end
def execute def execute
...@@ -35,7 +50,13 @@ module Gitlab ...@@ -35,7 +50,13 @@ module Gitlab
import_comments(:issues) import_comments(:issues)
import_comments(:pull_requests) import_comments(:pull_requests)
import_wiki import_wiki
# Gitea doesn't have a Release API yet
# See https://github.com/go-gitea/gitea/issues/330
unless project.gitea_import?
import_releases import_releases
end
handle_errors handle_errors
true true
...@@ -44,7 +65,9 @@ module Gitlab ...@@ -44,7 +65,9 @@ module Gitlab
private private
def credentials def credentials
@credentials ||= project.import_data.credentials if project.import_data return @credentials if defined?(@credentials)
@credentials = project.import_data ? project.import_data.credentials : nil
end end
def handle_errors def handle_errors
...@@ -60,9 +83,10 @@ module Gitlab ...@@ -60,9 +83,10 @@ module Gitlab
fetch_resources(:labels, repo, per_page: 100) do |labels| fetch_resources(:labels, repo, per_page: 100) do |labels|
labels.each do |raw| labels.each do |raw|
begin begin
LabelFormatter.new(project, raw).create! gh_label = LabelFormatter.new(project, raw)
gh_label.create!
rescue => e rescue => e
errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(gh_label.url), errors: e.message }
end end
end end
end end
...@@ -74,9 +98,10 @@ module Gitlab ...@@ -74,9 +98,10 @@ module Gitlab
fetch_resources(:milestones, repo, state: :all, per_page: 100) do |milestones| fetch_resources(:milestones, repo, state: :all, per_page: 100) do |milestones|
milestones.each do |raw| milestones.each do |raw|
begin begin
MilestoneFormatter.new(project, raw).create! gh_milestone = MilestoneFormatter.new(project, raw)
gh_milestone.create!
rescue => e rescue => e
errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(gh_milestone.url), errors: e.message }
end end
end end
end end
...@@ -97,7 +122,7 @@ module Gitlab ...@@ -97,7 +122,7 @@ module Gitlab
apply_labels(issuable, raw) apply_labels(issuable, raw)
rescue => e rescue => e
errors << { type: :issue, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } errors << { type: :issue, url: Gitlab::UrlSanitizer.sanitize(gh_issue.url), errors: e.message }
end end
end end
end end
...@@ -106,18 +131,23 @@ module Gitlab ...@@ -106,18 +131,23 @@ module Gitlab
def import_pull_requests def import_pull_requests
fetch_resources(:pull_requests, repo, state: :all, sort: :created, direction: :asc, per_page: 100) do |pull_requests| fetch_resources(:pull_requests, repo, state: :all, sort: :created, direction: :asc, per_page: 100) do |pull_requests|
pull_requests.each do |raw| pull_requests.each do |raw|
pull_request = PullRequestFormatter.new(project, raw) gh_pull_request = PullRequestFormatter.new(project, raw)
next unless pull_request.valid? next unless gh_pull_request.valid?
begin begin
restore_source_branch(pull_request) unless pull_request.source_branch_exists? restore_source_branch(gh_pull_request) unless gh_pull_request.source_branch_exists?
restore_target_branch(pull_request) unless pull_request.target_branch_exists? restore_target_branch(gh_pull_request) unless gh_pull_request.target_branch_exists?
pull_request.create! merge_request = gh_pull_request.create!
# Gitea doesn't return PR in the Issue API endpoint, so labels must be assigned at this stage
if project.gitea_import?
apply_labels(merge_request, raw)
end
rescue => e rescue => e
errors << { type: :pull_request, url: Gitlab::UrlSanitizer.sanitize(pull_request.url), errors: e.message } errors << { type: :pull_request, url: Gitlab::UrlSanitizer.sanitize(gh_pull_request.url), errors: e.message }
ensure ensure
clean_up_restored_branches(pull_request) clean_up_restored_branches(gh_pull_request)
end end
end end
end end
...@@ -233,7 +263,7 @@ module Gitlab ...@@ -233,7 +263,7 @@ module Gitlab
gh_release = ReleaseFormatter.new(project, raw) gh_release = ReleaseFormatter.new(project, raw)
gh_release.create! if gh_release.valid? gh_release.create! if gh_release.valid?
rescue => e rescue => e
errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(gh_release.url), errors: e.message }
end end
end end
end end
......
module Gitlab
module GithubImport
class IssuableFormatter < BaseFormatter
def project_association
raise NotImplementedError
end
def number
raw_data.number
end
def find_condition
{ iid: number }
end
private
def state
raw_data.state == 'closed' ? 'closed' : 'opened'
end
def assigned?
raw_data.assignee.present?
end
def assignee_id
if assigned?
gitlab_user_id(raw_data.assignee.id)
end
end
def author
raw_data.user.login
end
def author_id
gitlab_author_id || project.creator_id
end
def body
raw_data.body || ""
end
def description
if gitlab_author_id
body
else
formatter.author_line(author) + body
end
end
def milestone
if raw_data.milestone.present?
milestone = MilestoneFormatter.new(project, raw_data.milestone)
project.milestones.find_by(milestone.find_condition)
end
end
end
end
end
module Gitlab module Gitlab
module GithubImport module GithubImport
class IssueFormatter < BaseFormatter class IssueFormatter < IssuableFormatter
def attributes def attributes
{ {
iid: number, iid: number,
...@@ -24,59 +24,9 @@ module Gitlab ...@@ -24,59 +24,9 @@ module Gitlab
:issues :issues
end end
def find_condition
{ iid: number }
end
def number
raw_data.number
end
def pull_request? def pull_request?
raw_data.pull_request.present? raw_data.pull_request.present?
end end
private
def assigned?
raw_data.assignee.present?
end
def assignee_id
if assigned?
gitlab_user_id(raw_data.assignee.id)
end
end
def author
raw_data.user.login
end
def author_id
gitlab_author_id || project.creator_id
end
def body
raw_data.body || ""
end
def description
if gitlab_author_id
body
else
formatter.author_line(author) + body
end
end
def milestone
if raw_data.milestone.present?
project.milestones.find_by(iid: raw_data.milestone.number)
end
end
def state
raw_data.state == 'closed' ? 'closed' : 'opened'
end
end end
end end
end end
...@@ -3,7 +3,7 @@ module Gitlab ...@@ -3,7 +3,7 @@ module Gitlab
class MilestoneFormatter < BaseFormatter class MilestoneFormatter < BaseFormatter
def attributes def attributes
{ {
iid: raw_data.number, iid: number,
project: project, project: project,
title: raw_data.title, title: raw_data.title,
description: raw_data.description, description: raw_data.description,
...@@ -19,7 +19,15 @@ module Gitlab ...@@ -19,7 +19,15 @@ module Gitlab
end end
def find_condition def find_condition
{ iid: raw_data.number } { iid: number }
end
def number
if project.gitea_import?
raw_data.id
else
raw_data.number
end
end end
private private
......
module Gitlab module Gitlab
module GithubImport module GithubImport
class ProjectCreator class ProjectCreator
attr_reader :repo, :name, :namespace, :current_user, :session_data attr_reader :repo, :name, :namespace, :current_user, :session_data, :type
def initialize(repo, name, namespace, current_user, session_data) def initialize(repo, name, namespace, current_user, session_data, type: 'github')
@repo = repo @repo = repo
@name = name @name = name
@namespace = namespace @namespace = namespace
@current_user = current_user @current_user = current_user
@session_data = session_data @session_data = session_data
@type = type
end end
def execute def execute
...@@ -19,7 +20,7 @@ module Gitlab ...@@ -19,7 +20,7 @@ module Gitlab
description: repo.description, description: repo.description,
namespace_id: namespace.id, namespace_id: namespace.id,
visibility_level: visibility_level, visibility_level: visibility_level,
import_type: "github", import_type: type,
import_source: repo.full_name, import_source: repo.full_name,
import_url: import_url, import_url: import_url,
skip_wiki: skip_wiki skip_wiki: skip_wiki
...@@ -29,7 +30,7 @@ module Gitlab ...@@ -29,7 +30,7 @@ module Gitlab
private private
def import_url def import_url
repo.clone_url.sub('https://', "https://#{session_data[:github_access_token]}@") repo.clone_url.sub('://', "://#{session_data[:github_access_token]}@")
end end
def visibility_level def visibility_level
......
module Gitlab module Gitlab
module GithubImport module GithubImport
class PullRequestFormatter < BaseFormatter class PullRequestFormatter < IssuableFormatter
delegate :exists?, :project, :ref, :repo, :sha, to: :source_branch, prefix: true delegate :exists?, :project, :ref, :repo, :sha, to: :source_branch, prefix: true
delegate :exists?, :project, :ref, :repo, :sha, to: :target_branch, prefix: true delegate :exists?, :project, :ref, :repo, :sha, to: :target_branch, prefix: true
...@@ -28,14 +28,6 @@ module Gitlab ...@@ -28,14 +28,6 @@ module Gitlab
:merge_requests :merge_requests
end end
def find_condition
{ iid: number }
end
def number
raw_data.number
end
def valid? def valid?
source_branch.valid? && target_branch.valid? source_branch.valid? && target_branch.valid?
end end
...@@ -60,55 +52,13 @@ module Gitlab ...@@ -60,55 +52,13 @@ module Gitlab
end end
end end
def url
raw_data.url
end
private private
def assigned?
raw_data.assignee.present?
end
def assignee_id
if assigned?
gitlab_user_id(raw_data.assignee.id)
end
end
def author
raw_data.user.login
end
def author_id
gitlab_author_id || project.creator_id
end
def body
raw_data.body || ""
end
def description
if gitlab_author_id
body
else
formatter.author_line(author) + body
end
end
def milestone
if raw_data.milestone.present?
project.milestones.find_by(iid: raw_data.milestone.number)
end
end
def state def state
@state ||= if raw_data.state == 'closed' && raw_data.merged_at.present? if raw_data.state == 'closed' && raw_data.merged_at.present?
'merged' 'merged'
elsif raw_data.state == 'closed'
'closed'
else else
'opened' super
end end
end end
end end
......
...@@ -7,21 +7,38 @@ module Gitlab ...@@ -7,21 +7,38 @@ module Gitlab
module ImportSources module ImportSources
extend CurrentSettings extend CurrentSettings
ImportSource = Struct.new(:name, :title, :importer)
ImportTable = [
ImportSource.new('github', 'GitHub', Gitlab::GithubImport::Importer),
ImportSource.new('bitbucket', 'Bitbucket', Gitlab::BitbucketImport::Importer),
ImportSource.new('gitlab', 'GitLab.com', Gitlab::GitlabImport::Importer),
ImportSource.new('google_code', 'Google Code', Gitlab::GoogleCodeImport::Importer),
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::GithubImport::Importer)
].freeze
class << self class << self
def options
@options ||= Hash[ImportTable.map { |importer| [importer.title, importer.name] }]
end
def values def values
options.values @values ||= ImportTable.map(&:name)
end end
def options def importer_names
{ @importer_names ||= ImportTable.select(&:importer).map(&:name)
'GitHub' => 'github', end
'Bitbucket' => 'bitbucket',
'GitLab.com' => 'gitlab', def importer(name)
'Google Code' => 'google_code', ImportTable.find { |import_source| import_source.name == name }.importer
'FogBugz' => 'fogbugz', end
'Repo by URL' => 'git',
'GitLab export' => 'gitlab_project' def title(name)
} options.key(name)
end end
end end
end end
......
require 'spec_helper'
describe Import::GiteaController do
include ImportSpecHelper
let(:provider) { :gitea }
let(:host_url) { 'https://try.gitea.io' }
include_context 'a GitHub-ish import controller'
def assign_host_url
session[:gitea_host_url] = host_url
end
describe "GET new" do
it_behaves_like 'a GitHub-ish import controller: GET new' do
before do
assign_host_url
end
end
end
describe "POST personal_access_token" do
it_behaves_like 'a GitHub-ish import controller: POST personal_access_token'
end
describe "GET status" do
it_behaves_like 'a GitHub-ish import controller: GET status' do
before do
assign_host_url
end
let(:extra_assign_expectations) { { gitea_host_url: host_url } }
end
end
describe 'POST create' do
it_behaves_like 'a GitHub-ish import controller: POST create' do
before do
assign_host_url
end
end
end
end
...@@ -3,34 +3,18 @@ require 'spec_helper' ...@@ -3,34 +3,18 @@ require 'spec_helper'
describe Import::GithubController do describe Import::GithubController do
include ImportSpecHelper include ImportSpecHelper
let(:user) { create(:user) } let(:provider) { :github }
let(:token) { "asdasd12345" }
let(:access_params) { { github_access_token: token } }
def assign_session_token include_context 'a GitHub-ish import controller'
session[:github_access_token] = token
end
before do
sign_in(user)
allow(controller).to receive(:github_import_enabled?).and_return(true)
end
describe "GET new" do describe "GET new" do
it "redirects to GitHub for an access token if logged in with GitHub" do it_behaves_like 'a GitHub-ish import controller: GET new'
allow(controller).to receive(:logged_in_with_github?).and_return(true)
expect(controller).to receive(:go_to_github_for_permissions)
get :new it "redirects to GitHub for an access token if logged in with GitHub" do
end allow(controller).to receive(:logged_in_with_provider?).and_return(true)
expect(controller).to receive(:go_to_provider_for_permissions)
it "redirects to status if we already have a token" do
assign_session_token
allow(controller).to receive(:logged_in_with_github?).and_return(false)
get :new get :new
expect(controller).to redirect_to(status_import_github_url)
end end
end end
...@@ -51,196 +35,14 @@ describe Import::GithubController do ...@@ -51,196 +35,14 @@ describe Import::GithubController do
end end
describe "POST personal_access_token" do describe "POST personal_access_token" do
it "updates access token" do it_behaves_like 'a GitHub-ish import controller: POST personal_access_token'
token = "asdfasdf9876"
allow_any_instance_of(Gitlab::GithubImport::Client).
to receive(:user).and_return(true)
post :personal_access_token, personal_access_token: token
expect(session[:github_access_token]).to eq(token)
expect(controller).to redirect_to(status_import_github_url)
end
end end
describe "GET status" do describe "GET status" do
before do it_behaves_like 'a GitHub-ish import controller: GET status'
@repo = OpenStruct.new(login: 'vim', full_name: 'asd/vim')
@org = OpenStruct.new(login: 'company')
@org_repo = OpenStruct.new(login: 'company', full_name: 'company/repo')
assign_session_token
end
it "assigns variables" do
@project = create(:project, import_type: 'github', creator_id: user.id)
stub_client(repos: [@repo, @org_repo], orgs: [@org], org_repos: [@org_repo])
get :status
expect(assigns(:already_added_projects)).to eq([@project])
expect(assigns(:repos)).to eq([@repo, @org_repo])
end
it "does not show already added project" do
@project = create(:project, import_type: 'github', creator_id: user.id, import_source: 'asd/vim')
stub_client(repos: [@repo], orgs: [])
get :status
expect(assigns(:already_added_projects)).to eq([@project])
expect(assigns(:repos)).to eq([])
end
it "handles an invalid access token" do
allow_any_instance_of(Gitlab::GithubImport::Client).
to receive(:repos).and_raise(Octokit::Unauthorized)
get :status
expect(session[:github_access_token]).to eq(nil)
expect(controller).to redirect_to(new_import_github_url)
expect(flash[:alert]).to eq('Access denied to your GitHub account.')
end
end end
describe "POST create" do describe "POST create" do
let(:github_username) { user.username } it_behaves_like 'a GitHub-ish import controller: POST create'
let(:github_user) { OpenStruct.new(login: github_username) }
let(:github_repo) do
OpenStruct.new(
name: 'vim',
full_name: "#{github_username}/vim",
owner: OpenStruct.new(login: github_username)
)
end
before do
stub_client(user: github_user, repo: github_repo)
assign_session_token
end
context "when the repository owner is the GitHub user" do
context "when the GitHub user and GitLab user's usernames match" do
it "takes the current user's namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(github_repo, github_repo.name, user.namespace, user, access_params).
and_return(double(execute: true))
post :create, format: :js
end
end
context "when the GitHub user and GitLab user's usernames don't match" do
let(:github_username) { "someone_else" }
it "takes the current user's namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(github_repo, github_repo.name, user.namespace, user, access_params).
and_return(double(execute: true))
post :create, format: :js
end
end
end
context "when the repository owner is not the GitHub user" do
let(:other_username) { "someone_else" }
before do
github_repo.owner = OpenStruct.new(login: other_username)
assign_session_token
end
context "when a namespace with the GitHub user's username already exists" do
let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
context "when the namespace is owned by the GitLab user" do
it "takes the existing namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(github_repo, github_repo.name, existing_namespace, user, access_params).
and_return(double(execute: true))
post :create, format: :js
end
end
context "when the namespace is not owned by the GitLab user" do
before do
existing_namespace.owner = create(:user)
existing_namespace.save
end
it "creates a project using user's namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(github_repo, github_repo.name, user.namespace, user, access_params).
and_return(double(execute: true))
post :create, format: :js
end
end
end
context "when a namespace with the GitHub user's username doesn't exist" do
context "when current user can create namespaces" do
it "creates the namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).and_return(double(execute: true))
expect { post :create, target_namespace: github_repo.name, format: :js }.to change(Namespace, :count).by(1)
end
it "takes the new namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(github_repo, github_repo.name, an_instance_of(Group), user, access_params).
and_return(double(execute: true))
post :create, target_namespace: github_repo.name, format: :js
end
end
context "when current user can't create namespaces" do
before do
user.update_attribute(:can_create_group, false)
end
it "doesn't create the namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).and_return(double(execute: true))
expect { post :create, format: :js }.not_to change(Namespace, :count)
end
it "takes the current user's namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(github_repo, github_repo.name, user.namespace, user, access_params).
and_return(double(execute: true))
post :create, format: :js
end
end
end
context 'user has chosen a namespace and name for the project' do
let(:test_namespace) { create(:namespace, name: 'test_namespace', owner: user) }
let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(github_repo, test_name, test_namespace, user, access_params).
and_return(double(execute: true))
post :create, { target_namespace: test_namespace.name, new_name: test_name, format: :js }
end
it 'takes the selected name and default namespace' do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(github_repo, test_name, user.namespace, user, access_params).
and_return(double(execute: true))
post :create, { new_name: test_name, format: :js }
end
end
end
end end
end end
...@@ -25,13 +25,14 @@ describe ImportHelper do ...@@ -25,13 +25,14 @@ describe ImportHelper do
end end
end end
describe '#github_project_link' do describe '#provider_project_link' do
context 'when provider is "github"' do
context 'when provider does not specify a custom URL' do context 'when provider does not specify a custom URL' do
it 'uses default GitHub URL' do it 'uses default GitHub URL' do
allow(Gitlab.config.omniauth).to receive(:providers). allow(Gitlab.config.omniauth).to receive(:providers).
and_return([Settingslogic.new('name' => 'github')]) and_return([Settingslogic.new('name' => 'github')])
expect(helper.github_project_link('octocat/Hello-World')). expect(helper.provider_project_link('github', 'octocat/Hello-World')).
to include('href="https://github.com/octocat/Hello-World"') to include('href="https://github.com/octocat/Hello-World"')
end end
end end
...@@ -41,9 +42,21 @@ describe ImportHelper do ...@@ -41,9 +42,21 @@ describe ImportHelper do
allow(Gitlab.config.omniauth).to receive(:providers). allow(Gitlab.config.omniauth).to receive(:providers).
and_return([Settingslogic.new('name' => 'github', 'url' => 'https://github.company.com')]) and_return([Settingslogic.new('name' => 'github', 'url' => 'https://github.company.com')])
expect(helper.github_project_link('octocat/Hello-World')). expect(helper.provider_project_link('github', 'octocat/Hello-World')).
to include('href="https://github.company.com/octocat/Hello-World"') to include('href="https://github.company.com/octocat/Hello-World"')
end end
end end
end end
context 'when provider is "gitea"' do
before do
assign(:gitea_host_url, 'https://try.gitea.io/')
end
it 'uses given host' do
expect(helper.provider_project_link('gitea', 'octocat/Hello-World')).
to include('href="https://try.gitea.io/octocat/Hello-World"')
end
end
end
end end
...@@ -45,6 +45,7 @@ describe Gitlab::GithubImport::Client, lib: true do ...@@ -45,6 +45,7 @@ describe Gitlab::GithubImport::Client, lib: true do
end end
end end
describe '#api_endpoint' do
context 'when provider does not specity an API endpoint' do context 'when provider does not specity an API endpoint' do
it 'uses GitHub root API endpoint' do it 'uses GitHub root API endpoint' do
expect(client.api.api_endpoint).to eq 'https://api.github.com/' expect(client.api.api_endpoint).to eq 'https://api.github.com/'
...@@ -62,6 +63,31 @@ describe Gitlab::GithubImport::Client, lib: true do ...@@ -62,6 +63,31 @@ describe Gitlab::GithubImport::Client, lib: true do
end end
end end
context 'when given a host' do
subject(:client) { described_class.new(token, host: 'https://try.gitea.io/') }
it 'builds a endpoint with the given host and the default API version' do
expect(client.api.api_endpoint).to eq 'https://try.gitea.io/api/v3/'
end
end
context 'when given an API version' do
subject(:client) { described_class.new(token, api_version: 'v3') }
it 'does not use the API version without a host' do
expect(client.api.api_endpoint).to eq 'https://api.github.com/'
end
end
context 'when given a host and version' do
subject(:client) { described_class.new(token, host: 'https://try.gitea.io/', api_version: 'v3') }
it 'builds a endpoint with the given options' do
expect(client.api.api_endpoint).to eq 'https://try.gitea.io/api/v3/'
end
end
end
it 'does not raise error when rate limit is disabled' do it 'does not raise error when rate limit is disabled' do
stub_request(:get, /api.github.com/) stub_request(:get, /api.github.com/)
allow(client.api).to receive(:rate_limit!).and_raise(Octokit::NotFound) allow(client.api).to receive(:rate_limit!).and_raise(Octokit::NotFound)
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::GithubImport::Importer, lib: true do describe Gitlab::GithubImport::Importer, lib: true do
describe '#execute' do shared_examples 'Gitlab::GithubImport::Importer#execute' do
let(:expected_not_called) { [] }
before do before do
allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new) allow(project).to receive(:import_data).and_return(double.as_null_object)
end
it 'calls import methods' do
importer = described_class.new(project)
expected_called = [
:import_labels, :import_milestones, :import_pull_requests, :import_issues,
:import_wiki, :import_releases, :handle_errors
]
expected_called -= expected_not_called
aggregate_failures do
expected_called.each do |method_name|
expect(importer).to receive(method_name)
end
expect(importer).to receive(:import_comments).with(:issues)
expect(importer).to receive(:import_comments).with(:pull_requests)
expected_not_called.each do |method_name|
expect(importer).not_to receive(method_name)
end
end
importer.execute
end
end end
context 'when an error occurs' do shared_examples 'Gitlab::GithubImport::Importer#execute an error occurs' do
let(:project) { create(:project, import_url: 'https://github.com/octocat/Hello-World.git', wiki_access_level: ProjectFeature::DISABLED) } before do
allow(project).to receive(:import_data).and_return(double.as_null_object)
allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound)
allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error)
allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label1, label2])
allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone])
allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2])
allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request])
allow_any_instance_of(Octokit::Client).to receive(:issues_comments).and_return([])
allow_any_instance_of(Octokit::Client).to receive(:pull_requests_comments).and_return([])
allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil }))
allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2])
end
let(:octocat) { double(id: 123456, login: 'octocat') } let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
let(:repository) { double(id: 1, fork: false) }
let(:source_sha) { create(:commit, project: project).id }
let(:source_branch) { double(ref: 'feature', repo: repository, sha: source_sha) }
let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) }
let(:label1) do let(:label1) do
double( double(
name: 'Bug', name: 'Bug',
color: 'ff0000', color: 'ff0000',
url: 'https://api.github.com/repos/octocat/Hello-World/labels/bug' url: "#{api_root}/repos/octocat/Hello-World/labels/bug"
) )
end end
...@@ -29,12 +68,13 @@ describe Gitlab::GithubImport::Importer, lib: true do ...@@ -29,12 +68,13 @@ describe Gitlab::GithubImport::Importer, lib: true do
double( double(
name: nil, name: nil,
color: 'ff0000', color: 'ff0000',
url: 'https://api.github.com/repos/octocat/Hello-World/labels/bug' url: "#{api_root}/repos/octocat/Hello-World/labels/bug"
) )
end end
let(:milestone) do let(:milestone) do
double( double(
id: 1347, # For Gitea
number: 1347, number: 1347,
state: 'open', state: 'open',
title: '1.0', title: '1.0',
...@@ -43,7 +83,7 @@ describe Gitlab::GithubImport::Importer, lib: true do ...@@ -43,7 +83,7 @@ describe Gitlab::GithubImport::Importer, lib: true do
created_at: created_at, created_at: created_at,
updated_at: updated_at, updated_at: updated_at,
closed_at: nil, closed_at: nil,
url: 'https://api.github.com/repos/octocat/Hello-World/milestones/1' url: "#{api_root}/repos/octocat/Hello-World/milestones/1"
) )
end end
...@@ -61,8 +101,8 @@ describe Gitlab::GithubImport::Importer, lib: true do ...@@ -61,8 +101,8 @@ describe Gitlab::GithubImport::Importer, lib: true do
created_at: created_at, created_at: created_at,
updated_at: updated_at, updated_at: updated_at,
closed_at: nil, closed_at: nil,
url: 'https://api.github.com/repos/octocat/Hello-World/issues/1347', url: "#{api_root}/repos/octocat/Hello-World/issues/1347",
labels: [double(name: 'Label #1')], labels: [double(name: 'Label #1')]
) )
end end
...@@ -80,11 +120,16 @@ describe Gitlab::GithubImport::Importer, lib: true do ...@@ -80,11 +120,16 @@ describe Gitlab::GithubImport::Importer, lib: true do
created_at: created_at, created_at: created_at,
updated_at: updated_at, updated_at: updated_at,
closed_at: nil, closed_at: nil,
url: 'https://api.github.com/repos/octocat/Hello-World/issues/1348', url: "#{api_root}/repos/octocat/Hello-World/issues/1348",
labels: [double(name: 'Label #2')], labels: [double(name: 'Label #2')]
) )
end end
let(:repository) { double(id: 1, fork: false) }
let(:source_sha) { create(:commit, project: project).id }
let(:source_branch) { double(ref: 'feature', repo: repository, sha: source_sha) }
let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) }
let(:pull_request) do let(:pull_request) do
double( double(
number: 1347, number: 1347,
...@@ -100,7 +145,8 @@ describe Gitlab::GithubImport::Importer, lib: true do ...@@ -100,7 +145,8 @@ describe Gitlab::GithubImport::Importer, lib: true do
updated_at: updated_at, updated_at: updated_at,
closed_at: nil, closed_at: nil,
merged_at: nil, merged_at: nil,
url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347', url: "#{api_root}/repos/octocat/Hello-World/pulls/1347",
labels: [double(name: 'Label #2')]
) )
end end
...@@ -112,7 +158,7 @@ describe Gitlab::GithubImport::Importer, lib: true do ...@@ -112,7 +158,7 @@ describe Gitlab::GithubImport::Importer, lib: true do
draft: false, draft: false,
created_at: created_at, created_at: created_at,
updated_at: updated_at, updated_at: updated_at,
url: 'https://api.github.com/repos/octocat/Hello-World/releases/1' url: "#{api_root}/repos/octocat/Hello-World/releases/1"
) )
end end
...@@ -124,24 +170,10 @@ describe Gitlab::GithubImport::Importer, lib: true do ...@@ -124,24 +170,10 @@ describe Gitlab::GithubImport::Importer, lib: true do
draft: false, draft: false,
created_at: created_at, created_at: created_at,
updated_at: updated_at, updated_at: updated_at,
url: 'https://api.github.com/repos/octocat/Hello-World/releases/2' url: "#{api_root}/repos/octocat/Hello-World/releases/2"
) )
end end
before do
allow(project).to receive(:import_data).and_return(double.as_null_object)
allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound)
allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label1, label2])
allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone])
allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2])
allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request])
allow_any_instance_of(Octokit::Client).to receive(:issues_comments).and_return([])
allow_any_instance_of(Octokit::Client).to receive(:pull_requests_comments).and_return([])
allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil }))
allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2])
allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error)
end
it 'returns true' do it 'returns true' do
expect(described_class.new(project).execute).to eq true expect(described_class.new(project).execute).to eq true
end end
...@@ -154,17 +186,67 @@ describe Gitlab::GithubImport::Importer, lib: true do ...@@ -154,17 +186,67 @@ describe Gitlab::GithubImport::Importer, lib: true do
error = { error = {
message: 'The remote data could not be fully imported.', message: 'The remote data could not be fully imported.',
errors: [ errors: [
{ type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" }, { type: :label, url: "#{api_root}/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" },
{ type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank" }, { type: :issue, url: "#{api_root}/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank" },
{ type: :wiki, errors: "Gitlab::Shell::Error" }, { type: :wiki, errors: "Gitlab::Shell::Error" }
{ type: :release, url: 'https://api.github.com/repos/octocat/Hello-World/releases/2', errors: "Validation failed: Description can't be blank" }
] ]
} }
unless project.gitea_import?
error[:errors] << { type: :release, url: "#{api_root}/repos/octocat/Hello-World/releases/2", errors: "Validation failed: Description can't be blank" }
end
described_class.new(project).execute described_class.new(project).execute
expect(project.import_error).to eq error.to_json expect(project.import_error).to eq error.to_json
end end
end end
let(:project) { create(:project, import_url: "#{repo_root}/octocat/Hello-World.git", wiki_access_level: ProjectFeature::DISABLED) }
let(:credentials) { { user: 'joe' } }
context 'when importing a GitHub project' do
let(:api_root) { 'https://api.github.com' }
let(:repo_root) { 'https://github.com' }
it_behaves_like 'Gitlab::GithubImport::Importer#execute'
it_behaves_like 'Gitlab::GithubImport::Importer#execute an error occurs'
describe '#client' do
it 'instantiates a Client' do
allow(project).to receive(:import_data).and_return(double(credentials: credentials))
expect(Gitlab::GithubImport::Client).to receive(:new).with(
credentials[:user],
{}
)
described_class.new(project).client
end
end
end
context 'when importing a Gitea project' do
let(:api_root) { 'https://try.gitea.io/api/v1' }
let(:repo_root) { 'https://try.gitea.io' }
before do
project.update(import_type: 'gitea', import_url: "#{repo_root}/foo/group/project.git")
end
it_behaves_like 'Gitlab::GithubImport::Importer#execute' do
let(:expected_not_called) { [:import_releases] }
end
it_behaves_like 'Gitlab::GithubImport::Importer#execute an error occurs'
describe '#client' do
it 'instantiates a Client' do
allow(project).to receive(:import_data).and_return(double(credentials: credentials))
expect(Gitlab::GithubImport::Client).to receive(:new).with(
credentials[:user],
{ host: "#{repo_root}:443/foo", api_version: 'v1' }
)
described_class.new(project).client
end
end
end end
end end
require 'spec_helper'
describe Gitlab::GithubImport::IssuableFormatter, lib: true do
let(:raw_data) do
double(number: 42)
end
let(:project) { double(import_type: 'github') }
let(:issuable_formatter) { described_class.new(project, raw_data) }
describe '#project_association' do
it { expect { issuable_formatter.project_association }.to raise_error(NotImplementedError) }
end
describe '#number' do
it { expect(issuable_formatter.number).to eq(42) }
end
describe '#find_condition' do
it { expect(issuable_formatter.find_condition).to eq({ iid: 42 }) }
end
end
...@@ -23,9 +23,9 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -23,9 +23,9 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
} }
end end
subject(:issue) { described_class.new(project, raw_data)} subject(:issue) { described_class.new(project, raw_data) }
describe '#attributes' do shared_examples 'Gitlab::GithubImport::IssueFormatter#attributes' do
context 'when issue is open' do context 'when issue is open' do
let(:raw_data) { double(base_data.merge(state: 'open')) } let(:raw_data) { double(base_data.merge(state: 'open')) }
...@@ -83,7 +83,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -83,7 +83,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end end
context 'when it has a milestone' do context 'when it has a milestone' do
let(:milestone) { double(number: 45) } let(:milestone) { double(id: 42, number: 42) }
let(:raw_data) { double(base_data.merge(milestone: milestone)) } let(:raw_data) { double(base_data.merge(milestone: milestone)) }
it 'returns nil when milestone does not exist' do it 'returns nil when milestone does not exist' do
...@@ -91,7 +91,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -91,7 +91,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end end
it 'returns milestone when it exists' do it 'returns milestone when it exists' do
milestone = create(:milestone, project: project, iid: 45) milestone = create(:milestone, project: project, iid: 42)
expect(issue.attributes.fetch(:milestone)).to eq milestone expect(issue.attributes.fetch(:milestone)).to eq milestone
end end
...@@ -118,6 +118,28 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -118,6 +118,28 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end end
end end
shared_examples 'Gitlab::GithubImport::IssueFormatter#number' do
let(:raw_data) { double(base_data.merge(number: 1347)) }
it 'returns issue number' do
expect(issue.number).to eq 1347
end
end
context 'when importing a GitHub project' do
it_behaves_like 'Gitlab::GithubImport::IssueFormatter#attributes'
it_behaves_like 'Gitlab::GithubImport::IssueFormatter#number'
end
context 'when importing a Gitea project' do
before do
project.update(import_type: 'gitea')
end
it_behaves_like 'Gitlab::GithubImport::IssueFormatter#attributes'
it_behaves_like 'Gitlab::GithubImport::IssueFormatter#number'
end
describe '#has_comments?' do describe '#has_comments?' do
context 'when number of comments is greater than zero' do context 'when number of comments is greater than zero' do
let(:raw_data) { double(base_data.merge(comments: 1)) } let(:raw_data) { double(base_data.merge(comments: 1)) }
...@@ -136,14 +158,6 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do ...@@ -136,14 +158,6 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end end
end end
describe '#number' do
let(:raw_data) { double(base_data.merge(number: 1347)) }
it 'returns pull request number' do
expect(issue.number).to eq 1347
end
end
describe '#pull_request?' do describe '#pull_request?' do
context 'when mention a pull request' do context 'when mention a pull request' do
let(:raw_data) { double(base_data.merge(pull_request: double)) } let(:raw_data) { double(base_data.merge(pull_request: double)) }
......
...@@ -6,7 +6,6 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do ...@@ -6,7 +6,6 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
let(:base_data) do let(:base_data) do
{ {
number: 1347,
state: 'open', state: 'open',
title: '1.0', title: '1.0',
description: 'Version 1.0', description: 'Version 1.0',
...@@ -16,12 +15,15 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do ...@@ -16,12 +15,15 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
closed_at: nil closed_at: nil
} }
end end
let(:iid_attr) { :number }
subject(:formatter) { described_class.new(project, raw_data)} subject(:formatter) { described_class.new(project, raw_data) }
shared_examples 'Gitlab::GithubImport::MilestoneFormatter#attributes' do
let(:data) { base_data.merge(iid_attr => 1347) }
describe '#attributes' do
context 'when milestone is open' do context 'when milestone is open' do
let(:raw_data) { double(base_data.merge(state: 'open')) } let(:raw_data) { double(data.merge(state: 'open')) }
it 'returns formatted attributes' do it 'returns formatted attributes' do
expected = { expected = {
...@@ -40,7 +42,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do ...@@ -40,7 +42,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
end end
context 'when milestone is closed' do context 'when milestone is closed' do
let(:raw_data) { double(base_data.merge(state: 'closed')) } let(:raw_data) { double(data.merge(state: 'closed')) }
it 'returns formatted attributes' do it 'returns formatted attributes' do
expected = { expected = {
...@@ -60,7 +62,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do ...@@ -60,7 +62,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
context 'when milestone has a due date' do context 'when milestone has a due date' do
let(:due_date) { DateTime.strptime('2011-01-28T19:01:12Z') } let(:due_date) { DateTime.strptime('2011-01-28T19:01:12Z') }
let(:raw_data) { double(base_data.merge(due_on: due_date)) } let(:raw_data) { double(data.merge(due_on: due_date)) }
it 'returns formatted attributes' do it 'returns formatted attributes' do
expected = { expected = {
...@@ -78,4 +80,17 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do ...@@ -78,4 +80,17 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
end end
end end
end end
context 'when importing a GitHub project' do
it_behaves_like 'Gitlab::GithubImport::MilestoneFormatter#attributes'
end
context 'when importing a Gitea project' do
let(:iid_attr) { :id }
before do
project.update(import_type: 'gitea')
end
it_behaves_like 'Gitlab::GithubImport::MilestoneFormatter#attributes'
end
end end
...@@ -32,9 +32,9 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -32,9 +32,9 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
} }
end end
subject(:pull_request) { described_class.new(project, raw_data)} subject(:pull_request) { described_class.new(project, raw_data) }
describe '#attributes' do shared_examples 'Gitlab::GithubImport::PullRequestFormatter#attributes' do
context 'when pull request is open' do context 'when pull request is open' do
let(:raw_data) { double(base_data.merge(state: 'open')) } let(:raw_data) { double(base_data.merge(state: 'open')) }
...@@ -149,7 +149,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -149,7 +149,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
context 'when it has a milestone' do context 'when it has a milestone' do
let(:milestone) { double(number: 45) } let(:milestone) { double(id: 42, number: 42) }
let(:raw_data) { double(base_data.merge(milestone: milestone)) } let(:raw_data) { double(base_data.merge(milestone: milestone)) }
it 'returns nil when milestone does not exist' do it 'returns nil when milestone does not exist' do
...@@ -157,22 +157,22 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -157,22 +157,22 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
it 'returns milestone when it exists' do it 'returns milestone when it exists' do
milestone = create(:milestone, project: project, iid: 45) milestone = create(:milestone, project: project, iid: 42)
expect(pull_request.attributes.fetch(:milestone)).to eq milestone expect(pull_request.attributes.fetch(:milestone)).to eq milestone
end end
end end
end end
describe '#number' do shared_examples 'Gitlab::GithubImport::PullRequestFormatter#number' do
let(:raw_data) { double(base_data.merge(number: 1347)) } let(:raw_data) { double(base_data) }
it 'returns pull request number' do it 'returns pull request number' do
expect(pull_request.number).to eq 1347 expect(pull_request.number).to eq 1347
end end
end end
describe '#source_branch_name' do shared_examples 'Gitlab::GithubImport::PullRequestFormatter#source_branch_name' do
context 'when source branch exists' do context 'when source branch exists' do
let(:raw_data) { double(base_data) } let(:raw_data) { double(base_data) }
...@@ -190,7 +190,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -190,7 +190,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
end end
describe '#target_branch_name' do shared_examples 'Gitlab::GithubImport::PullRequestFormatter#target_branch_name' do
context 'when source branch exists' do context 'when source branch exists' do
let(:raw_data) { double(base_data) } let(:raw_data) { double(base_data) }
...@@ -208,6 +208,24 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -208,6 +208,24 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
end end
context 'when importing a GitHub project' do
it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#attributes'
it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#number'
it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#source_branch_name'
it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#target_branch_name'
end
context 'when importing a Gitea project' do
before do
project.update(import_type: 'gitea')
end
it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#attributes'
it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#number'
it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#source_branch_name'
it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#target_branch_name'
end
describe '#valid?' do describe '#valid?' do
context 'when source, and target repos are not a fork' do context 'when source, and target repos are not a fork' do
let(:raw_data) { double(base_data) } let(:raw_data) { double(base_data) }
......
require 'spec_helper'
describe Gitlab::ImportSources do
describe '.options' do
it 'returns a hash' do
expected =
{
'GitHub' => 'github',
'Bitbucket' => 'bitbucket',
'GitLab.com' => 'gitlab',
'Google Code' => 'google_code',
'FogBugz' => 'fogbugz',
'Repo by URL' => 'git',
'GitLab export' => 'gitlab_project',
'Gitea' => 'gitea'
}
expect(described_class.options).to eq(expected)
end
end
describe '.values' do
it 'returns an array' do
expected =
[
'github',
'bitbucket',
'gitlab',
'google_code',
'fogbugz',
'git',
'gitlab_project',
'gitea'
]
expect(described_class.values).to eq(expected)
end
end
describe '.importer_names' do
it 'returns an array of importer names' do
expected =
[
'github',
'bitbucket',
'gitlab',
'google_code',
'fogbugz',
'gitlab_project',
'gitea'
]
expect(described_class.importer_names).to eq(expected)
end
end
describe '.importer' do
import_sources = {
'github' => Gitlab::GithubImport::Importer,
'bitbucket' => Gitlab::BitbucketImport::Importer,
'gitlab' => Gitlab::GitlabImport::Importer,
'google_code' => Gitlab::GoogleCodeImport::Importer,
'fogbugz' => Gitlab::FogbugzImport::Importer,
'git' => nil,
'gitlab_project' => Gitlab::ImportExport::Importer,
'gitea' => Gitlab::GithubImport::Importer
}
import_sources.each do |name, klass|
it "returns #{klass} when given #{name}" do
expect(described_class.importer(name)).to eq(klass)
end
end
end
describe '.title' do
import_sources = {
'github' => 'GitHub',
'bitbucket' => 'Bitbucket',
'gitlab' => 'GitLab.com',
'google_code' => 'Google Code',
'fogbugz' => 'FogBugz',
'git' => 'Repo by URL',
'gitlab_project' => 'GitLab export',
'gitea' => 'Gitea'
}
import_sources.each do |name, title|
it "returns #{title} when given #{name}" do
expect(described_class.title(name)).to eq(title)
end
end
end
end
...@@ -1458,6 +1458,18 @@ describe Project, models: true do ...@@ -1458,6 +1458,18 @@ describe Project, models: true do
end end
end end
describe '#gitlab_project_import?' do
subject(:project) { build(:project, import_type: 'gitlab_project') }
it { expect(project.gitlab_project_import?).to be true }
end
describe '#gitea_import?' do
subject(:project) { build(:project, import_type: 'gitea') }
it { expect(project.gitea_import?).to be true }
end
describe '#lfs_enabled?' do describe '#lfs_enabled?' do
let(:project) { create(:project) } let(:project) { create(:project) }
......
require 'spec_helper'
# Shared examples for a resource inside a Project
#
# By default it tests all the default REST actions: index, create, new, edit,
# show, update, and destroy. You can remove actions by customizing the
# `actions` variable.
#
# It also expects a `controller` variable to be available which defines both
# the path to the resource as well as the controller name.
#
# Examples
#
# # Default behavior
# it_behaves_like 'RESTful project resources' do
# let(:controller) { 'issues' }
# end
#
# # Customizing actions
# it_behaves_like 'RESTful project resources' do
# let(:actions) { [:index] }
# let(:controller) { 'issues' }
# end
shared_examples 'importer routing' do
let(:except_actions) { [] }
it 'to #create' do
expect(post("/import/#{provider}")).to route_to("import/#{provider}#create") unless except_actions.include?(:create)
end
it 'to #new' do
expect(get("/import/#{provider}/new")).to route_to("import/#{provider}#new") unless except_actions.include?(:new)
end
it 'to #status' do
expect(get("/import/#{provider}/status")).to route_to("import/#{provider}#status") unless except_actions.include?(:status)
end
it 'to #callback' do
expect(get("/import/#{provider}/callback")).to route_to("import/#{provider}#callback") unless except_actions.include?(:callback)
end
it 'to #jobs' do
expect(get("/import/#{provider}/jobs")).to route_to("import/#{provider}#jobs") unless except_actions.include?(:jobs)
end
end
# personal_access_token_import_github POST /import/github/personal_access_token(.:format) import/github#personal_access_token
# status_import_github GET /import/github/status(.:format) import/github#status
# callback_import_github GET /import/github/callback(.:format) import/github#callback
# jobs_import_github GET /import/github/jobs(.:format) import/github#jobs
# import_github POST /import/github(.:format) import/github#create
# new_import_github GET /import/github/new(.:format) import/github#new
describe Import::GithubController, 'routing' do
it_behaves_like 'importer routing' do
let(:provider) { 'github' }
end
it 'to #personal_access_token' do
expect(post('/import/github/personal_access_token')).to route_to('import/github#personal_access_token')
end
end
# personal_access_token_import_gitea POST /import/gitea/personal_access_token(.:format) import/gitea#personal_access_token
# status_import_gitea GET /import/gitea/status(.:format) import/gitea#status
# jobs_import_gitea GET /import/gitea/jobs(.:format) import/gitea#jobs
# import_gitea POST /import/gitea(.:format) import/gitea#create
# new_import_gitea GET /import/gitea/new(.:format) import/gitea#new
describe Import::GiteaController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:callback] }
let(:provider) { 'gitea' }
end
it 'to #personal_access_token' do
expect(post('/import/gitea/personal_access_token')).to route_to('import/gitea#personal_access_token')
end
end
# status_import_gitlab GET /import/gitlab/status(.:format) import/gitlab#status
# callback_import_gitlab GET /import/gitlab/callback(.:format) import/gitlab#callback
# jobs_import_gitlab GET /import/gitlab/jobs(.:format) import/gitlab#jobs
# import_gitlab POST /import/gitlab(.:format) import/gitlab#create
describe Import::GitlabController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:new] }
let(:provider) { 'gitlab' }
end
end
# status_import_bitbucket GET /import/bitbucket/status(.:format) import/bitbucket#status
# callback_import_bitbucket GET /import/bitbucket/callback(.:format) import/bitbucket#callback
# jobs_import_bitbucket GET /import/bitbucket/jobs(.:format) import/bitbucket#jobs
# import_bitbucket POST /import/bitbucket(.:format) import/bitbucket#create
describe Import::BitbucketController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:new] }
let(:provider) { 'bitbucket' }
end
end
# status_import_google_code GET /import/google_code/status(.:format) import/google_code#status
# callback_import_google_code POST /import/google_code/callback(.:format) import/google_code#callback
# jobs_import_google_code GET /import/google_code/jobs(.:format) import/google_code#jobs
# new_user_map_import_google_code GET /import/google_code/user_map(.:format) import/google_code#new_user_map
# create_user_map_import_google_code POST /import/google_code/user_map(.:format) import/google_code#create_user_map
# import_google_code POST /import/google_code(.:format) import/google_code#create
# new_import_google_code GET /import/google_code/new(.:format) import/google_code#new
describe Import::GoogleCodeController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:callback] }
let(:provider) { 'google_code' }
end
it 'to #callback' do
expect(post("/import/google_code/callback")).to route_to("import/google_code#callback")
end
it 'to #new_user_map' do
expect(get('/import/google_code/user_map')).to route_to('import/google_code#new_user_map')
end
it 'to #create_user_map' do
expect(post('/import/google_code/user_map')).to route_to('import/google_code#create_user_map')
end
end
# status_import_fogbugz GET /import/fogbugz/status(.:format) import/fogbugz#status
# callback_import_fogbugz POST /import/fogbugz/callback(.:format) import/fogbugz#callback
# jobs_import_fogbugz GET /import/fogbugz/jobs(.:format) import/fogbugz#jobs
# new_user_map_import_fogbugz GET /import/fogbugz/user_map(.:format) import/fogbugz#new_user_map
# create_user_map_import_fogbugz POST /import/fogbugz/user_map(.:format) import/fogbugz#create_user_map
# import_fogbugz POST /import/fogbugz(.:format) import/fogbugz#create
# new_import_fogbugz GET /import/fogbugz/new(.:format) import/fogbugz#new
describe Import::FogbugzController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:callback] }
let(:provider) { 'fogbugz' }
end
it 'to #callback' do
expect(post("/import/fogbugz/callback")).to route_to("import/fogbugz#callback")
end
it 'to #new_user_map' do
expect(get('/import/fogbugz/user_map')).to route_to('import/fogbugz#new_user_map')
end
it 'to #create_user_map' do
expect(post('/import/fogbugz/user_map')).to route_to('import/fogbugz#create_user_map')
end
end
# import_gitlab_project POST /import/gitlab_project(.:format) import/gitlab_projects#create
# POST /import/gitlab_project(.:format) import/gitlab_projects#create
# new_import_gitlab_project GET /import/gitlab_project/new(.:format) import/gitlab_projects#new
describe Import::GitlabProjectsController, 'routing' do
it 'to #create' do
expect(post('/import/gitlab_project')).to route_to('import/gitlab_projects#create')
end
it 'to #new' do
expect(get('/import/gitlab_project/new')).to route_to('import/gitlab_projects#new')
end
end
shared_context 'a GitHub-ish import controller' do
let(:user) { create(:user) }
let(:token) { "asdasd12345" }
let(:access_params) { { github_access_token: token } }
before do
sign_in(user)
allow(controller).to receive(:"#{provider}_import_enabled?").and_return(true)
end
end
# Specifications for behavior common to all objects with an email attribute.
# Takes a list of email-format attributes and requires:
# - subject { "the object with a attribute= setter" }
# Note: You have access to `email_value` which is the email address value
# being currently tested).
def assign_session_token(provider)
session[:"#{provider}_access_token"] = 'asdasd12345'
end
shared_examples 'a GitHub-ish import controller: POST personal_access_token' do
let(:status_import_url) { public_send("status_import_#{provider}_url") }
it "updates access token" do
token = 'asdfasdf9876'
allow_any_instance_of(Gitlab::GithubImport::Client).
to receive(:user).and_return(true)
post :personal_access_token, personal_access_token: token
expect(session[:"#{provider}_access_token"]).to eq(token)
expect(controller).to redirect_to(status_import_url)
end
end
shared_examples 'a GitHub-ish import controller: GET new' do
let(:status_import_url) { public_send("status_import_#{provider}_url") }
it "redirects to status if we already have a token" do
assign_session_token(provider)
allow(controller).to receive(:logged_in_with_provider?).and_return(false)
get :new
expect(controller).to redirect_to(status_import_url)
end
it "renders the :new page if no token is present in session" do
get :new
expect(response).to render_template(:new)
end
end
shared_examples 'a GitHub-ish import controller: GET status' do
let(:new_import_url) { public_send("new_import_#{provider}_url") }
let(:user) { create(:user) }
let(:repo) { OpenStruct.new(login: 'vim', full_name: 'asd/vim') }
let(:org) { OpenStruct.new(login: 'company') }
let(:org_repo) { OpenStruct.new(login: 'company', full_name: 'company/repo') }
let(:extra_assign_expectations) { {} }
before do
assign_session_token(provider)
end
it "assigns variables" do
project = create(:empty_project, import_type: provider, creator_id: user.id)
stub_client(repos: [repo, org_repo], orgs: [org], org_repos: [org_repo])
get :status
expect(assigns(:already_added_projects)).to eq([project])
expect(assigns(:repos)).to eq([repo, org_repo])
extra_assign_expectations.each do |key, value|
expect(assigns(key)).to eq(value)
end
end
it "does not show already added project" do
project = create(:empty_project, import_type: provider, creator_id: user.id, import_source: 'asd/vim')
stub_client(repos: [repo], orgs: [])
get :status
expect(assigns(:already_added_projects)).to eq([project])
expect(assigns(:repos)).to eq([])
end
it "handles an invalid access token" do
allow_any_instance_of(Gitlab::GithubImport::Client).
to receive(:repos).and_raise(Octokit::Unauthorized)
get :status
expect(session[:"#{provider}_access_token"]).to be_nil
expect(controller).to redirect_to(new_import_url)
expect(flash[:alert]).to eq("Access denied to your #{Gitlab::ImportSources.title(provider.to_s)} account.")
end
end
shared_examples 'a GitHub-ish import controller: POST create' do
let(:user) { create(:user) }
let(:provider_username) { user.username }
let(:provider_user) { OpenStruct.new(login: provider_username) }
let(:provider_repo) do
OpenStruct.new(
name: 'vim',
full_name: "#{provider_username}/vim",
owner: OpenStruct.new(login: provider_username)
)
end
before do
stub_client(user: provider_user, repo: provider_repo)
assign_session_token(provider)
end
context "when the repository owner is the provider user" do
context "when the provider user and GitLab user's usernames match" do
it "takes the current user's namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider).
and_return(double(execute: true))
post :create, format: :js
end
end
context "when the provider user and GitLab user's usernames don't match" do
let(:provider_username) { "someone_else" }
it "takes the current user's namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider).
and_return(double(execute: true))
post :create, format: :js
end
end
end
context "when the repository owner is not the provider user" do
let(:other_username) { "someone_else" }
before do
provider_repo.owner = OpenStruct.new(login: other_username)
assign_session_token(provider)
end
context "when a namespace with the provider user's username already exists" do
let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
context "when the namespace is owned by the GitLab user" do
it "takes the existing namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, provider_repo.name, existing_namespace, user, access_params, type: provider).
and_return(double(execute: true))
post :create, format: :js
end
end
context "when the namespace is not owned by the GitLab user" do
before do
existing_namespace.owner = create(:user)
existing_namespace.save
end
it "creates a project using user's namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider).
and_return(double(execute: true))
post :create, format: :js
end
end
end
context "when a namespace with the provider user's username doesn't exist" do
context "when current user can create namespaces" do
it "creates the namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).and_return(double(execute: true))
expect { post :create, target_namespace: provider_repo.name, format: :js }.to change(Namespace, :count).by(1)
end
it "takes the new namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, provider_repo.name, an_instance_of(Group), user, access_params, type: provider).
and_return(double(execute: true))
post :create, target_namespace: provider_repo.name, format: :js
end
end
context "when current user can't create namespaces" do
before do
user.update_attribute(:can_create_group, false)
end
it "doesn't create the namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).and_return(double(execute: true))
expect { post :create, format: :js }.not_to change(Namespace, :count)
end
it "takes the current user's namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider).
and_return(double(execute: true))
post :create, format: :js
end
end
end
context 'user has chosen a namespace and name for the project' do
let(:test_namespace) { create(:namespace, name: 'test_namespace', owner: user) }
let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider).
and_return(double(execute: true))
post :create, { target_namespace: test_namespace.name, new_name: test_name, format: :js }
end
it 'takes the selected name and default namespace' do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider).
and_return(double(execute: true))
post :create, { new_name: test_name, format: :js }
end
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