From 103114e3d73819f76bed9d8ad1bbdb8964875579 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9my=20Coutable?= <remy@rymai.me>
Date: Thu, 15 Dec 2016 17:31:14 +0100
Subject: [PATCH] Rename Gogs to Gitea, DRY the controller and improve views
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Rémy Coutable <remy@rymai.me>
---
 app/assets/images/gogs-logo.svg               |   1 -
 app/controllers/application_controller.rb     |   6 +-
 app/controllers/import/gitea_controller.rb    |  41 ++++
 app/controllers/import/github_controller.rb   |  91 ++++---
 app/controllers/import/gogs_controller.rb     |  76 ------
 app/helpers/import_helper.rb                  |   8 +-
 app/services/projects/import_service.rb       |  27 ++-
 app/views/import/gitea/new.html.haml          |  23 ++
 .../import/{gogs => gitea}/status.html.haml   |  14 +-
 app/views/import/gogs/new.html.haml           |  17 --
 app/views/projects/new.html.haml              |  10 +-
 app/views/shared/icons/_go_logo.svg.erb       |   1 +
 .../unreleased/22348-gitea-importer.yml       |   4 +
 changelogs/unreleased/22348-gogs-importer.yml |   4 -
 config/initializers/1_settings.rb             |   2 +-
 config/routes/import.rb                       |   2 +-
 lib/gitlab/current_settings.rb                |   2 +-
 lib/gitlab/gogs_import/importer.rb            |  54 -----
 lib/gitlab/gogs_import/milestone_formatter.rb |   9 -
 lib/gitlab/import_sources.rb                  |   4 +-
 .../import/gitea_controller_spec.rb           |  43 ++++
 .../import/github_controller_spec.rb          | 218 +----------------
 spec/routing/import_routing_spec.rb           | 166 +++++++++++++
 ...hubish_import_controller_shared_context.rb |  10 +
 ...ubish_import_controller_shared_examples.rb | 228 ++++++++++++++++++
 spec/support/import_spec_helper.rb            |   4 +
 26 files changed, 632 insertions(+), 433 deletions(-)
 delete mode 100644 app/assets/images/gogs-logo.svg
 create mode 100644 app/controllers/import/gitea_controller.rb
 delete mode 100644 app/controllers/import/gogs_controller.rb
 create mode 100644 app/views/import/gitea/new.html.haml
 rename app/views/import/{gogs => gitea}/status.html.haml (86%)
 delete mode 100644 app/views/import/gogs/new.html.haml
 create mode 100644 app/views/shared/icons/_go_logo.svg.erb
 create mode 100644 changelogs/unreleased/22348-gitea-importer.yml
 delete mode 100644 changelogs/unreleased/22348-gogs-importer.yml
 delete mode 100644 lib/gitlab/gogs_import/importer.rb
 delete mode 100644 lib/gitlab/gogs_import/milestone_formatter.rb
 create mode 100644 spec/controllers/import/gitea_controller_spec.rb
 create mode 100644 spec/routing/import_routing_spec.rb
 create mode 100644 spec/support/githubish_import_controller_shared_context.rb
 create mode 100644 spec/support/githubish_import_controller_shared_examples.rb

diff --git a/app/assets/images/gogs-logo.svg b/app/assets/images/gogs-logo.svg
deleted file mode 100644
index 60a18263033..00000000000
--- a/app/assets/images/gogs-logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" 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>
\ No newline at end of file
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 3ac4975f815..bb47e2a8bf7 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -25,7 +25,7 @@ class ApplicationController < ActionController::Base
   protect_from_forgery with: :exception
 
   helper_method :can?, :current_application_settings
-  helper_method :import_sources_enabled?, :github_import_enabled?, :gogs_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|
     log_exception(exception)
@@ -245,8 +245,8 @@ class ApplicationController < ActionController::Base
     current_application_settings.import_sources.include?('github')
   end
 
-  def gogs_import_enabled?
-    current_application_settings.import_sources.include?('gogs')
+  def gitea_import_enabled?
+    current_application_settings.import_sources.include?('gitea')
   end
 
   def github_import_configured?
