summaryrefslogtreecommitdiff
path: root/lib/api/branches.rb
blob: a2c9020ac84711a167182fb1a314e4bb88dd97bd (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
# 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! :download_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 :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, type: String, desc: 'The ID of a project'
    end
    resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
      desc 'Get a project repository branches' do
        success Entities::Branch
      end
      params do
        use :pagination
        use :filter_params

        optional :page_token, type: String, desc: 'Name of branch to start the paginaition 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))

          if Feature.enabled?(:api_caching_branches, user_project, type: :development, default_enabled: :yaml)
            present_cached(
              branches,
              with: Entities::Branch,
              current_user: current_user,
              project: user_project,
              merged_branch_names: merged_branch_names,
              expires_in: 10.minutes,
              cache_context: -> (branch) { [current_user&.cache_key, merged_branch_names.include?(branch.name)] }
            )
          else
            present(
              branches,
              with: Entities::Branch,
              current_user: current_user,
              project: user_project,
              merged_branch_names: merged_branch_names
            )
          end
        end
      end

      resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
        desc 'Get a single branch' do
          success Entities::Branch
        end
        params do
          requires :branch, type: String, desc: 'The name of the branch'
        end
        head do
          user_project.repository.branch_exists?(params[:branch]) ? no_content! : not_found!
        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
      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
      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)
        protected_branch&.destroy

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

      desc 'Create branch' do
        success Entities::Branch
      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'
      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'
      delete ':id/repository/merged_branches' do
        ::Branches::DeleteMergedService.new(user_project, current_user).async_execute

        accepted!
      end
    end
  end
end