summaryrefslogtreecommitdiff
path: root/lib/api/branches.rb
blob: 5ae1a80a7fd3e159fdcd0506d2a3b77f2cc2842b (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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# frozen_string_literal: true

require 'mime/types'

module API
  class Branches < ::API::Base
    include PaginationParams

    BRANCH_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX)

    after_validation { content_type "application/json" }

    feature_category :source_code_management

    before do
      require_repository_enabled!
      authorize! :read_code, user_project
    end

    rescue_from Gitlab::Git::Repository::NoRepository do
      not_found!
    end

    helpers do
      params :filter_params do
        optional :search, type: String, desc: 'Return list of branches matching the search criteria'
        optional :regex, type: String, desc: 'Return list of branches matching the regex'
        optional :sort, type: String, desc: 'Return list of branches sorted by the given field', values: %w[name_asc updated_asc updated_desc]
      end
    end

    params do
      requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
    end
    resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
      desc 'Get a project repository branches' do
        success Entities::Branch
        success code: 200, model: Entities::Branch
        failure [{ code: 404, message: '404 Project Not Found' }]
        tags %w[branches]
        is_array true
      end
      params do
        use :pagination
        use :filter_params

        optional :page_token, type: String, desc: 'Name of branch to start the pagination from'
      end
      get ':id/repository/branches', urgency: :low do
        cache_action([user_project, :branches, current_user, declared_params], expires_in: 30.seconds) do
          user_project.preload_protected_branches

          repository = user_project.repository

          branches_finder = BranchesFinder.new(repository, declared_params(include_missing: false))
          branches = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(branches_finder)

          merged_branch_names = repository.merged_branch_names(branches.map(&:name))

          present_cached(
            branches,
            with: Entities::Branch,
            current_user: current_user,
            project: user_project,
            merged_branch_names: merged_branch_names,
            expires_in: 60.minutes,
            cache_context: -> (branch) { [current_user&.cache_key, merged_branch_names.include?(branch.name)] }
          )
        end
      end

      resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
        params do
          requires :branch, type: String, desc: 'The name of the branch'
        end
        desc 'Check if a branch exists' do
          success [{ code: 204, message: 'No Content' }]
          failure [{ code: 404, message: 'Not Found' }]
          tags %w[branches]
        end
        head do
          user_project.repository.branch_exists?(params[:branch]) ? no_content! : not_found!
        end
        desc 'Get a single repository branch' do
          success Entities::Branch
          success code: 200, model: Entities::Branch
          failure [{ code: 404, message: 'Branch Not Found' }, { code: 404, message: 'Project Not Found' }]
          tags %w[branches]
        end
        get '/', urgency: :low do
          branch = find_branch!(params[:branch])

          present branch, with: Entities::Branch, current_user: current_user, project: user_project
        end
      end

      # Note: This API will be deprecated in favor of the protected branches API.
      # Note: The internal data model moved from `developers_can_{merge,push}` to `allowed_to_{merge,push}`
      # in `gitlab-org/gitlab-ce!5081`. The API interface has not been changed (to maintain compatibility),
      # but it works with the changed data model to infer `developers_can_merge` and `developers_can_push`.
      desc 'Protect a single branch' do
        success Entities::Branch
        success code: 200, model: Entities::Branch
        failure [{ code: 404, message: '404 Branch Not Found' }]
        tags %w[branches]
      end
      params do
        requires :branch, type: String, desc: 'The name of the branch', allow_blank: false
        optional :developers_can_push, type: Boolean, desc: 'Flag if developers can push to that branch'
        optional :developers_can_merge, type: Boolean, desc: 'Flag if developers can merge to that branch'
      end
      # rubocop: disable CodeReuse/ActiveRecord
      put ':id/repository/branches/:branch/protect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
        authorize_admin_project

        branch = find_branch!(params[:branch])

        protected_branch = user_project.protected_branches.find_by(name: branch.name)

        protected_branch_params = {
          name: branch.name,
          developers_can_push: params[:developers_can_push],
          developers_can_merge: params[:developers_can_merge]
        }

        service_args = [user_project, current_user, protected_branch_params]

        protected_branch = if protected_branch
                             ::ProtectedBranches::LegacyApiUpdateService.new(*service_args).execute(protected_branch)
                           else
                             ::ProtectedBranches::LegacyApiCreateService.new(*service_args).execute
                           end

        if protected_branch.valid?
          present branch, with: Entities::Branch, current_user: current_user, project: user_project
        else
          render_api_error!(protected_branch.errors.full_messages, 422)
        end
      end
      # rubocop: enable CodeReuse/ActiveRecord

      # Note: This API will be deprecated in favor of the protected branches API.
      desc 'Unprotect a single branch' do
        success Entities::Branch
        success code: 200, model: Entities::Branch
        failure [{ code: 404, message: '404 Project Not Found' }, { code: 404, message: '404 Branch Not Found' }]
        tags %w[branches]
      end
      params do
        requires :branch, type: String, desc: 'The name of the branch', allow_blank: false
      end
      # rubocop: disable CodeReuse/ActiveRecord
      put ':id/repository/branches/:branch/unprotect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
        authorize_admin_project

        branch = find_branch!(params[:branch])
        protected_branch = user_project.protected_branches.find_by(name: branch.name)

        ::ProtectedBranches::DestroyService.new(user_project, current_user).execute(protected_branch) if protected_branch

        present branch, with: Entities::Branch, current_user: current_user, project: user_project
      end
      # rubocop: enable CodeReuse/ActiveRecord

      desc 'Create branch' do
        success Entities::Branch
        success code: 201, model: Entities::Branch
        failure [{ code: 400, message: 'Failed to create branch' }, { code: 400, message: 'Branch already exists' }]
        tags %w[branches]
      end
      params do
        requires :branch, type: String, desc: 'The name of the branch', allow_blank: false
        requires :ref, type: String, desc: 'Create branch from commit sha or existing branch', allow_blank: false
      end
      post ':id/repository/branches' do
        authorize_push_project

        result = ::Branches::CreateService.new(user_project, current_user)
                 .execute(params[:branch], params[:ref])

        if result[:status] == :success
          present result[:branch],
                  with: Entities::Branch,
                  current_user: current_user,
                  project: user_project
        else
          render_api_error!(result[:message], 400)
        end
      end

      desc 'Delete a branch' do
        success code: 204
        failure [{ code: 404, message: 'Branch Not Found' }]
        tags %w[branches]
      end
      params do
        requires :branch, type: String, desc: 'The name of the branch', allow_blank: false
      end
      delete ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
        authorize_push_project

        branch = find_branch!(params[:branch])

        commit = user_project.repository.commit(branch.dereferenced_target)

        destroy_conditionally!(commit, last_updated: commit.authored_date) do
          result = ::Branches::DeleteService.new(user_project, current_user)
                    .execute(params[:branch])

          if result.error?
            render_api_error!(result.message, result.http_status)
          end
        end
      end

      desc 'Delete all merged branches' do
        success code: 202, message: '202 Accepted'
        failure [{ code: 404, message: '404 Project Not Found' }]
        tags %w[branches]
      end
      delete ':id/repository/merged_branches' do
        ::Branches::DeleteMergedService.new(user_project, current_user).async_execute

        accepted!
      end
    end
  end
end