diff --git a/app/controllers/import/gitea_controller.rb b/app/controllers/import/gitea_controller.rb
new file mode 100644
index 00000000000..c82a20be04c
--- /dev/null
+++ b/app/controllers/import/gitea_controller.rb
@@ -0,0 +1,41 @@
+class Import::GiteaController < Import::GithubController
+  def new
+    if session[:access_token].present? && session[:host_url].present?
+      redirect_to status_import_url
+    end
+  end
+
+  def personal_access_token
+    session[:host_url] = params[:gitea_host_url]
+    super
+  end
+
+  def status
+    @gitea_root_url = session[:host_url]
+    super
+  end
+
+  private
+
+  # 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].blank? || session[:host_url].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_url], api_version: 'v1' }
+  end
+end
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index ee7d498c59c..343ca51e510 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -1,39 +1,37 @@
 class Import::GithubController < Import::BaseController
-  before_action :verify_github_import_enabled
-  before_action :github_auth, only: [:status, :jobs, :create]
+  before_action :verify_import_enabled
+  before_action :provider_auth, only: [:status, :jobs, :create]
 
-  rescue_from Octokit::Unauthorized, with: :github_unauthorized
-
-  helper_method :logged_in_with_github?
+  rescue_from Octokit::Unauthorized, with: :provider_unauthorized
 
   def new
-    if logged_in_with_github?
-      go_to_github_for_permissions
-    elsif session[:github_access_token]
-      redirect_to status_import_github_url
+    if logged_in_with_provider?
+      go_to_provider_for_permissions
+    elsif session[:access_token]
+      redirect_to status_import_url
     end
   end
 
   def callback
-    session[:github_access_token] = client.get_token(params[:code])
-    redirect_to status_import_github_url
+    session[:access_token] = client.get_token(params[:code])
+    redirect_to status_import_url
   end
 
   def personal_access_token
-    session[:github_access_token] = params[:personal_access_token]
-    redirect_to status_import_github_url
+    session[:access_token] = params[:personal_access_token]
+    redirect_to status_import_url
   end
 
   def status
     @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)
 
-    @repos.reject!{ |repo| already_added_projects_names.include? repo.full_name }
+    @repos.reject! { |repo| already_added_projects_names.include? repo.full_name }
   end
 
   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
   end
 
@@ -44,8 +42,8 @@ class Import::GithubController < Import::BaseController
     namespace_path = params[:target_namespace].presence || current_user.namespace_path
     @target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path)
 
-    if current_user.can?(:create_projects, @target_namespace)
-      @project = Gitlab::GithubImport::ProjectCreator.new(repo, @project_name, @target_namespace, current_user, access_params).execute
+    if can?(current_user, :create_projects, @target_namespace)
+      @project = Gitlab::GithubImport::ProjectCreator.new(repo, @project_name, @target_namespace, current_user, access_params, type: provider).execute
     else
       render 'unauthorized'
     end
@@ -54,34 +52,59 @@ class Import::GithubController < Import::BaseController
   private
 
   def client
-    @client ||= Gitlab::GithubImport::Client.new(session[:github_access_token])
+    @client ||= Gitlab::GithubImport::Client.new(session[:access_token], client_options)
   end
 
-  def verify_github_import_enabled
-    render_404 unless github_import_enabled?
+  def verify_import_enabled
+    render_404 unless import_enabled?
   end
 
-  def github_auth
-    if session[:github_access_token].blank?
-      go_to_github_for_permissions
-    end
+  def go_to_provider_for_permissions
+    redirect_to client.authorize_url(callback_import_url)
+  end
+
+  def import_enabled?
+    __send__("#{provider}_import_enabled?")
   end
 
-  def go_to_github_for_permissions
-    redirect_to client.authorize_url(callback_import_github_url)
+  def new_import_url
+    public_send("new_import_#{provider}_url")
   end
 
-  def github_unauthorized
-    session[:github_access_token] = nil
-    redirect_to new_import_github_url,
-      alert: 'Access denied to your GitHub account.'
+  def status_import_url
+    public_send("status_import_#{provider}_url")
   end
 
-  def logged_in_with_github?
-    current_user.identities.exists?(provider: 'github')
+  def callback_import_url
+    public_send("callback_import_#{provider}_url")
+  end
+
+  def provider_unauthorized
+    session[:access_token] = nil
+    redirect_to new_import_url,
+      alert: "Access denied to your #{Gitlab::ImportSources.options.key(provider.to_s)} account."
   end
 
   def access_params
-    { github_access_token: session[:github_access_token] }
+    { github_access_token: session[:access_token] }
+  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].blank?
+      go_to_provider_for_permissions
+    end
+  end
+
+  def client_options
+    {}
   end
 end
