summaryrefslogtreecommitdiff
path: root/lib/api/branches.rb
blob: 61a2d688282e6ed1d9e0070a16c9bdf7d8609470 (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
require 'mime/types'

module API
  class Branches < Grape::API
    include PaginationParams

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

    before { authorize! :download_code, user_project }

    params do
      requires :id, type: String, desc: 'The ID of a project'
    end
    resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
      desc 'Get a project repository branches' do
        success Entities::Branch
      end
      params do
        use :pagination
      end
      get ':id/repository/branches' do
        branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))

        # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37442
        Gitlab::GitalyClient.allow_n_plus_1_calls do
          present paginate(branches), with: Entities::Branch, project: user_project
        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]) ? status(204) : status(404)
        end
        get do
          branch = user_project.repository.find_branch(params[:branch])
          not_found!('Branch') unless branch

          present branch, with: Entities::Branch, 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'
        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
      put ':id/repository/branches/:branch/protect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
        authorize_admin_project

        branch = user_project.repository.find_branch(params[:branch])
        not_found!('Branch') unless 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::ApiUpdateService.new(*service_args).execute(protected_branch)
                           else
                             ::ProtectedBranches::ApiCreateService.new(*service_args).execute
                           end

        if protected_branch.valid?
          present branch, with: Entities::Branch, project: user_project
        else
          render_api_error!(protected_branch.errors.full_messages, 422)
        end
      end

      # 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'
      end
      put ':id/repository/branches/:branch/unprotect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
        authorize_admin_project

        branch = user_project.repository.find_branch(params[:branch])
        not_found!("Branch") unless branch
        protected_branch = user_project.protected_branches.find_by(name: branch.name)
        protected_branch&.destroy

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

      desc 'Create branch' do
        success Entities::Branch
      end
      params do
        requires :branch, type: String, desc: 'The name of the branch'
        requires :ref, type: String, desc: 'Create branch from commit sha or existing branch'
      end
      post ':id/repository/branches' do
        authorize_push_project

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

        if result[:status] == :success
          present result[:branch],
                  with: Entities::Branch,
                  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'
      end
      delete ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
        authorize_push_project

        branch = user_project.repository.find_branch(params[:branch])
        not_found!('Branch') unless branch

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

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

          if result[:status] != :success
            render_api_error!(result[:message], result[:return_code])
          end
        end
      end

      desc 'Delete all merged branches'
      delete ':id/repository/merged_branches' do
        DeleteMergedBranchesService.new(user_project, current_user).async_execute

        accepted!
      end
    end
  end
end