gitlab-org--gitlab-foss/lib/api/branches.rb
Stan Hu e7bd824484 Fix timeout issues retrieving branches via API
47d4890d changed the order of pagination so that the full list of
branches would be passed to Gitaly to determine which ones had been
merged, but this operation can timeout for large repositories with
many branches. We only need to determine whether the found branches have
been merged, so limit the scan to those.

Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/55724
2018-12-27 04:24:37 -08:00

179 lines
6.5 KiB
Ruby

# frozen_string_literal: true
require 'mime/types'
module API
class Branches < Grape::API
include PaginationParams
BRANCH_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX)
before { authorize! :download_code, user_project }
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'
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
end
get ':id/repository/branches' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42329')
repository = user_project.repository
branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute
branches = ::Kaminari.paginate_array(branches)
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
present(
paginate(branches),
with: Entities::Branch,
current_user: current_user,
project: user_project,
merged_branch_names: merged_branch_names
)
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 = 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 = CreateBranchService.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 = 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