diff --git a/CHANGELOG b/CHANGELOG index 235a99b43270fb8c1491c8d733e39eddceed0932..8d01bc4d890ce03cf83e7646b321cc81c5cd0e5b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,8 @@ v 7.8.0 - Disable blacklist validation for project names - Allow configuring protection of the default branch upon first push (Marco Wessel) - + - Add gitlab.com importer + - Add an ability to login with gitlab.com - - Add a commit calendar to the user profile (Hannes Rosenögger) - diff --git a/Gemfile b/Gemfile index 8eede269e2d278bd95051366dde7541eacab147d..9676d3c85d21011818632826dea5a10045ad7082 100644 --- a/Gemfile +++ b/Gemfile @@ -29,6 +29,7 @@ gem 'omniauth-twitter' gem 'omniauth-github' gem 'omniauth-shibboleth' gem 'omniauth-kerberos' +gem 'omniauth-gitlab' gem 'doorkeeper', '2.1.0' gem "rack-oauth2", "~> 1.0.5" diff --git a/Gemfile.lock b/Gemfile.lock index 7f115d79de02729d3ec7f590cdba2dbd57e7e312..6d2d281e4768acf6079696b381ab277faaf28525 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -332,6 +332,9 @@ GEM omniauth-github (1.1.1) omniauth (~> 1.0) omniauth-oauth2 (~> 1.1) + omniauth-gitlab (1.0.0) + omniauth (~> 1.0) + omniauth-oauth2 (~> 1.0) omniauth-google-oauth2 (0.2.5) omniauth (> 1.0) omniauth-oauth2 (~> 1.1) @@ -689,6 +692,7 @@ DEPENDENCIES octokit (= 3.7.0) omniauth (~> 1.1.3) omniauth-github + omniauth-gitlab omniauth-google-oauth2 omniauth-kerberos omniauth-shibboleth diff --git a/app/assets/images/authbuttons/gitlab_32.png b/app/assets/images/authbuttons/gitlab_32.png new file mode 100644 index 0000000000000000000000000000000000000000..f3b78cb6efba1e8cff625fd262b36705dd707c93 Binary files /dev/null and b/app/assets/images/authbuttons/gitlab_32.png differ diff --git a/app/assets/images/authbuttons/gitlab_64.png b/app/assets/images/authbuttons/gitlab_64.png new file mode 100644 index 0000000000000000000000000000000000000000..ff2945fe89eddf03e2e1fc8deb39a6a7bc5bc088 Binary files /dev/null and b/app/assets/images/authbuttons/gitlab_64.png differ diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..268efd7c832555b1d27bb87d5419f092bbddaf82 --- /dev/null +++ b/app/assets/javascripts/importer_status.js.coffee @@ -0,0 +1,31 @@ +class @ImporterStatus + constructor: (@jobs_url, @import_url) -> + this.initStatusPage() + this.setAutoUpdate() + + initStatusPage: -> + $(".btn-add-to-import").click (event) => + new_namespace = null + tr = $(event.currentTarget).closest("tr") + id = tr.attr("id").replace("repo_", "") + if tr.find(".import-target input").length > 0 + new_namespace = tr.find(".import-target input").prop("value") + tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name")) + $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script' + + setAutoUpdate: -> + setInterval (=> + $.get @jobs_url, (data) => + $.each data, (i, job) => + job_item = $("#project_" + job.id) + status_field = job_item.find(".job-status") + + if job.import_status == 'finished' + job_item.removeClass("active").addClass("success") + status_field.html('<span class="cgreen"><i class="fa fa-check"></i> done</span>') + else if job.import_status == 'started' + status_field.html("<i class='fa fa-spinner fa-spin'></i> started") + else + status_field.html(job.import_status) + + ), 4000 \ No newline at end of file diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..4df171dbcfea2ffaf7ab2d6ec721eb7056a58037 --- /dev/null +++ b/app/controllers/import/base_controller.rb @@ -0,0 +1,21 @@ +class Import::BaseController < ApplicationController + + private + + def get_or_create_namespace + existing_namespace = Namespace.find_by("path = ? OR name = ?", @target_namespace, @target_namespace) + + if existing_namespace + if existing_namespace.owner == current_user + namespace = existing_namespace + else + @already_been_taken = true + return false + end + else + namespace = Group.create(name: @target_namespace, path: @target_namespace, owner: current_user) + namespace.add_owner(current_user) + namespace + end + end +end diff --git a/app/controllers/github_imports_controller.rb b/app/controllers/import/github_controller.rb similarity index 57% rename from app/controllers/github_imports_controller.rb rename to app/controllers/import/github_controller.rb index b73e3f7ffac44f3ad16d5a39849735fef702f209..108fc4396a6069bda1d6f6e5ff0b647200340932 100644 --- a/app/controllers/github_imports_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -1,4 +1,4 @@ -class GithubImportsController < ApplicationController +class Import::GithubController < Import::BaseController before_filter :github_auth, except: :callback rescue_from Octokit::Unauthorized, with: :github_unauthorized @@ -7,7 +7,7 @@ class GithubImportsController < ApplicationController token = client.auth_code.get_token(params[:code]).token current_user.github_access_token = token current_user.save - redirect_to status_github_import_url + redirect_to status_import_github_url end def status @@ -19,7 +19,7 @@ class GithubImportsController < ApplicationController @already_added_projects = current_user.created_projects.where(import_type: "github") 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 @@ -30,30 +30,18 @@ class GithubImportsController < ApplicationController def create @repo_id = params[:repo_id].to_i repo = octo_client.repo(@repo_id) - target_namespace = params[:new_namespace].presence || repo.owner.login - existing_namespace = Namespace.find_by("path = ? OR name = ?", target_namespace, target_namespace) + @target_namespace = params[:new_namespace].presence || repo.owner.login + @project_name = repo.name + + namespace = get_or_create_namespace || (render and return) - if existing_namespace - if existing_namespace.owner == current_user - namespace = existing_namespace - else - @already_been_taken = true - @target_namespace = target_namespace - @project_name = repo.name - render and return - end - else - namespace = Group.create(name: target_namespace, path: target_namespace, owner: current_user) - namespace.add_owner(current_user) - end - - @project = Gitlab::Github::ProjectCreator.new(repo, namespace, current_user).execute + @project = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, current_user).execute end private def client - @client ||= Gitlab::Github::Client.new.client + @client ||= Gitlab::GithubImport::Client.new.client end def octo_client @@ -69,7 +57,7 @@ class GithubImportsController < ApplicationController def go_to_github_for_permissions redirect_to client.auth_code.authorize_url({ - redirect_uri: callback_github_import_url, + redirect_uri: callback_import_github_url, scope: "repo, user, user:email" }) end diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..a51ea36aff8dce24be9b8d72d95a4626d3b09d8d --- /dev/null +++ b/app/controllers/import/gitlab_controller.rb @@ -0,0 +1,57 @@ +class Import::GitlabController < Import::BaseController + before_filter :gitlab_auth, except: :callback + + rescue_from OAuth2::Error, with: :gitlab_unauthorized + + def callback + token = client.get_token(params[:code], callback_import_gitlab_url) + current_user.gitlab_access_token = token + current_user.save + redirect_to status_import_gitlab_url + end + + def status + @repos = client.projects + + @already_added_projects = current_user.created_projects.where(import_type: "gitlab") + already_added_projects_names = @already_added_projects.pluck(:import_source) + + @repos.to_a.reject!{ |repo| already_added_projects_names.include? repo["path_with_namespace"] } + end + + def jobs + jobs = current_user.created_projects.where(import_type: "gitlab").to_json(only: [:id, :import_status]) + render json: jobs + end + + def create + @repo_id = params[:repo_id].to_i + repo = client.project(@repo_id) + @target_namespace = params[:new_namespace].presence || repo["namespace"]["path"] + @project_name = repo["name"] + + namespace = get_or_create_namespace || (render and return) + + @project = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, current_user).execute + end + + private + + def client + @client ||= Gitlab::GitlabImport::Client.new(current_user.gitlab_access_token) + end + + def gitlab_auth + if current_user.gitlab_access_token.blank? + go_to_gitlab_for_permissions + end + end + + def go_to_gitlab_for_permissions + redirect_to client.authorize_url(callback_import_gitlab_url) + end + + def gitlab_unauthorized + go_to_gitlab_for_permissions + end +end diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb index df18db71c84d7c1a88f9c18a2388355c693ab5bb..c7bc9307a58c31244a0ae618914b00d58126b4c3 100644 --- a/app/helpers/oauth_helper.rb +++ b/app/helpers/oauth_helper.rb @@ -4,7 +4,7 @@ module OauthHelper end def default_providers - [:twitter, :github, :google_oauth2, :ldap] + [:twitter, :github, :gitlab, :google_oauth2, :ldap] end def enabled_oauth_providers @@ -13,7 +13,7 @@ module OauthHelper def enabled_social_providers enabled_oauth_providers.select do |name| - [:twitter, :github, :google_oauth2].include?(name.to_sym) + [:twitter, :gitlab, :github, :google_oauth2].include?(name.to_sym) end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 5cec6ae99d87d3223c4fd74de6c80b30b0d274e3..36463892ebf9f9d0fcd2558350291e9937140156 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -253,4 +253,8 @@ module ProjectsHelper def github_import_enabled? enabled_oauth_providers.include?(:github) end + + def gitlab_import_enabled? + enabled_oauth_providers.include?(:gitlab) + end end diff --git a/app/views/github_imports/status.html.haml b/app/views/github_imports/status.html.haml deleted file mode 100644 index 52a1e16cd047543535afc6df2fba4c9da075f1b3..0000000000000000000000000000000000000000 --- a/app/views/github_imports/status.html.haml +++ /dev/null @@ -1,63 +0,0 @@ -%h3.page-title - %i.fa.fa-github - Import repositories from GitHub.com - -%p.light - Select projects you want to import. - -%hr -%table.table.import-jobs - %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= project.import_source - %td - %strong= link_to project.name_with_namespace, project - %td.job-status - - if project.import_status == 'finished' - %span.cgreen - %i.fa.fa-check - done - - else - = project.human_import_status_name - - - @repos.each do |repo| - %tr{id: "repo_#{repo.id}"} - %td= repo.full_name - %td.import-target - = repo.full_name - %td.import-actions.job-status - = button_tag "Add", class: "btn btn-add-to-import" - - -:coffeescript - $(".btn-add-to-import").click () -> - new_namespace = null - tr = $(this).closest("tr") - id = tr.attr("id").replace("repo_", "") - if tr.find(".import-target input").length > 0 - new_namespace = tr.find(".import-target input").prop("value") - tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name")) - $.post "#{github_import_url}", {repo_id: id, new_namespace: new_namespace}, dataType: 'script' - - - setInterval (-> - $.get "#{jobs_github_import_path}", (data)-> - $.each data, (i, job) -> - job_item = $("#project_" + job.id) - status_field = job_item.find(".job-status") - - if job.import_status == 'finished' - job_item.removeClass("active").addClass("success") - status_field.html('<span class="cgreen"><i class="fa fa-check"></i> done</span>') - else if job.import_status == 'started' - status_field.html("<i class='fa fa-spinner fa-spin'></i> started") - else - status_field.html(job.import_status) - - ), 4000 diff --git a/app/views/github_imports/create.js.haml b/app/views/import/base/create.js.haml similarity index 100% rename from app/views/github_imports/create.js.haml rename to app/views/import/base/create.js.haml diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..1676c3c26aed508c1fbcb2e82c70042ad9fd8107 --- /dev/null +++ b/app/views/import/github/status.html.haml @@ -0,0 +1,39 @@ +%h3.page-title + %i.fa.fa-github + Import repositories from GitHub.com + +%p.light + Select projects you want to import. + +%hr +%table.table.import-jobs + %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= project.import_source + %td + %strong= link_to project.path_with_namespace, project + %td.job-status + - if project.import_status == 'finished' + %span.cgreen + %i.fa.fa-check + done + - else + = project.human_import_status_name + + - @repos.each do |repo| + %tr{id: "repo_#{repo.id}"} + %td= repo.full_name + %td.import-target + = repo.full_name + %td.import-actions.job-status + = button_tag "Add", class: "btn btn-add-to-import" + +:coffeescript + $ -> + new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}") diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..9aedacef04bc2d6bc96c2ad7f62bbe242d11a039 --- /dev/null +++ b/app/views/import/gitlab/status.html.haml @@ -0,0 +1,39 @@ +%h3.page-title + %i.fa.fa-github + Import repositories from GitLab.com + +%p.light + Select projects you want to import. + +%hr +%table.table.import-jobs + %thead + %tr + %th From GitLab.com + %th To GitLab private instance + %th Status + %tbody + - @already_added_projects.each do |project| + %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %td= project.import_source + %td + %strong= link_to project.path_with_namespace, project + %td.job-status + - if project.import_status == 'finished' + %span.cgreen + %i.fa.fa-check + done + - else + = project.human_import_status_name + + - @repos.each do |repo| + %tr{id: "repo_#{repo["id"]}"} + %td= repo["path_with_namespace"] + %td.import-target + = repo["path_with_namespace"] + %td.import-actions.job-status + = button_tag "Add", class: "btn btn-add-to-import" + +:coffeescript + $ -> + new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_url}") diff --git a/app/views/projects/_github_import_modal.html.haml b/app/views/projects/_github_import_modal.html.haml index 02c9ef45f2b37625909a11b4f0469e232455ca9d..99325e66119a67a8f22a82bf230d44369d8d30fa 100644 --- a/app/views/projects/_github_import_modal.html.haml +++ b/app/views/projects/_github_import_modal.html.haml @@ -6,17 +6,4 @@ %h3 GitHub OAuth import .modal-body You need to setup integration with GitHub first. - = link_to 'How to setup integration with GitHub', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md' - - -:javascript - $(function(){ - var import_modal = $('#github_import_modal').modal({modal: true, show:false}); - $('.how_to_import_link').bind("click", function(e){ - e.preventDefault(); - import_modal.show(); - }); - $('.modal-header .close').bind("click", function(){ - import_modal.hide(); - }) - }) + = link_to 'How to setup integration with GitHub', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md' \ No newline at end of file diff --git a/app/views/projects/_gitlab_import_modal.html.haml b/app/views/projects/_gitlab_import_modal.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..e7503f023b13b8fd187ef3a5f63a54a9cb821f33 --- /dev/null +++ b/app/views/projects/_gitlab_import_modal.html.haml @@ -0,0 +1,9 @@ +%div#gitlab_import_modal.modal.hide + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3 GitLab OAuth import + .modal-body + You need to setup integration with GitLab first. + = link_to 'How to setup integration with GitLab', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/gitlab.md' \ No newline at end of file diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 3e0f9cbd80b1785f79212f93482c5f7f48484ab4..61f6a66c386cd07eca2690d130e528643fa8d3f5 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -44,7 +44,7 @@ .col-sm-2 .col-sm-10 - if github_import_enabled? - = link_to status_github_import_path do + = link_to status_import_github_path do %i.fa.fa-github Import projects from GitHub - else @@ -52,6 +52,19 @@ %i.fa.fa-github Import projects from GitHub = render 'github_import_modal' + + .project-import.form-group + .col-sm-2 + .col-sm-10 + - if gitlab_import_enabled? + = link_to status_import_gitlab_path do + %i.fa.fa-heart + Import projects from GitLab.com + - else + = link_to '#', class: 'how_to_import_link light' do + %i.fa.fa-heart + Import projects from GitLab.com + = render 'gitlab_import_modal' %hr.prepend-botton-10 @@ -79,3 +92,11 @@ %i.fa.fa-spinner.fa-spin Creating project & repository. %p Please wait a moment, this page will automatically refresh when ready. + +:coffeescript + $ -> + $('.how_to_import_link').bind 'click', (e) -> + e.preventDefault() + import_modal = $(this).parent().find(".modal").show() + $('.modal-header .close').bind 'click', -> + $(".modal").hide() \ No newline at end of file diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index 0bcc42bc62c8d9781556814b503841552eab4950..5f9970d3795a4fe2eba22bb8a73547035976c545 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -10,11 +10,13 @@ class RepositoryImportWorker project.path_with_namespace, project.import_url) - if project.import_type == 'github' - result_of_data_import = Gitlab::Github::Importer.new(project).execute - else - result_of_data_import = true - end + result_of_data_import = if project.import_type == 'github' + Gitlab::GithubImport::Importer.new(project).execute + elsif project.import_type == 'gitlab' + Gitlab::GitlabImport::Importer.new(project).execute + else + true + end if result && result_of_data_import project.import_finish diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index e9b843e29b4b2e44544a3012b3efa497c2f204f2..9da7ebf4290df3f7a04f44c96480018332ba536c 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -27,7 +27,7 @@ Doorkeeper.configure do # Access token expiration time (default 2 hours). # If you want to disable expiration, set this to nil. - # access_token_expires_in 2.hours + access_token_expires_in nil # Reuse access token for the same resource owner within an application (disabled by default) # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383 diff --git a/config/routes.rb b/config/routes.rb index f0abd876ecda998d3d8b0efff67eb2a860a129f8..3aadb732e6d0a6cf98c634e707d485d4081275f9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -51,14 +51,25 @@ Gitlab::Application.routes.draw do end get '/s/:username' => 'snippets#user_index', as: :user_snippets, constraints: { username: /.*/ } + # - # Github importer area + # Import # - resource :github_import, only: [:create, :new] do - get :status - get :callback - get :jobs + namespace :import do + resource :github, only: [:create, :new], controller: :github do + get :status + get :callback + get :jobs + end + + resource :gitlab, only: [:create, :new], controller: :gitlab do + get :status + get :callback + get :jobs + end end + + # # Explore area diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md new file mode 100644 index 0000000000000000000000000000000000000000..b3b1d897225c20137f9bb46208cd2eadfab06736 --- /dev/null +++ b/doc/integration/gitlab.md @@ -0,0 +1,54 @@ +# GitLab OAuth2 OmniAuth Provider + +To enable the GitLab OmniAuth provider you must register your application with GitLab. GitLab will generate a client ID and secret key for you to use. + +1. Sign in to GitLab. + +1. Navigate to your settings. + +1. Select "Applications" in the left menu. + +1. Select "New application". + +1. Provide the required details. + - Name: This can be anything. Consider something like "\<Organization\>'s GitLab" or "\<Your Name\>'s GitLab" or something else descriptive. + - Redirect URI: + + ``` + http://gitlab.example.com/import/gitlab/callback + http://gitlab.example.com/users/auth/gitlab/callback + ``` + + The first link is required for the importer and second for the authorization. + +1. Select "Submit". + +1. You should now see a Application ID and Secret. Keep this page open as you continue configuration. + +1. On your GitLab server, open the configuration file. + + ```sh + cd /home/git/gitlab + + sudo -u git -H editor config/gitlab.yml + ``` + +1. Find the section dealing with OmniAuth. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) for more details. + +1. Under `providers:` uncomment (or add) lines that look like the following: + + ``` + - { name: 'gitlab', app_id: 'YOUR APP ID', + app_secret: 'YOUR APP SECRET', + args: { scope: 'api' } } + ``` + +1. Change 'YOUR APP ID' to the Application ID from the GitLab application page. + +1. Change 'YOUR APP SECRET' to the secret from the GitLab application page. + +1. Save the configuration file. + +1. Restart GitLab for the changes to take effect. + +On the sign in page there should now be a GitLab icon below the regular sign in form. Click the icon to begin the authentication process. GitLab will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to your GitLab instance and will be signed in. diff --git a/lib/gitlab/github/client.rb b/lib/gitlab/github_import/client.rb similarity index 82% rename from lib/gitlab/github/client.rb rename to lib/gitlab/github_import/client.rb index d6b936c649c74ca33e4ed2edc0926fe4835e6944..cf43d36c6c383fdb3e2f1ccea21ef3a3672ad614 100644 --- a/lib/gitlab/github/client.rb +++ b/lib/gitlab/github_import/client.rb @@ -1,5 +1,5 @@ module Gitlab - module Github + module GithubImport class Client attr_reader :client @@ -14,7 +14,7 @@ module Gitlab private def config - Gitlab.config.omniauth.providers.select{|provider| provider.name == "github"}.first + Gitlab.config.omniauth.providers.find{|provider| provider.name == "github"} end def github_options diff --git a/lib/gitlab/github/importer.rb b/lib/gitlab/github_import/importer.rb similarity index 71% rename from lib/gitlab/github/importer.rb rename to lib/gitlab/github_import/importer.rb index 9f0fc6c4471d4474b5f0ef82a371c017d0a4f153..1f02ee49b6a359a08c0ceebe230271b96050950c 100644 --- a/lib/gitlab/github/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -1,10 +1,11 @@ module Gitlab - module Github + module GithubImport class Importer attr_reader :project def initialize(project) @project = project + @formatter = Gitlab::ImportFormatter.new end def execute @@ -13,12 +14,14 @@ module Gitlab #Issues && Comments client.list_issues(project.import_source, state: :all).each do |issue| if issue.pull_request.nil? - body = "*Created by: #{issue.user.login}*\n\n#{issue.body}" + + body = @formatter.author_line(issue.user.login, issue.body) if issue.comments > 0 - body += "\n\n\n**Imported comments:**\n" + body += @formatter.comments_header + client.issue_comments(project.import_source, issue.number).each do |c| - body += "\n\n*By #{c.user.login} on #{c.created_at}*\n\n#{c.body}" + body += @formatter.comment_to_md(c.user.login, c.created_at, c.body) end end @@ -40,7 +43,8 @@ module Gitlab end def gl_user_id(project, github_id) - user = User.joins(:identities).find_by("identities.extern_uid = ?", github_id.to_s) + user = User.joins(:identities). + find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s) (user && user.id) || project.creator_id end end diff --git a/lib/gitlab/github/project_creator.rb b/lib/gitlab/github_import/project_creator.rb similarity index 97% rename from lib/gitlab/github/project_creator.rb rename to lib/gitlab/github_import/project_creator.rb index 7b04926071f6180d67233f23e8463dd5c7b1e263..9439ca6cbf4803f12a50dcf416f519b32026a37d 100644 --- a/lib/gitlab/github/project_creator.rb +++ b/lib/gitlab/github_import/project_creator.rb @@ -1,5 +1,5 @@ module Gitlab - module Github + module GithubImport class ProjectCreator attr_reader :repo, :namespace, :current_user diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb new file mode 100644 index 0000000000000000000000000000000000000000..2206b68da99200147b5e22e643525c7b0fa14b4b --- /dev/null +++ b/lib/gitlab/gitlab_import/client.rb @@ -0,0 +1,82 @@ +module Gitlab + module GitlabImport + class Client + attr_reader :client, :api + + PER_PAGE = 100 + + def initialize(access_token) + @client = ::OAuth2::Client.new( + config.app_id, + config.app_secret, + github_options + ) + + if access_token + @api = OAuth2::AccessToken.from_hash(@client, access_token: access_token) + end + end + + def authorize_url(redirect_uri) + client.auth_code.authorize_url({ + redirect_uri: redirect_uri, + scope: "api" + }) + end + + def get_token(code, redirect_uri) + client.auth_code.get_token(code, redirect_uri: redirect_uri).token + end + + def issues(project_identifier) + lazy_page_iterator(PER_PAGE) do |page| + api.get("/api/v3/projects/#{project_identifier}/issues?per_page=#{PER_PAGE}&page=#{page}").parsed + end + end + + def issue_comments(project_identifier, issue_id) + lazy_page_iterator(PER_PAGE) do |page| + api.get("/api/v3/projects/#{project_identifier}/issues/#{issue_id}/notes?per_page=#{PER_PAGE}&page=#{page}").parsed + end + end + + def project(id) + api.get("/api/v3/projects/#{id}").parsed + end + + def projects + lazy_page_iterator(PER_PAGE) do |page| + api.get("/api/v3/projects?per_page=#{PER_PAGE}&page=#{page}").parsed + end + end + + private + + def lazy_page_iterator(per_page) + Enumerator.new do |y| + page = 1 + loop do + items = yield(page) + items.each do |item| + y << item + end + break if items.empty? || items.size < per_page + page += 1 + end + end + end + + def config + Gitlab.config.omniauth.providers.find{|provider| provider.name == "gitlab"} + end + + def github_options + { + site: 'https://gitlab.com/', + authorize_url: 'oauth/authorize', + token_url: 'oauth/token' + } + end + end + end +end diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb new file mode 100644 index 0000000000000000000000000000000000000000..5f9b14399a450136ea7849f32f49eb5f4f2e8b8e --- /dev/null +++ b/lib/gitlab/gitlab_import/importer.rb @@ -0,0 +1,50 @@ +module Gitlab + module GitlabImport + class Importer + attr_reader :project, :client + + def initialize(project) + @project = project + @client = Client.new(project.creator.gitlab_access_token) + @formatter = Gitlab::ImportFormatter.new + end + + def execute + project_identifier = URI.encode(project.import_source, '/') + + #Issues && Comments + issues = client.issues(project_identifier) + + issues.each do |issue| + body = @formatter.author_line(issue["author"]["name"], issue["description"]) + + comments = client.issue_comments(project_identifier, issue["id"]) + + if comments.any? + body += @formatter.comments_header + end + + comments.each do |comment| + body += @formatter.comment_to_md(comment["author"]["name"], comment["created_at"], comment["body"]) + end + + project.issues.create!( + description: body, + title: issue["title"], + state: issue["state"], + author_id: gl_user_id(project, issue["author"]["id"]) + ) + end + + true + end + + private + + def gl_user_id(project, gitlab_id) + user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'gitlab'", gitlab_id.to_s) + (user && user.id) || project.creator_id + end + end + end +end diff --git a/lib/gitlab/gitlab_import/project_creator.rb b/lib/gitlab/gitlab_import/project_creator.rb new file mode 100644 index 0000000000000000000000000000000000000000..6424d56f8f1b41ab5f0c88bf0e3ffbc8afb049c2 --- /dev/null +++ b/lib/gitlab/gitlab_import/project_creator.rb @@ -0,0 +1,39 @@ +module Gitlab + module GitlabImport + class ProjectCreator + attr_reader :repo, :namespace, :current_user + + def initialize(repo, namespace, current_user) + @repo = repo + @namespace = namespace + @current_user = current_user + end + + def execute + @project = Project.new( + name: repo["name"], + path: repo["path"], + description: repo["description"], + namespace: namespace, + creator: current_user, + visibility_level: repo["visibility_level"], + import_type: "gitlab", + import_source: repo["path_with_namespace"], + import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{current_user.gitlab_access_token}@") + ) + + if @project.save! + @project.reload + + if @project.import_failed? + @project.import_retry + else + @project.import_start + end + end + + @project + end + end + end +end diff --git a/lib/gitlab/import_formatter.rb b/lib/gitlab/import_formatter.rb new file mode 100644 index 0000000000000000000000000000000000000000..ebb4b87f7e33a5d907af5c23d40a4754a3c4bdc1 --- /dev/null +++ b/lib/gitlab/import_formatter.rb @@ -0,0 +1,15 @@ +module Gitlab + class ImportFormatter + def comment_to_md(author, date, body) + "\n\n*By #{author} on #{date}*\n\n#{body}" + end + + def comments_header + "\n\n\n**Imported comments:**\n" + end + + def author_line(author, body) + "*Created by: #{author}*\n\n#{body}" + end + end +end diff --git a/spec/controllers/github_imports_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb similarity index 85% rename from spec/controllers/github_imports_controller_spec.rb rename to spec/controllers/import/github_controller_spec.rb index 26e7854fea3a9fc5fc5ad0f95d75f44ff94987a9..010635677333549e185db3bd9ca908ca39bce465 100644 --- a/spec/controllers/github_imports_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe GithubImportsController do +describe Import::GithubController do let(:user) { create(:user, github_access_token: 'asd123') } before do @@ -10,13 +10,13 @@ describe GithubImportsController do describe "GET callback" do it "updates access token" do token = "asdasd12345" - Gitlab::Github::Client.any_instance.stub_chain(:client, :auth_code, :get_token, :token).and_return(token) + Gitlab::GithubImport::Client.any_instance.stub_chain(:client, :auth_code, :get_token, :token).and_return(token) Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "github") get :callback user.reload.github_access_token.should == token - controller.should redirect_to(status_github_import_url) + controller.should redirect_to(status_import_github_url) end end @@ -55,7 +55,7 @@ describe GithubImportsController do it "takes already existing namespace" do namespace = create(:namespace, name: "john", owner: user) - Gitlab::Github::ProjectCreator.should_receive(:new).with(@repo, namespace, user). + Gitlab::GithubImport::ProjectCreator.should_receive(:new).with(@repo, namespace, user). and_return(double(execute: true)) controller.stub_chain(:octo_client, :repo).and_return(@repo) diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..36995091c690211d4163b9349a119df0d6077ebf --- /dev/null +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +describe Import::GitlabController do + let(:user) { create(:user, gitlab_access_token: 'asd123') } + + before do + sign_in(user) + end + + describe "GET callback" do + it "updates access token" do + token = "asdasd12345" + Gitlab::GitlabImport::Client.any_instance.stub_chain(:client, :auth_code, :get_token, :token).and_return(token) + Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "gitlab") + + get :callback + + user.reload.gitlab_access_token.should == token + controller.should redirect_to(status_import_gitlab_url) + end + end + + describe "GET status" do + before do + @repo = OpenStruct.new(path: 'vim', path_with_namespace: 'asd/vim') + end + + it "assigns variables" do + @project = create(:project, import_type: 'gitlab', creator_id: user.id) + controller.stub_chain(:client, :projects).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([@repo]) + end + + it "does not show already added project" do + @project = create(:project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim') + controller.stub_chain(:client, :projects).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([]) + end + end + + describe "POST create" do + before do + @repo = { + path: 'vim', + path_with_namespace: 'asd/vim', + owner: {name: "john"}, + namespace: {path: "john"} + }.with_indifferent_access + end + + it "takes already existing namespace" do + namespace = create(:namespace, name: "john", owner: user) + Gitlab::GitlabImport::ProjectCreator.should_receive(:new).with(@repo, namespace, user). + and_return(double(execute: true)) + controller.stub_chain(:client, :project).and_return(@repo) + + post :create, format: :js + end + end +end diff --git a/spec/lib/gitlab/gitlab_import/project_creator.rb b/spec/lib/gitlab/gitlab_import/project_creator.rb new file mode 100644 index 0000000000000000000000000000000000000000..51f3534ed600fa56afbbe3b81f8349d85eadce97 --- /dev/null +++ b/spec/lib/gitlab/gitlab_import/project_creator.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Gitlab::GitlabImport::ProjectCreator do + let(:user) { create(:user, gitlab_access_token: "asdffg") } + let(:repo) {{ + name: 'vim', + path: 'vim', + visibility_level: Gitlab::VisibilityLevel::PRIVATE, + path_with_namespace: 'asd/vim', + http_url_to_repo: "https://gitlab.com/asd/vim.git", + owner: {name: "john"}}.with_indifferent_access + } + let(:namespace){ create(:namespace) } + + it 'creates project' do + Project.any_instance.stub(:add_import_job) + + project_creator = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, user) + project_creator.execute + project = Project.last + + project.import_url.should == "https://oauth2:asdffg@gitlab.com/asd/vim.git" + project.visibility_level.should == Gitlab::VisibilityLevel::PRIVATE + end +end