summaryrefslogtreecommitdiff
path: root/lib/gitlab/github_import/importer/protected_branch_importer.rb
blob: 801a0840c5291c85957bbb3dc8679718698d88a8 (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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# frozen_string_literal: true

module Gitlab
  module GithubImport
    module Importer
      class ProtectedBranchImporter
        attr_reader :protected_branch, :project, :client

        # By default on GitHub, both developers and maintainers can merge
        # a PR into the protected branch
        GITHUB_DEFAULT_MERGE_ACCESS_LEVEL = Gitlab::Access::DEVELOPER

        # protected_branch - An instance of
        #   `Gitlab::GithubImport::Representation::ProtectedBranch`.
        # project - An instance of `Project`
        # client - An instance of `Gitlab::GithubImport::Client`
        def initialize(protected_branch, project, client)
          @protected_branch = protected_branch
          @project = project
          @client = client
        end

        def execute
          # The creator of the project is always allowed to create protected
          # branches, so we skip the authorization check in this service class.
          ProtectedBranches::CreateService
            .new(project, project.creator, params)
            .execute(skip_authorization: true)

          update_project_settings if default_branch?
        end

        private

        def params
          {
            name: protected_branch.id,
            push_access_levels_attributes: [{ access_level: push_access_level }],
            merge_access_levels_attributes: [{ access_level: merge_access_level }],
            allow_force_push: allow_force_push?,
            code_owner_approval_required: code_owner_approval_required?
          }
        end

        def allow_force_push?
          return false unless protected_branch.allow_force_pushes

          if protected_on_gitlab?
            ProtectedBranch.allow_force_push?(project, protected_branch.id)
          elsif default_branch?
            !default_branch_protection.any?
          else
            true
          end
        end

        def code_owner_approval_required?
          return false unless project.licensed_feature_available?(:code_owner_approval_required)

          return protected_branch.require_code_owner_reviews unless protected_on_gitlab?

          # Gets the strictest require_code_owner rule between GitHub and GitLab
          protected_branch.require_code_owner_reviews ||
            ProtectedBranch.branch_requires_code_owner_approval?(
              project,
              protected_branch.id
            )
        end

        def default_branch?
          protected_branch.id == project.default_branch
        end

        def update_project_settings
          update_setting_for_only_allow_merge_if_all_discussions_are_resolved
          update_project_push_rule
        end

        def update_setting_for_only_allow_merge_if_all_discussions_are_resolved
          return unless protected_branch.required_conversation_resolution

          project.update(only_allow_merge_if_all_discussions_are_resolved: true)
        end

        def update_project_push_rule
          return unless project.licensed_feature_available?(:push_rules)
          return unless protected_branch.required_signatures

          push_rule = project.push_rule || project.build_push_rule
          push_rule.update!(reject_unsigned_commits: true)
          project.project_setting.update!(push_rule_id: push_rule.id)
        end

        def push_access_level
          if protected_branch.required_pull_request_reviews
            Gitlab::Access::NO_ACCESS
          else
            gitlab_access_level_for(:push)
          end
        end

        # Gets the strictest merge_access_level between GitHub and GitLab
        def merge_access_level
          gitlab_access = gitlab_access_level_for(:merge)

          return gitlab_access if gitlab_access == Gitlab::Access::NO_ACCESS

          [gitlab_access, GITHUB_DEFAULT_MERGE_ACCESS_LEVEL].max
        end

        # action - :push/:merge
        def gitlab_access_level_for(action)
          if default_branch?
            action == :push ? default_branch_push_access_level : default_branch_merge_access_level
          elsif protected_on_gitlab?
            non_default_branch_access_level_for(action)
          else
            gitlab_default_access_level_for(action)
          end
        end

        def default_branch_push_access_level
          if default_branch_protection.developer_can_push?
            Gitlab::Access::DEVELOPER
          else
            gitlab_default_access_level_for(:push)
          end
        end

        def default_branch_merge_access_level
          if default_branch_protection.developer_can_merge?
            Gitlab::Access::DEVELOPER
          else
            gitlab_default_access_level_for(:merge)
          end
        end

        def default_branch_protection
          Gitlab::Access::BranchProtection.new(project.namespace.default_branch_protection)
        end

        def protected_on_gitlab?
          ProtectedBranch.protected?(project, protected_branch.id)
        end

        def non_default_branch_access_level_for(action)
          access_level = ProtectedBranch.access_levels_for_ref(protected_branch.id, action: action)
            .find(&:role?)&.access_level

          access_level || gitlab_default_access_level_for(action)
        end

        def gitlab_default_access_level_for(action)
          return ProtectedBranch::PushAccessLevel::GITLAB_DEFAULT_ACCESS_LEVEL if action == :push

          ProtectedBranch::MergeAccessLevel::GITLAB_DEFAULT_ACCESS_LEVEL
        end
      end
    end
  end
end