163 lines
5.1 KiB
Ruby
163 lines
5.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Gitlab
|
|
module ImportExport
|
|
module Project
|
|
# Given a class, it finds or creates a new object
|
|
# (initializes in the case of Label) at group or project level.
|
|
# If it does not exist in the group, it creates it at project level.
|
|
#
|
|
# Example:
|
|
# `ObjectBuilder.build(Label, label_attributes)`
|
|
# finds or initializes a label with the given attributes.
|
|
#
|
|
# It also adds some logic around Group Labels/Milestones for edge cases.
|
|
class ObjectBuilder < Base::ObjectBuilder
|
|
def initialize(klass, attributes)
|
|
super
|
|
|
|
@group = @attributes['group']
|
|
@project = @attributes['project']
|
|
end
|
|
|
|
def find
|
|
return if epic? && group.nil?
|
|
return find_diff_commit_user if diff_commit_user?
|
|
return find_diff_commit if diff_commit?
|
|
|
|
super
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :group, :project
|
|
|
|
def where_clauses
|
|
[
|
|
where_clause_base,
|
|
where_clause_for_title,
|
|
where_clause_for_klass
|
|
].compact
|
|
end
|
|
|
|
# Returns Arel clause `"{table_name}"."project_id" = {project.id}` if project is present
|
|
# For example: merge_request has :target_project_id, and we are searching by :iid
|
|
# or, if group is present:
|
|
# `"{table_name}"."project_id" = {project.id} OR "{table_name}"."group_id" = {group.id}`
|
|
def where_clause_base
|
|
[].tap do |clauses|
|
|
clauses << table[:project_id].eq(project.id) if project
|
|
clauses << table[:group_id].in(group.self_and_ancestors_ids) if group
|
|
end.reduce(:or)
|
|
end
|
|
|
|
# Returns Arel clause for a particular model or `nil`.
|
|
def where_clause_for_klass
|
|
return attrs_to_arel(attributes.slice('filename')).and(table[:issue_id].eq(nil)) if design?
|
|
|
|
attrs_to_arel(attributes.slice('iid', 'target_project_id')) if merge_request?
|
|
end
|
|
|
|
def prepare_attributes
|
|
attributes.dup.tap do |atts|
|
|
atts.delete('group') unless epic?
|
|
|
|
if label?
|
|
atts['type'] = 'ProjectLabel' # Always create project labels
|
|
elsif milestone?
|
|
if atts['group_id'] # Transform new group milestones into project ones
|
|
atts['iid'] = nil
|
|
atts.delete('group_id')
|
|
else
|
|
claim_iid
|
|
end
|
|
end
|
|
|
|
atts['importing'] = true if klass.ancestors.include?(Importable)
|
|
end
|
|
end
|
|
|
|
def find_diff_commit_user
|
|
find_or_create_diff_commit_user(@attributes['name'], @attributes['email'])
|
|
end
|
|
|
|
def find_diff_commit
|
|
row = @attributes.dup
|
|
|
|
# Diff commits come in two formats:
|
|
#
|
|
# 1. The old format where author/committer details are separate fields
|
|
# 2. The new format where author/committer details are nested objects,
|
|
# and pre-processed by `find_diff_commit_user`.
|
|
#
|
|
# The code here ensures we support both the old and new format.
|
|
aname = row.delete('author_name')
|
|
amail = row.delete('author_email')
|
|
cname = row.delete('committer_name')
|
|
cmail = row.delete('committer_email')
|
|
author = row.delete('commit_author')
|
|
committer = row.delete('committer')
|
|
|
|
row['commit_author'] = author ||
|
|
find_or_create_diff_commit_user(aname, amail)
|
|
|
|
row['committer'] = committer ||
|
|
find_or_create_diff_commit_user(cname, cmail)
|
|
|
|
MergeRequestDiffCommit.new(row)
|
|
end
|
|
|
|
def find_or_create_diff_commit_user(name, email)
|
|
find_with_cache([MergeRequest::DiffCommitUser, name, email]) do
|
|
MergeRequest::DiffCommitUser.find_or_create(name, email)
|
|
end
|
|
end
|
|
|
|
def label?
|
|
klass == Label
|
|
end
|
|
|
|
def milestone?
|
|
klass == Milestone
|
|
end
|
|
|
|
def merge_request?
|
|
klass == MergeRequest
|
|
end
|
|
|
|
def epic?
|
|
klass == Epic
|
|
end
|
|
|
|
def design?
|
|
klass == ::DesignManagement::Design
|
|
end
|
|
|
|
def diff_commit_user?
|
|
klass == MergeRequest::DiffCommitUser
|
|
end
|
|
|
|
def diff_commit?
|
|
klass == MergeRequestDiffCommit
|
|
end
|
|
|
|
# If an existing group milestone used the IID
|
|
# claim the IID back and set the group milestone to use one available
|
|
# This is necessary to fix situations like the following:
|
|
# - Importing into a user namespace project with exported group milestones
|
|
# where the IID of the Group milestone could conflict with a project one.
|
|
def claim_iid
|
|
# The milestone has to be a group milestone, as it's the only case where
|
|
# we set the IID as the maximum. The rest of them are fixed.
|
|
milestone = project.milestones.find_by(iid: attributes['iid'])
|
|
|
|
return unless milestone
|
|
|
|
milestone.iid = nil
|
|
milestone.ensure_project_iid!
|
|
milestone.save!
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|