Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/bitbucket_import')
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb6
-rw-r--r--lib/gitlab/bitbucket_import/importers/pull_request_importer.rb88
-rw-r--r--lib/gitlab/bitbucket_import/importers/pull_requests_importer.rb47
-rw-r--r--lib/gitlab/bitbucket_import/importers/repository_importer.rb78
-rw-r--r--lib/gitlab/bitbucket_import/loggable.rb41
-rw-r--r--lib/gitlab/bitbucket_import/logger.rb11
-rw-r--r--lib/gitlab/bitbucket_import/parallel_importer.rb37
-rw-r--r--lib/gitlab/bitbucket_import/parallel_scheduling.rb93
-rw-r--r--lib/gitlab/bitbucket_import/user_finder.rb46
9 files changed, 446 insertions, 1 deletions
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index e785ce558db..7f228c19b6b 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -210,7 +210,11 @@ module Gitlab
source_branch_sha = pull_request.source_branch_sha
target_branch_sha = pull_request.target_branch_sha
- source_branch_sha = project.repository.commit(source_branch_sha)&.sha || source_branch_sha
+
+ source_sha_from_commit_sha = project.repository.commit(source_branch_sha)&.sha
+ source_sha_from_merge_sha = project.repository.commit(pull_request.merge_commit_sha)&.sha
+
+ source_branch_sha = source_sha_from_commit_sha || source_sha_from_merge_sha || source_branch_sha
target_branch_sha = project.repository.commit(target_branch_sha)&.sha || target_branch_sha
merge_request = project.merge_requests.create!(
diff --git a/lib/gitlab/bitbucket_import/importers/pull_request_importer.rb b/lib/gitlab/bitbucket_import/importers/pull_request_importer.rb
new file mode 100644
index 00000000000..d76e08e1039
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/importers/pull_request_importer.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BitbucketImport
+ module Importers
+ class PullRequestImporter
+ include Loggable
+
+ def initialize(project, hash)
+ @project = project
+ @formatter = Gitlab::ImportFormatter.new
+ @user_finder = UserFinder.new(project)
+ @object = hash.with_indifferent_access
+ end
+
+ def execute
+ log_info(import_stage: 'import_pull_request', message: 'starting', iid: object[:iid])
+
+ description = ''
+ description += author_line
+ description += object[:description] if object[:description]
+
+ attributes = {
+ iid: object[:iid],
+ title: object[:title],
+ description: description,
+ source_project_id: project.id,
+ source_branch: Gitlab::Git.ref_name(object[:source_branch_name]),
+ source_branch_sha: source_branch_sha,
+ target_project_id: project.id,
+ target_branch: Gitlab::Git.ref_name(object[:target_branch_name]),
+ target_branch_sha: object[:target_branch_sha],
+ state_id: MergeRequest.available_states[object[:state]],
+ author_id: author_id,
+ created_at: object[:created_at],
+ updated_at: object[:updated_at]
+ }
+
+ creator = Gitlab::Import::MergeRequestCreator.new(project)
+
+ merge_request = creator.execute(attributes)
+
+ if merge_request
+ merge_request.assignee_ids = [author_id]
+ merge_request.reviewer_ids = reviewers
+ merge_request.save!
+ end
+
+ log_info(import_stage: 'import_pull_request', message: 'finished', iid: object[:iid])
+ rescue StandardError => e
+ Gitlab::Import::ImportFailureService.track(project_id: project.id, exception: e)
+ end
+
+ private
+
+ attr_reader :object, :project, :formatter, :user_finder
+
+ def author_line
+ return '' if find_user_id
+
+ formatter.author_line(object[:author])
+ end
+
+ def find_user_id
+ user_finder.find_user_id(object[:author])
+ end
+
+ def author_id
+ user_finder.gitlab_user_id(project, object[:author])
+ end
+
+ def reviewers
+ return [] unless object[:reviewers].present?
+
+ object[:reviewers].filter_map do |reviewer|
+ user_finder.find_user_id(reviewer)
+ end
+ end
+
+ def source_branch_sha
+ project.repository.commit(object[:source_branch_sha])&.sha ||
+ project.repository.commit(object[:merge_commit_sha])&.sha ||
+ object[:source_branch_sha]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/importers/pull_requests_importer.rb b/lib/gitlab/bitbucket_import/importers/pull_requests_importer.rb
new file mode 100644
index 00000000000..1c7ce7f2f3a
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/importers/pull_requests_importer.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BitbucketImport
+ module Importers
+ class PullRequestsImporter
+ include ParallelScheduling
+
+ def execute
+ log_info(import_stage: 'import_pull_requests', message: 'importing pull requests')
+
+ pull_requests = client.pull_requests(project.import_source)
+
+ pull_requests.each do |pull_request|
+ job_waiter.jobs_remaining += 1
+
+ next if already_enqueued?(pull_request)
+
+ job_delay = calculate_job_delay(job_waiter.jobs_remaining)
+
+ sidekiq_worker_class.perform_in(job_delay, project.id, pull_request.to_hash, job_waiter.key)
+
+ mark_as_enqueued(pull_request)
+ end
+
+ job_waiter
+ rescue StandardError => e
+ track_import_failure!(project, exception: e)
+ end
+
+ private
+
+ def sidekiq_worker_class
+ ImportPullRequestWorker
+ end
+
+ def collection_method
+ :pull_requests
+ end
+
+ def id_for_already_enqueued_cache(object)
+ object.iid
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/importers/repository_importer.rb b/lib/gitlab/bitbucket_import/importers/repository_importer.rb
new file mode 100644
index 00000000000..7b0362b6ec6
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/importers/repository_importer.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BitbucketImport
+ module Importers
+ class RepositoryImporter
+ include Loggable
+
+ def initialize(project)
+ @project = project
+ end
+
+ def execute
+ log_info(import_stage: 'import_repository', message: 'starting import')
+
+ if project.empty_repo?
+ project.repository.import_repository(project.import_url)
+ project.repository.fetch_as_mirror(project.import_url, refmap: refmap)
+
+ validate_repository_size!
+
+ update_clone_time
+ end
+
+ import_wiki
+
+ log_info(import_stage: 'import_repository', message: 'finished import')
+
+ true
+ rescue ::Gitlab::Git::CommandError => e
+ Gitlab::ErrorTracking.log_exception(
+ e, import_stage: 'import_repository', message: 'failed import', error: e.message
+ )
+
+ # Expire cache to prevent scenarios such as:
+ # 1. First import failed, but the repo was imported successfully, so +exists?+ returns true
+ # 2. Retried import, repo is broken or not imported but +exists?+ still returns true
+ project.repository.expire_content_cache if project.repository_exists?
+
+ raise
+ end
+
+ private
+
+ attr_reader :project
+
+ def refmap
+ # We omit :heads and :tags since these are fetched in the import_repository
+ ['+refs/pull-requests/*/to:refs/merge-requests/*/head']
+ end
+
+ def import_wiki
+ return if project.wiki.repository_exists?
+
+ project.wiki.repository.import_repository(wiki.import_url)
+ rescue StandardError => e
+ Gitlab::ErrorTracking.log_exception(
+ e, import_stage: 'import_repository', message: 'failed to import wiki', error: e.message
+ )
+ end
+
+ def wiki
+ WikiFormatter.new(project)
+ end
+
+ def update_clone_time
+ project.touch(:last_repository_updated_at)
+ end
+
+ def validate_repository_size!
+ # Defined in EE
+ end
+ end
+ end
+ end
+end
+
+Gitlab::BitbucketImport::Importers::RepositoryImporter.prepend_mod
diff --git a/lib/gitlab/bitbucket_import/loggable.rb b/lib/gitlab/bitbucket_import/loggable.rb
new file mode 100644
index 00000000000..eda3cc96d4d
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/loggable.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BitbucketImport
+ module Loggable
+ def log_debug(messages)
+ logger.debug(log_data(messages))
+ end
+
+ def log_info(messages)
+ logger.info(log_data(messages))
+ end
+
+ def log_warn(messages)
+ logger.warn(log_data(messages))
+ end
+
+ def log_error(messages)
+ logger.error(log_data(messages))
+ end
+
+ private
+
+ def logger
+ Gitlab::BitbucketImport::Logger
+ end
+
+ def log_data(messages)
+ messages.merge(log_base_data)
+ end
+
+ def log_base_data
+ {
+ class: self.class.name,
+ project_id: project.id,
+ project_path: project.full_path
+ }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/logger.rb b/lib/gitlab/bitbucket_import/logger.rb
new file mode 100644
index 00000000000..1f4d175f8a3
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/logger.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BitbucketImport
+ class Logger < ::Gitlab::Import::Logger
+ def default_attributes
+ super.merge(import_type: :bitbucket)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/parallel_importer.rb b/lib/gitlab/bitbucket_import/parallel_importer.rb
new file mode 100644
index 00000000000..1563261fa4a
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/parallel_importer.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BitbucketImport
+ class ParallelImporter
+ def self.async?
+ true
+ end
+
+ def self.imports_repository?
+ true
+ end
+
+ def self.track_start_import(project)
+ Gitlab::Import::Metrics.new(:bitbucket_importer, project).track_start_import
+ end
+
+ def initialize(project)
+ @project = project
+ end
+
+ def execute
+ Gitlab::Import::SetAsyncJid.set_jid(project.import_state)
+
+ Stage::ImportRepositoryWorker
+ .with_status
+ .perform_async(project.id)
+
+ true
+ end
+
+ private
+
+ attr_reader :project
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/parallel_scheduling.rb b/lib/gitlab/bitbucket_import/parallel_scheduling.rb
new file mode 100644
index 00000000000..f4df9a35526
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/parallel_scheduling.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BitbucketImport
+ module ParallelScheduling
+ include Loggable
+
+ attr_reader :project, :already_enqueued_cache_key, :job_waiter_cache_key
+
+ # The base cache key to use for tracking already enqueued objects.
+ ALREADY_ENQUEUED_CACHE_KEY =
+ 'bitbucket-importer/already-enqueued/%{project}/%{collection}'
+
+ # The base cache key to use for storing job waiter key
+ JOB_WAITER_CACHE_KEY =
+ 'bitbucket-importer/job-waiter/%{project}/%{collection}'
+
+ BATCH_SIZE = 100
+
+ # project - An instance of `Project`.
+ def initialize(project)
+ @project = project
+
+ @already_enqueued_cache_key =
+ format(ALREADY_ENQUEUED_CACHE_KEY, project: project.id, collection: collection_method)
+ @job_waiter_cache_key =
+ format(JOB_WAITER_CACHE_KEY, project: project.id, collection: collection_method)
+ end
+
+ private
+
+ def client
+ @client ||= Bitbucket::Client.new(project.import_data.credentials)
+ end
+
+ # Returns the ID to use for the cache used for checking if an object has
+ # already been enqueued or not.
+ #
+ # object - The object we may want to import.
+ def id_for_already_enqueued_cache(object)
+ raise NotImplementedError
+ end
+
+ # The Sidekiq worker class used for scheduling the importing of objects in
+ # parallel.
+ def sidekiq_worker_class
+ raise NotImplementedError
+ end
+
+ # The name of the method to call to retrieve the data to import.
+ def collection_method
+ raise NotImplementedError
+ end
+
+ def job_waiter
+ @job_waiter ||= begin
+ key = Gitlab::Cache::Import::Caching.read(job_waiter_cache_key)
+ key ||= Gitlab::Cache::Import::Caching.write(job_waiter_cache_key, JobWaiter.generate_key)
+
+ JobWaiter.new(0, key)
+ end
+ end
+
+ def already_enqueued?(object)
+ id = id_for_already_enqueued_cache(object)
+
+ Gitlab::Cache::Import::Caching.set_includes?(already_enqueued_cache_key, id)
+ end
+
+ # Marks the given object as "already enqueued".
+ def mark_as_enqueued(object)
+ id = id_for_already_enqueued_cache(object)
+
+ Gitlab::Cache::Import::Caching.set_add(already_enqueued_cache_key, id)
+ end
+
+ def calculate_job_delay(job_index)
+ multiplier = (job_index / BATCH_SIZE)
+
+ (multiplier * 1.minute) + 1.second
+ end
+
+ def track_import_failure!(project, exception:, **args)
+ Gitlab::Import::ImportFailureService.track(
+ project_id: project.id,
+ error_source: self.class.name,
+ exception: exception,
+ **args
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/user_finder.rb b/lib/gitlab/bitbucket_import/user_finder.rb
new file mode 100644
index 00000000000..70ed94351d5
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/user_finder.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BitbucketImport
+ class UserFinder
+ USER_ID_FOR_AUTHOR_CACHE_KEY = 'bitbucket-importer/user-finder/%{project_id}/%{author}'
+ CACHE_USER_ID_NOT_FOUND = -1
+
+ attr_reader :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def find_user_id(author)
+ return unless author
+
+ cache_key = build_cache_key(author)
+ cached_id = cache.read_integer(cache_key)
+
+ return if cached_id == CACHE_USER_ID_NOT_FOUND
+ return cached_id if cached_id
+
+ id = User.by_provider_and_extern_uid(:bitbucket, author).select(:id).first&.id
+
+ cache.write(cache_key, id || CACHE_USER_ID_NOT_FOUND)
+
+ id
+ end
+
+ def gitlab_user_id(project, username)
+ find_user_id(username) || project.creator_id
+ end
+
+ private
+
+ def cache
+ Cache::Import::Caching
+ end
+
+ def build_cache_key(author)
+ format(USER_ID_FOR_AUTHOR_CACHE_KEY, project_id: project.id, author: author)
+ end
+ end
+ end
+end