debian-mirror-gitlab/lib/gitlab/bitbucket_import/importer.rb

286 lines
9.5 KiB
Ruby
Raw Normal View History

2018-12-13 13:39:08 +05:30
# frozen_string_literal: true
2015-04-26 12:48:37 +05:30
module Gitlab
module BitbucketImport
class Importer
2017-08-17 22:00:37 +05:30
include Gitlab::ShellAdapter
LABELS = [{ title: 'bug', color: '#FF0000' },
{ title: 'enhancement', color: '#428BCA' },
{ title: 'proposal', color: '#69D100' },
{ title: 'task', color: '#7F8C8D' }].freeze
attr_reader :project, :client, :errors, :users
2019-09-30 21:07:59 +05:30
attr_accessor :logger
2015-04-26 12:48:37 +05:30
def initialize(project)
@project = project
2017-08-17 22:00:37 +05:30
@client = Bitbucket::Client.new(project.import_data.credentials)
2015-04-26 12:48:37 +05:30
@formatter = Gitlab::ImportFormatter.new
2017-08-17 22:00:37 +05:30
@labels = {}
@errors = []
@users = {}
2019-09-30 21:07:59 +05:30
@logger = Gitlab::Import::Logger.build
2015-04-26 12:48:37 +05:30
end
def execute
2017-08-17 22:00:37 +05:30
import_wiki
import_issues
import_pull_requests
handle_errors
2015-04-26 12:48:37 +05:30
2016-04-02 18:10:28 +05:30
true
end
2015-04-26 12:48:37 +05:30
2016-04-02 18:10:28 +05:30
private
2017-08-17 22:00:37 +05:30
def handle_errors
return unless errors.any?
2019-02-15 15:39:39 +05:30
project.import_state.update_column(:last_error, {
2017-08-17 22:00:37 +05:30
message: 'The remote data could not be fully imported.',
errors: errors
}.to_json)
end
2019-09-30 21:07:59 +05:30
def store_pull_request_error(pull_request, ex)
backtrace = Gitlab::Profiler.clean_backtrace(ex.backtrace)
error = { type: :pull_request, iid: pull_request.iid, errors: ex.message, trace: backtrace, raw_response: pull_request.raw }
log_error(error)
# Omit the details from the database to avoid blowing up usage in the error column
error.delete(:trace)
error.delete(:raw_response)
errors << error
end
2017-08-17 22:00:37 +05:30
def gitlab_user_id(project, username)
find_user_id(username) || project.creator_id
2016-04-02 18:10:28 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ActiveRecord
2017-08-17 22:00:37 +05:30
def find_user_id(username)
2019-07-07 11:18:12 +05:30
return unless username
2017-08-17 22:00:37 +05:30
return users[username] if users.key?(username)
users[username] = User.select(:id)
.joins(:identities)
.find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", username)
.try(:id)
2016-04-02 18:10:28 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ActiveRecord
2016-04-02 18:10:28 +05:30
2017-08-17 22:00:37 +05:30
def repo
@repo ||= client.repo(project.import_source)
2016-04-02 18:10:28 +05:30
end
2017-08-17 22:00:37 +05:30
def import_wiki
return if project.wiki.repository_exists?
2015-09-25 12:07:36 +05:30
2019-03-02 22:35:43 +05:30
wiki = WikiFormatter.new(project)
gitlab_shell.import_wiki_repository(project, wiki)
2017-08-17 22:00:37 +05:30
rescue StandardError => e
errors << { type: :wiki, errors: e.message }
end
2015-09-25 12:07:36 +05:30
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ActiveRecord
2017-08-17 22:00:37 +05:30
def import_issues
return unless repo.issues_enabled?
create_labels
client.issues(repo).each do |issue|
2019-07-07 11:18:12 +05:30
description = ''
description += @formatter.author_line(issue.author) unless find_user_id(issue.author)
description += issue.description
label_name = issue.kind
milestone = issue.milestone ? project.milestones.find_or_create_by(title: issue.milestone) : nil
gitlab_issue = project.issues.create!(
iid: issue.iid,
title: issue.title,
description: description,
state: issue.state,
author_id: gitlab_user_id(project, issue.author),
milestone: milestone,
created_at: issue.created_at,
updated_at: issue.updated_at
)
gitlab_issue.labels << @labels[label_name]
import_issue_comments(issue, gitlab_issue) if gitlab_issue.persisted?
rescue StandardError => e
errors << { type: :issue, iid: issue.iid, errors: e.message }
2017-08-17 22:00:37 +05:30
end
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ActiveRecord
2015-09-25 12:07:36 +05:30
2017-08-17 22:00:37 +05:30
def import_issue_comments(issue, gitlab_issue)
client.issue_comments(repo, issue.iid).each do |comment|
# The note can be blank for issue service messages like "Changed title: ..."
# We would like to import those comments as well but there is no any
# specific parameter that would allow to process them, it's just an empty comment.
# To prevent our importer from just crashing or from creating useless empty comments
# we do this check.
next unless comment.note.present?
note = ''
note += @formatter.author_line(comment.author) unless find_user_id(comment.author)
note += comment.note
begin
gitlab_issue.notes.create!(
project: project,
note: note,
author_id: gitlab_user_id(project, comment.author),
created_at: comment.created_at,
updated_at: comment.updated_at
)
rescue StandardError => e
errors << { type: :issue_comment, iid: issue.iid, errors: e.message }
end
end
end
2015-09-25 12:07:36 +05:30
2017-08-17 22:00:37 +05:30
def create_labels
LABELS.each do |label_params|
2019-09-04 21:01:54 +05:30
label = ::Labels::FindOrCreateService.new(nil, project, label_params).execute(skip_authorization: true)
2017-08-17 22:00:37 +05:30
if label.valid?
@labels[label_params[:title]] = label
else
2018-03-27 19:54:05 +05:30
raise "Failed to create label \"#{label_params[:title]}\" for project \"#{project.full_name}\""
2017-08-17 22:00:37 +05:30
end
end
end
2015-09-25 12:07:36 +05:30
2017-08-17 22:00:37 +05:30
def import_pull_requests
pull_requests = client.pull_requests(repo)
pull_requests.each do |pull_request|
2019-07-07 11:18:12 +05:30
description = ''
description += @formatter.author_line(pull_request.author) unless find_user_id(pull_request.author)
description += pull_request.description
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
target_branch_sha = project.repository.commit(target_branch_sha)&.sha || target_branch_sha
merge_request = project.merge_requests.create!(
iid: pull_request.iid,
title: pull_request.title,
description: description,
source_project: project,
source_branch: pull_request.source_branch_name,
source_branch_sha: source_branch_sha,
target_project: project,
target_branch: pull_request.target_branch_name,
target_branch_sha: target_branch_sha,
state: pull_request.state,
author_id: gitlab_user_id(project, pull_request.author),
assignee_id: nil,
created_at: pull_request.created_at,
updated_at: pull_request.updated_at
)
import_pull_request_comments(pull_request, merge_request) if merge_request.persisted?
rescue StandardError => e
2019-09-30 21:07:59 +05:30
store_pull_request_error(pull_request, e)
2017-08-17 22:00:37 +05:30
end
end
def import_pull_request_comments(pull_request, merge_request)
comments = client.pull_request_comments(repo, pull_request.iid)
inline_comments, pr_comments = comments.partition(&:inline?)
import_inline_comments(inline_comments, pull_request, merge_request)
import_standalone_pr_comments(pr_comments, merge_request)
end
2015-04-26 12:48:37 +05:30
2017-08-17 22:00:37 +05:30
def import_inline_comments(inline_comments, pull_request, merge_request)
2018-11-20 20:47:30 +05:30
position_map = {}
discussion_map = {}
2015-09-25 12:07:36 +05:30
2017-08-17 22:00:37 +05:30
children, parents = inline_comments.partition(&:has_parent?)
2015-09-25 12:07:36 +05:30
2017-08-17 22:00:37 +05:30
# The Bitbucket API returns threaded replies as parent-child
# relationships. We assume that the child can appear in any order in
# the JSON.
parents.each do |comment|
2018-11-20 20:47:30 +05:30
position_map[comment.iid] = build_position(merge_request, comment)
2017-08-17 22:00:37 +05:30
end
children.each do |comment|
2018-11-20 20:47:30 +05:30
position_map[comment.iid] = position_map.fetch(comment.parent_id, nil)
2017-08-17 22:00:37 +05:30
end
inline_comments.each do |comment|
2019-07-07 11:18:12 +05:30
attributes = pull_request_comment_attributes(comment)
attributes[:discussion_id] = discussion_map[comment.parent_id] if comment.has_parent?
2018-11-20 20:47:30 +05:30
2019-07-07 11:18:12 +05:30
attributes.merge!(
position: position_map[comment.iid],
type: 'DiffNote')
2017-08-17 22:00:37 +05:30
2019-07-07 11:18:12 +05:30
note = merge_request.notes.create!(attributes)
2018-11-20 20:47:30 +05:30
2019-07-07 11:18:12 +05:30
# We can't store a discussion ID until a note is created, so if
# replies are created before the parent the discussion ID won't be
# linked properly.
discussion_map[comment.iid] = note.discussion_id
rescue StandardError => e
errors << { type: :pull_request, iid: comment.iid, errors: e.message }
2017-08-17 22:00:37 +05:30
end
end
def build_position(merge_request, pr_comment)
params = {
diff_refs: merge_request.diff_refs,
old_path: pr_comment.file_path,
new_path: pr_comment.file_path,
old_line: pr_comment.old_pos,
new_line: pr_comment.new_pos
}
2015-04-26 12:48:37 +05:30
2017-08-17 22:00:37 +05:30
Gitlab::Diff::Position.new(params)
end
def import_standalone_pr_comments(pr_comments, merge_request)
pr_comments.each do |comment|
2019-07-07 11:18:12 +05:30
merge_request.notes.create!(pull_request_comment_attributes(comment))
rescue StandardError => e
errors << { type: :pull_request, iid: comment.iid, errors: e.message }
2015-04-26 12:48:37 +05:30
end
2017-08-17 22:00:37 +05:30
end
def pull_request_comment_attributes(comment)
{
project: project,
note: comment.note,
author_id: gitlab_user_id(project, comment.author),
created_at: comment.created_at,
updated_at: comment.updated_at
}
2015-04-26 12:48:37 +05:30
end
2019-09-30 21:07:59 +05:30
def log_error(details)
logger.error(log_base_data.merge(details))
end
def log_base_data
{
class: self.class.name,
project_id: project.id,
project_path: project.full_path
}
end
2015-04-26 12:48:37 +05:30
end
end
end