diff --git a/app/controllers/import/gogs_controller.rb b/app/controllers/import/gogs_controller.rb
deleted file mode 100644
index 4caf7d2605a..00000000000
--- a/app/controllers/import/gogs_controller.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-class Import::GogsController < Import::BaseController
-  before_action :verify_gogs_import_enabled
-  before_action :gogs_auth, only: [:status, :jobs, :create]
-
-  rescue_from Octokit::Unauthorized, with: :gogs_unauthorized
-
-  helper_method :logged_in_with_gogs?
-
-  def new
-    if session[:gogs_access_token]
-      redirect_to status_import_gogs_url
-    end
-  end
-
-  def personal_access_token
-    session[:gogs_access_token] = params[:personal_access_token]
-    session[:gogs_host_url] = params[:gogs_host_url]
-    redirect_to status_import_gogs_url
-  end
-
-  def status
-    @repos = client.repos
-    @already_added_projects = current_user.created_projects.where(import_type: "gogs")
-    already_added_projects_names = @already_added_projects.pluck(:import_source)
-
-    @gogs_root_url = session[:gogs_host_url]
-
-    @repos.reject!{ |repo| already_added_projects_names.include? repo.full_name }
-  end
-
-  def jobs
-    jobs = current_user.created_projects.where(import_type: "gogs").to_json(only: [:id, :import_status])
-    render json: jobs
-  end
-
-  def create
-    @repo_id = params[:repo_id].to_i
-    repo = client.repo(@repo_id)
-    @project_name = params[:new_name].presence || repo.name
-    namespace_path = params[:target_namespace].presence || current_user.namespace_path
-    @target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path)
-
-    if current_user.can?(:create_projects, @target_namespace)
-      @project = Gitlab::GithubImport::ProjectCreator.new(repo, @project_name, @target_namespace, current_user, access_params, type: 'gogs').execute
-    else
-      render 'unauthorized'
-    end
-  end
-
-  private
-
-  def client
-    @client ||= Gitlab::GithubImport::Client.new(session[:gogs_access_token], host: session[:gogs_host_url], api_version: 'v1')
-  end
-
-  def verify_gogs_import_enabled
-    render_404 unless gogs_import_enabled?
-  end
-
-  def gogs_auth
-    if session[:gogs_access_token].blank? || session[:gogs_host_url].blank?
-      redirect_to new_import_gogs_url,
-        alert: 'You need to specify both an Access Token and a Host URL.'
-    end
-  end
-
-  def gogs_unauthorized
-    session[:gogs_access_token] = nil
-    redirect_to new_import_gogs_url,
-      alert: 'Access denied to your Gogs account.'
-  end
-
-  def access_params
-    { github_access_token: session[:gogs_access_token] }
-  end
-end
diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb
index 29df2703d52..fb79e2a4eef 100644
--- a/app/helpers/import_helper.rb
+++ b/app/helpers/import_helper.rb
@@ -8,8 +8,8 @@ module ImportHelper
     link_to path_with_namespace, github_project_url(path_with_namespace), target: '_blank'
   end
 
-  def gogs_project_link(path_with_namespace)
-    link_to path_with_namespace, gogs_project_url(path_with_namespace), target: '_blank'
+  def gitea_project_link(root_url, path_with_namespace)
+    link_to path_with_namespace, gitea_project_url(root_url, path_with_namespace), target: '_blank'
   end
 
   private
@@ -25,7 +25,7 @@ module ImportHelper
     @github_url = provider.fetch('url', 'https://github.com') if provider
   end
 
-  def gogs_project_url(path_with_namespace)
-    "#{@gogs_root_url}/#{path_with_namespace}"
+  def gitea_project_url(root_url, path_with_namespace)
+    "#{root_url}/#{path_with_namespace}"
   end
 end
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index 8cac0b01881..287c0a4257f 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -5,13 +5,13 @@ module Projects
     class Error < StandardError; end
 
     ALLOWED_TYPES = [
-      'gogs',
+      'github',
       'bitbucket',
-      'fogbugz',
       'gitlab',
-      'github',
       'google_code',
-      'gitlab_project'
+      'fogbugz',
+      'gitlab_project',
+      'gitea'
     ]
 
     def execute
@@ -71,8 +71,23 @@ module Projects
     def importer
       return Gitlab::ImportExport::Importer.new(project) if @project.gitlab_project_import?
 
