PageRenderTime 33ms CodeModel.GetById 1ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/gitlab/bitbucket_import/importer.rb

https://bitbucket.org/terrchen/gitlab-ce
Ruby | 258 lines | 205 code | 45 blank | 8 comment | 9 complexity | 680ccf050eb636382a66d17dcba72770 MD5 | raw file
Possible License(s): Apache-2.0, CC0-1.0
  1. module Gitlab
  2. module BitbucketImport
  3. class Importer
  4. include Gitlab::ShellAdapter
  5. LABELS = [{ title: 'bug', color: '#FF0000' },
  6. { title: 'enhancement', color: '#428BCA' },
  7. { title: 'proposal', color: '#69D100' },
  8. { title: 'task', color: '#7F8C8D' }].freeze
  9. attr_reader :project, :client, :errors, :users
  10. def initialize(project)
  11. @project = project
  12. @client = Bitbucket::Client.new(project.import_data.credentials)
  13. @formatter = Gitlab::ImportFormatter.new
  14. @labels = {}
  15. @errors = []
  16. @users = {}
  17. end
  18. def execute
  19. import_wiki
  20. import_issues
  21. import_pull_requests
  22. handle_errors
  23. true
  24. end
  25. private
  26. def handle_errors
  27. return unless errors.any?
  28. project.update_column(:import_error, {
  29. message: 'The remote data could not be fully imported.',
  30. errors: errors
  31. }.to_json)
  32. end
  33. def gitlab_user_id(project, username)
  34. find_user_id(username) || project.creator_id
  35. end
  36. def find_user_id(username)
  37. return nil unless username
  38. return users[username] if users.key?(username)
  39. users[username] = User.select(:id)
  40. .joins(:identities)
  41. .find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", username)
  42. .try(:id)
  43. end
  44. def repo
  45. @repo ||= client.repo(project.import_source)
  46. end
  47. def import_wiki
  48. return if project.wiki.repository_exists?
  49. disk_path = project.wiki.disk_path
  50. import_url = project.import_url.sub(/\.git\z/, ".git/wiki")
  51. gitlab_shell.import_repository(project.repository_storage, disk_path, import_url)
  52. rescue StandardError => e
  53. errors << { type: :wiki, errors: e.message }
  54. end
  55. def import_issues
  56. return unless repo.issues_enabled?
  57. create_labels
  58. client.issues(repo).each do |issue|
  59. begin
  60. description = ''
  61. description += @formatter.author_line(issue.author) unless find_user_id(issue.author)
  62. description += issue.description
  63. label_name = issue.kind
  64. milestone = issue.milestone ? project.milestones.find_or_create_by(title: issue.milestone) : nil
  65. gitlab_issue = project.issues.create!(
  66. iid: issue.iid,
  67. title: issue.title,
  68. description: description,
  69. state: issue.state,
  70. author_id: gitlab_user_id(project, issue.author),
  71. milestone: milestone,
  72. created_at: issue.created_at,
  73. updated_at: issue.updated_at
  74. )
  75. gitlab_issue.labels << @labels[label_name]
  76. import_issue_comments(issue, gitlab_issue) if gitlab_issue.persisted?
  77. rescue StandardError => e
  78. errors << { type: :issue, iid: issue.iid, errors: e.message }
  79. end
  80. end
  81. end
  82. def import_issue_comments(issue, gitlab_issue)
  83. client.issue_comments(repo, issue.iid).each do |comment|
  84. # The note can be blank for issue service messages like "Changed title: ..."
  85. # We would like to import those comments as well but there is no any
  86. # specific parameter that would allow to process them, it's just an empty comment.
  87. # To prevent our importer from just crashing or from creating useless empty comments
  88. # we do this check.
  89. next unless comment.note.present?
  90. note = ''
  91. note += @formatter.author_line(comment.author) unless find_user_id(comment.author)
  92. note += comment.note
  93. begin
  94. gitlab_issue.notes.create!(
  95. project: project,
  96. note: note,
  97. author_id: gitlab_user_id(project, comment.author),
  98. created_at: comment.created_at,
  99. updated_at: comment.updated_at
  100. )
  101. rescue StandardError => e
  102. errors << { type: :issue_comment, iid: issue.iid, errors: e.message }
  103. end
  104. end
  105. end
  106. def create_labels
  107. LABELS.each do |label_params|
  108. label = ::Labels::CreateService.new(label_params).execute(project: project)
  109. if label.valid?
  110. @labels[label_params[:title]] = label
  111. else
  112. raise "Failed to create label \"#{label_params[:title]}\" for project \"#{project.full_name}\""
  113. end
  114. end
  115. end
  116. def import_pull_requests
  117. pull_requests = client.pull_requests(repo)
  118. pull_requests.each do |pull_request|
  119. begin
  120. description = ''
  121. description += @formatter.author_line(pull_request.author) unless find_user_id(pull_request.author)
  122. description += pull_request.description
  123. source_branch_sha = pull_request.source_branch_sha
  124. target_branch_sha = pull_request.target_branch_sha
  125. source_branch_sha = project.repository.commit(source_branch_sha)&.sha || source_branch_sha
  126. target_branch_sha = project.repository.commit(target_branch_sha)&.sha || target_branch_sha
  127. merge_request = project.merge_requests.create!(
  128. iid: pull_request.iid,
  129. title: pull_request.title,
  130. description: description,
  131. source_project: project,
  132. source_branch: pull_request.source_branch_name,
  133. source_branch_sha: source_branch_sha,
  134. target_project: project,
  135. target_branch: pull_request.target_branch_name,
  136. target_branch_sha: target_branch_sha,
  137. state: pull_request.state,
  138. author_id: gitlab_user_id(project, pull_request.author),
  139. assignee_id: nil,
  140. created_at: pull_request.created_at,
  141. updated_at: pull_request.updated_at
  142. )
  143. import_pull_request_comments(pull_request, merge_request) if merge_request.persisted?
  144. rescue StandardError => e
  145. errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, trace: e.backtrace.join("\n"), raw_response: pull_request.raw }
  146. end
  147. end
  148. end
  149. def import_pull_request_comments(pull_request, merge_request)
  150. comments = client.pull_request_comments(repo, pull_request.iid)
  151. inline_comments, pr_comments = comments.partition(&:inline?)
  152. import_inline_comments(inline_comments, pull_request, merge_request)
  153. import_standalone_pr_comments(pr_comments, merge_request)
  154. end
  155. def import_inline_comments(inline_comments, pull_request, merge_request)
  156. line_code_map = {}
  157. children, parents = inline_comments.partition(&:has_parent?)
  158. # The Bitbucket API returns threaded replies as parent-child
  159. # relationships. We assume that the child can appear in any order in
  160. # the JSON.
  161. parents.each do |comment|
  162. line_code_map[comment.iid] = generate_line_code(comment)
  163. end
  164. children.each do |comment|
  165. line_code_map[comment.iid] = line_code_map.fetch(comment.parent_id, nil)
  166. end
  167. inline_comments.each do |comment|
  168. begin
  169. attributes = pull_request_comment_attributes(comment)
  170. attributes.merge!(
  171. position: build_position(merge_request, comment),
  172. line_code: line_code_map.fetch(comment.iid),
  173. type: 'DiffNote')
  174. merge_request.notes.create!(attributes)
  175. rescue StandardError => e
  176. errors << { type: :pull_request, iid: comment.iid, errors: e.message }
  177. end
  178. end
  179. end
  180. def build_position(merge_request, pr_comment)
  181. params = {
  182. diff_refs: merge_request.diff_refs,
  183. old_path: pr_comment.file_path,
  184. new_path: pr_comment.file_path,
  185. old_line: pr_comment.old_pos,
  186. new_line: pr_comment.new_pos
  187. }
  188. Gitlab::Diff::Position.new(params)
  189. end
  190. def import_standalone_pr_comments(pr_comments, merge_request)
  191. pr_comments.each do |comment|
  192. begin
  193. merge_request.notes.create!(pull_request_comment_attributes(comment))
  194. rescue StandardError => e
  195. errors << { type: :pull_request, iid: comment.iid, errors: e.message }
  196. end
  197. end
  198. end
  199. def generate_line_code(pr_comment)
  200. Gitlab::Git.diff_line_code(pr_comment.file_path, pr_comment.new_pos, pr_comment.old_pos)
  201. end
  202. def pull_request_comment_attributes(comment)
  203. {
  204. project: project,
  205. note: comment.note,
  206. author_id: gitlab_user_id(project, comment.author),
  207. created_at: comment.created_at,
  208. updated_at: comment.updated_at
  209. }
  210. end
  211. end
  212. end
  213. end