Commit 9e6466c78442075e0f8fb8fe317ffe53dfcb5a95

Authored by Dmitriy Zaporozhets
2 parents eee14a0c b9d1fc2f

Merge branch 'api-remove-branch' into 'master'

New rules for protected branches

This MR change permissions logic for branch removal. Changes listed below:

Before
* developer can remove branch with terminal but not in UI
* masters can remove any branch with UI even protected one
* force-push to protected branch is not allowed but remove is allowed

After
* none can force push or remove protected branches
* developers and masters can remove normal branches with console and UI
app/controllers/projects/branches_controller.rb
... ... @@ -4,8 +4,7 @@ class Projects::BranchesController < Projects::ApplicationController
4 4 before_filter :require_non_empty_project
5 5  
6 6 before_filter :authorize_code_access!
7   - before_filter :authorize_push!, only: [:create]
8   - before_filter :authorize_admin_project!, only: [:destroy]
  7 + before_filter :authorize_push!, only: [:create, :destroy]
9 8  
10 9 def index
11 10 @branches = Kaminari.paginate_array(@repository.branches).page(params[:page]).per(30)
... ... @@ -22,11 +21,7 @@ class Projects::BranchesController < Projects::ApplicationController
22 21 end
23 22  
24 23 def destroy
25   - branch = @repository.find_branch(params[:id])
26   -
27   - if branch && @repository.rm_branch(branch.name)
28   - Event.create_ref_event(@project, current_user, branch, 'rm')
29   - end
  24 + DeleteBranchService.new.execute(project, params[:id], current_user)
30 25  
31 26 respond_to do |format|
32 27 format.html { redirect_to project_branches_path(@project) }
... ...
app/helpers/branches_helper.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +module BranchesHelper
  2 + def can_remove_branch?(project, branch_name)
  3 + if project.protected_branch? branch_name
  4 + false
  5 + elsif branch_name == project.repository.root_ref
  6 + false
  7 + else
  8 + can?(current_user, :push_code, project)
  9 + end
  10 + end
  11 +end
... ...
app/services/delete_branch_service.rb 0 → 100644
... ... @@ -0,0 +1,46 @@
  1 +class DeleteBranchService
  2 + def execute(project, branch_name, current_user)
  3 + repository = project.repository
  4 + branch = repository.find_branch(branch_name)
  5 +
  6 + # No such branch
  7 + unless branch
  8 + return error('No such branch')
  9 + end
  10 +
  11 + if branch_name == repository.root_ref
  12 + return error('Cannot remove HEAD branch')
  13 + end
  14 +
  15 + # Dont allow remove of protected branch
  16 + if project.protected_branch?(branch_name)
  17 + return error('Protected branch cant be removed')
  18 + end
  19 +
  20 + # Dont allow user to remove branch if he is not allowed to push
  21 + unless current_user.can?(:push_code, project)
  22 + return error('You dont have push access to repo')
  23 + end
  24 +
  25 + if repository.rm_branch(branch_name)
  26 + Event.create_ref_event(project, current_user, branch, 'rm')
  27 + success('Branch was removed')
  28 + else
  29 + return error('Failed to remove branch')
  30 + end
  31 + end
  32 +
  33 + def error(message)
  34 + {
  35 + message: message,
  36 + state: :error
  37 + }
  38 + end
  39 +
  40 + def success(message)
  41 + {
  42 + message: message,
  43 + state: :success
  44 + }
  45 + end
  46 +end
... ...
app/views/projects/branches/_branch.html.haml
... ... @@ -16,8 +16,8 @@
16 16 %i.icon-copy
17 17 Compare
18 18  
19   - - if can?(current_user, :admin_project, @project) && branch.name != @repository.root_ref
20   - = link_to project_branch_path(@project, branch.name), class: 'btn btn-grouped btn-small remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do
  19 + - if can_remove_branch?(@project, branch.name)
  20 + = link_to project_branch_path(@project, branch.name), class: 'btn btn-grouped btn-small btn-remove remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do
21 21 %i.icon-trash
22 22  
23 23 - if commit
... ...
app/views/projects/protected_branches/index.html.haml
... ... @@ -4,12 +4,12 @@
4 4 = render "projects/branches/filter"
5 5 .col-md-9
6 6 .bs-callout.bs-callout-info
7   - %p Protected branches designed to prevent push for all except #{link_to "masters", help_permissions_path, class: "vlink"}.
8   - %p This ability allows:
  7 + %p Protected branches designed to
9 8 %ul
10   - %li keep stable branches secured
11   - %li forced code review before merge to protected branches
12   - %li prevents branch from force push
  9 + %li prevent push for all except #{link_to "masters", help_permissions_path, class: "vlink"}.
  10 + %li prevent branch from force push
  11 + %li prevent branch from removal
  12 + %p This ability allows to keep stable branches secured and force code review before merge to protected branches
13 13 %p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined-link"}
14 14  
15 15 - if can? current_user, :admin_project, @project
... ...
lib/api/branches.rb
... ... @@ -84,6 +84,18 @@ module API
84 84  
85 85 present @branch, with: Entities::RepoObject, project: user_project
86 86 end
  87 +
  88 + # Delete branch
  89 + #
  90 + # Parameters:
  91 + # id (required) - The ID of a project
  92 + # branch (required) - The name of the branch
  93 + # Example Request:
  94 + # DELETE /projects/:id/repository/branches/:branch
  95 + delete ":id/repository/branches/:branch" do
  96 + authorize_push_project
  97 + DeleteBranchService.new.execute(user_project, params[:branch], current_user)
  98 + end
87 99 end
88 100 end
89 101 end
... ...
lib/gitlab/git_access.rb
... ... @@ -44,14 +44,18 @@ module Gitlab
44 44 def push_allowed?(user, project, ref, oldrev, newrev, forced_push)
45 45 if user && user_allowed?(user)
46 46 action = if project.protected_branch?(ref)
47   - if forced_push.to_s == 'true'
48   - :force_push_code_to_protected_branches
49   - else
50   - :push_code_to_protected_branches
51   - end
52   - else
53   - :push_code
54   - end
  47 + # we dont allow force push to protected branch
  48 + if forced_push.to_s == 'true'
  49 + :force_push_code_to_protected_branches
  50 + # and we dont allow remove of protected branch
  51 + elsif newrev =~ /0000000/
  52 + :remove_protected_branches
  53 + else
  54 + :push_code_to_protected_branches
  55 + end
  56 + else
  57 + :push_code
  58 + end
55 59 user.can?(action, project)
56 60 else
57 61 false
... ...