-      class_name = "Gitlab::#{project.import_type.camelize}Import::Importer"
-      class_name.constantize.new(project)
+      class_name =
+        case project.import_type
+        when 'github', 'gitea'
+          Gitlab::GithubImport::Importer
+        when 'bitbucket'
+          Gitlab::BitbucketImport::Importer
+        when 'gitlab'
+          Gitlab::GitlabImport::Importer
+        when 'google_code'
+          Gitlab::GoogleCodeImport::Importer
+        when 'fogbugz'
+          Gitlab::FogbugzImport::Importer
+        else
+          raise 'Unknown importer type!'
+        end
+
+      class_name.new(project)
     end
 
     def unknown_url?
diff --git a/app/views/import/gitea/new.html.haml b/app/views/import/gitea/new.html.haml
new file mode 100644
index 00000000000..02a116f996b
--- /dev/null
+++ b/app/views/import/gitea/new.html.haml
@@ -0,0 +1,23 @@
+- 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'
diff --git a/app/views/import/gogs/status.html.haml b/app/views/import/gitea/status.html.haml
similarity index 86%
rename from app/views/import/gogs/status.html.haml
rename to app/views/import/gitea/status.html.haml
index 86ccc79efc8..2b25892c0da 100644
--- a/app/views/import/gogs/status.html.haml
+++ b/app/views/import/gitea/status.html.haml
@@ -1,8 +1,8 @@
-- page_title "Gogs import"
+- page_title "Gitea import"
 - header_title "Projects", root_path
 %h3.page-title
-  %i.fa.fa-github
-  Import projects from Gogs
+  = custom_icon('go_logo')
+  Import projects from Gitea
 
 %p.light
   Select projects you want to import.
@@ -19,14 +19,14 @@
     %colgroup.import-jobs-status-col
     %thead
       %tr
-        %th From Gogs
+        %th From Gitea
         %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
-            = gogs_project_link(project.import_source)
+            = gitea_project_link(@gitea_root_url, project.import_source)
           %td
             = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
           %td.job-status
@@ -43,7 +43,7 @@
       - @repos.each do |repo|
         %tr{id: "repo_#{repo.id}"}
           %td
-            = gogs_project_link(repo.full_name)
+            = gitea_project_link(@gitea_root_url, repo.full_name)
           %td.import-target
             %fieldset.row
             .input-group
@@ -61,4 +61,4 @@
               Import
               = icon("spinner spin", class: "loading-icon")
 
-.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gogs_path}", import_path: "#{import_gogs_path}" } }
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitea_path}", import_path: "#{import_gitea_path}" } }
diff --git a/app/views/import/gogs/new.html.haml b/app/views/import/gogs/new.html.haml
deleted file mode 100644
index e1ae1be283c..00000000000
--- a/app/views/import/gogs/new.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-- page_title "Gogs Import"
-- header_title "Projects", root_path
-
-%h3.page-title
-  = image_tag(image_path('gogs-logo.svg'), alt: 'Gogs', size: "16x16")
-  Gogs
-
-%p
-  To import a Gogs project, you can use a
-  = succeed '.' do
-    = link_to 'Personal Access Token', 'https://github.com/gogits/go-gogs-client/wiki#access-token'
-
-= form_tag personal_access_token_import_gogs_path, method: :post, class: 'form-inline' do
-  .form-group
-    = text_field_tag :personal_access_token, '', class: 'form-control', placeholder: "Personal Access Token", size: 40
-    = text_field_tag :gogs_host_url, '', class: 'form-control', placeholder: "Gogs Host URL", size: 128
-    = submit_tag 'List Your Gogs Repositories', class: 'btn btn-success'
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 5edb9b69ed2..866b278ce57 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -48,11 +48,6 @@
                   - if github_import_enabled?
                     = link_to new_import_github_path, class: 'btn import_github' do
                       = icon('github', text: 'GitHub')
-                %div
-                  - if gogs_import_enabled?
-                    = link_to new_import_gogs_url, class: 'btn import_gogs' do
-                      = image_tag(image_path('gogs-logo.svg'), alt: 'Gogs', size: "14x14")
-                      Gogs
                 %div
                   - if bitbucket_import_enabled?
                     = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}", "data-no-turbolink" => "true" do
@@ -73,6 +68,11 @@
                   - if fogbugz_import_enabled?
                     = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
                       = 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
                   - if git_import_enabled?
                     = link_to "#", class: 'btn js-toggle-button import_git' do
