summaryrefslogtreecommitdiff
path: root/lib/gitlab/import_export/group_project_object_builder.rb
blob: 1c62591ed5a261b8db6fcfcfeec9378d1a6bc7f4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# frozen_string_literal: true

module Gitlab
  module ImportExport
    # 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:
    #   `GroupProjectObjectBuilder.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 GroupProjectObjectBuilder
      def self.build(*args)
        Project.transaction do
          new(*args).find
        end
      end

      def initialize(klass, attributes)
        @klass = klass < Label ? Label : klass
        @attributes = attributes
        @group = @attributes['group']
        @project = @attributes['project']
      end

      def find
        find_object || @klass.create(project_attributes)
      end

      private

      def find_object
        @klass.where(where_clause).first
      end

      def where_clause
        @attributes.slice('title').map do |key, value|
          scope_clause = table[:project_id].eq(@project.id)
          scope_clause = scope_clause.or(table[:group_id].eq(@group.id)) if @group

          table[key].eq(value).and(scope_clause)
        end.reduce(:or)
      end

      def table
        @table ||= @klass.arel_table
      end

      def project_attributes
        @attributes.except('group').tap do |atts|
          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
        end
      end

      def label?
        @klass == Label
      end

      def milestone?
        @klass == Milestone
      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