diff --git a/app/views/shared/icons/_go_logo.svg.erb b/app/views/shared/icons/_go_logo.svg.erb
new file mode 100644
index 00000000000..5052651c110
--- /dev/null
+++ b/app/views/shared/icons/_go_logo.svg.erb
@@ -0,0 +1 @@
+<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>
diff --git a/changelogs/unreleased/22348-gitea-importer.yml b/changelogs/unreleased/22348-gitea-importer.yml
new file mode 100644
index 00000000000..ce81a3cfefb
--- /dev/null
+++ b/changelogs/unreleased/22348-gitea-importer.yml
@@ -0,0 +1,4 @@
+---
+title: New Gitea importer
+merge_request: 6945
+author:
diff --git a/changelogs/unreleased/22348-gogs-importer.yml b/changelogs/unreleased/22348-gogs-importer.yml
deleted file mode 100644
index 9543d2a4d26..00000000000
--- a/changelogs/unreleased/22348-gogs-importer.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Gogs importer
-merge_request: 6945
-author: Kim Carlbäcker
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index c0dbf62bfa2..ee97b4e42b9 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -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['visibility_level']   = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
 Settings.gitlab['domain_whitelist'] ||= []
-Settings.gitlab['import_sources'] ||= %w[gogs 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['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config', 'no_todos_messages.yml'))
 
diff --git a/config/routes/import.rb b/config/routes/import.rb
index a0427dade99..c378253bf15 100644
--- a/config/routes/import.rb
+++ b/config/routes/import.rb
@@ -6,7 +6,7 @@ namespace :import do
     get :jobs
   end
 
-  resource :gogs, only: [:create, :new], controller: :gogs do
+  resource :gitea, only: [:create, :new], controller: :gitea do
     post :personal_access_token
     get :status
     get :jobs
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index eb3d9f29451..9d142f1b82e 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -45,7 +45,7 @@ module Gitlab
         default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
         default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
         domain_whitelist: Settings.gitlab['domain_whitelist'],
-        import_sources: %w[gogs 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'],
         max_artifacts_size: Settings.artifacts['max_size'],
         require_two_factor_authentication: false,
diff --git a/lib/gitlab/gogs_import/importer.rb b/lib/gitlab/gogs_import/importer.rb
deleted file mode 100644
index 604e31d35a3..00000000000
--- a/lib/gitlab/gogs_import/importer.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'uri'
-
-module Gitlab
-  module GogsImport
-    class Importer < Gitlab::GithubImport::Importer
-      include Gitlab::ShellAdapter
-
-      attr_reader :client, :errors, :project, :repo, :repo_url
-
-      def initialize(project)
-        @project  = project
-        @repo     = project.import_source
-        @repo_url = project.import_url
-        @errors   = []
-        @labels   = {}
-
-        if credentials
-          uri = URI.parse(project.import_url)
-          host = "#{uri.scheme}://#{uri.host}:#{uri.port}#{uri.path}".sub(/[\w-]+\/[\w-]+\.git\z/, '')
-          @client = GithubImport::Client.new(credentials[:user], host: host, api_version: 'v1')
-        else
-          raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
-        end
-      end
-
-      def execute
-        import_labels
-        import_milestones
-        import_pull_requests
-        import_issues
-        import_comments(:issues)
-        import_comments(:pull_requests)
-        import_wiki
-        # NOTE: this is commented out since Gogs doesn't have release-API yet
-        # import_releases
-        handle_errors
-
-        true
-      end
-
-      def import_milestones
-        fetch_resources(:milestones, repo, state: :all, per_page: 100) do |milestones|
-          milestones.each do |raw|
-            begin
-              GogsImport::MilestoneFormatter.new(project, raw).create!
-            rescue => e
-              errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
-            end
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/gogs_import/milestone_formatter.rb b/lib/gitlab/gogs_import/milestone_formatter.rb
deleted file mode 100644
index 990e792929a..00000000000
--- a/lib/gitlab/gogs_import/milestone_formatter.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module Gitlab
-  module GogsImport
-    class MilestoneFormatter < GithubImport::MilestoneFormatter
-      def self.iid_attr
-        :id
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index 9564c4cc134..34587582bd1 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -14,14 +14,14 @@ module Gitlab
 
       def options
         {
-          'Gogs'          => 'gogs',
           'GitHub'        => 'github',
           'Bitbucket'     => 'bitbucket',
           'GitLab.com'    => 'gitlab',
           'Google Code'   => 'google_code',
           'FogBugz'       => 'fogbugz',
           'Repo by URL'   => 'git',
-          'GitLab export' => 'gitlab_project'
+          'GitLab export' => 'gitlab_project',
+          'Gitea'         => 'gitea'
         }
       end
     end
diff --git a/spec/controllers/import/gitea_controller_spec.rb b/spec/controllers/import/gitea_controller_spec.rb
new file mode 100644
index 00000000000..3064d1dd58a
--- /dev/null
+++ b/spec/controllers/import/gitea_controller_spec.rb
@@ -0,0 +1,43 @@
+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[: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_root_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
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index 4f96567192d..55820a7cc65 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -3,34 +3,18 @@ require 'spec_helper'
 describe Import::GithubController do
   include ImportSpecHelper
 
-  let(:user) { create(:user) }
-  let(:token) { "asdasd12345" }
-  let(:access_params) { { github_access_token: token } }
+  let(:provider) { :github }
 
-  def assign_session_token
-    session[:github_access_token] = token
-  end
-
-  before do
-    sign_in(user)
-    allow(controller).to receive(:github_import_enabled?).and_return(true)
-  end
+  include_context 'a GitHub-ish import controller'
 
   describe "GET new" do
-    it "redirects to GitHub for an access token if logged in with GitHub" do
-      allow(controller).to receive(:logged_in_with_github?).and_return(true)
-      expect(controller).to receive(:go_to_github_for_permissions)
+    it_behaves_like 'a GitHub-ish import controller: GET new'
 
-      get :new
-    end
-
-    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)
+    it "redirects to GitHub for an access token if logged in with GitHub" do
+      allow(controller).to receive(:logged_in_with_provider?).and_return(true)
+      expect(controller).to receive(:go_to_provider_for_permissions)
 
       get :new
-
-      expect(controller).to redirect_to(status_import_github_url)
     end
   end
 
@@ -45,202 +29,20 @@ describe Import::GithubController do
 
       get :callback
 
-      expect(session[:github_access_token]).to eq(token)
+      expect(session[:access_token]).to eq(token)
       expect(controller).to redirect_to(status_import_github_url)
     end
   end
 
   describe "POST personal_access_token" do
-    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[:github_access_token]).to eq(token)
-      expect(controller).to redirect_to(status_import_github_url)
-    end
+    it_behaves_like 'a GitHub-ish import controller: POST personal_access_token'
   end
 
   describe "GET status" do
-    before do
-      @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
+    it_behaves_like 'a GitHub-ish import controller: GET status'
   end
 
   describe "POST create" do
-    let(:github_username) { user.username }
-    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
+    it_behaves_like 'a GitHub-ish import controller: POST create'
   end
 end
diff --git a/spec/routing/import_routing_spec.rb b/spec/routing/import_routing_spec.rb
new file mode 100644
index 00000000000..f1234ff470b
--- /dev/null
+++ b/spec/routing/import_routing_spec.rb
@@ -0,0 +1,166 @@
+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
diff --git a/spec/support/githubish_import_controller_shared_context.rb b/spec/support/githubish_import_controller_shared_context.rb
new file mode 100644
index 00000000000..e71994edec6
--- /dev/null
+++ b/spec/support/githubish_import_controller_shared_context.rb
@@ -0,0 +1,10 @@
+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
diff --git a/spec/support/githubish_import_controller_shared_examples.rb b/spec/support/githubish_import_controller_shared_examples.rb
new file mode 100644
index 00000000000..aa2d8aed0bd
--- /dev/null
+++ b/spec/support/githubish_import_controller_shared_examples.rb
@@ -0,0 +1,228 @@
+# 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).
+
+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[: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
+    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
+  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[:access_token]).to eq(nil)
+    expect(controller).to redirect_to(new_import_url)
+    expect(flash[:alert]).to eq("Access denied to your #{Gitlab::ImportSources.options.key(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
+  end
+
+  context "when the repository owner is the Gitea user" do
+    context "when the Gitea 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 Gitea 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 Gitea user" do
+    let(:other_username) { "someone_else" }
+
+    before do
+      provider_repo.owner = OpenStruct.new(login: other_username)
+      assign_session_token
+    end
+
+    context "when a namespace with the Gitea 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 Gitea 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
diff --git a/spec/support/import_spec_helper.rb b/spec/support/import_spec_helper.rb
index 6710962f082..cd25e05ac4b 100644
--- a/spec/support/import_spec_helper.rb
+++ b/spec/support/import_spec_helper.rb
@@ -30,4 +30,8 @@ module ImportSpecHelper
     )
     allow(Gitlab.config.omniauth).to receive(:providers).and_return([provider])
   end
+
+  def assign_session_token
+    session[:access_token] = 'asdasd12345'
+  end
 end
-- 
GitLab