Add confirm delete protected branch modal
This commit is contained in:
parent
1a5e84febe
commit
1ebd9dad8e
21 changed files with 383 additions and 150 deletions
36
app/assets/javascripts/branches/branches_delete_modal.js
Normal file
36
app/assets/javascripts/branches/branches_delete_modal.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
const MODAL_SELECTOR = '#modal-delete-branch';
|
||||||
|
|
||||||
|
class DeleteModal {
|
||||||
|
constructor() {
|
||||||
|
this.$modal = $(MODAL_SELECTOR);
|
||||||
|
this.$toggleBtns = $(`[data-target="${MODAL_SELECTOR}"]`);
|
||||||
|
this.$branchName = $('.js-branch-name', this.$modal);
|
||||||
|
this.$confirmInput = $('.js-delete-branch-input', this.$modal);
|
||||||
|
this.$deleteBtn = $('.js-delete-branch', this.$modal);
|
||||||
|
this.bindEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
this.$toggleBtns.on('click', this.setModalData.bind(this));
|
||||||
|
this.$confirmInput.on('input', this.setDeleteDisabled.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
setModalData(e) {
|
||||||
|
this.branchName = e.currentTarget.dataset.branchName || '';
|
||||||
|
this.deletePath = e.currentTarget.dataset.deletePath || '';
|
||||||
|
this.updateModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
setDeleteDisabled(e) {
|
||||||
|
this.$deleteBtn.attr('disabled', e.currentTarget.value !== this.branchName);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateModal() {
|
||||||
|
this.$branchName.text(this.branchName);
|
||||||
|
this.$confirmInput.val('');
|
||||||
|
this.$deleteBtn.attr('href', this.deletePath);
|
||||||
|
this.$deleteBtn.attr('disabled', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeleteModal;
|
|
@ -38,6 +38,7 @@
|
||||||
import Issue from './issue';
|
import Issue from './issue';
|
||||||
|
|
||||||
import BindInOut from './behaviors/bind_in_out';
|
import BindInOut from './behaviors/bind_in_out';
|
||||||
|
import DeleteModal from './branches/branches_delete_modal';
|
||||||
import Group from './group';
|
import Group from './group';
|
||||||
import GroupName from './group_name';
|
import GroupName from './group_name';
|
||||||
import GroupsList from './groups_list';
|
import GroupsList from './groups_list';
|
||||||
|
@ -180,6 +181,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
|
||||||
break;
|
break;
|
||||||
case 'projects:branches:index':
|
case 'projects:branches:index':
|
||||||
gl.AjaxLoadingSpinner.init();
|
gl.AjaxLoadingSpinner.init();
|
||||||
|
new DeleteModal();
|
||||||
break;
|
break;
|
||||||
case 'projects:issues:new':
|
case 'projects:issues:new':
|
||||||
case 'projects:issues:edit':
|
case 'projects:issues:edit':
|
||||||
|
|
|
@ -152,6 +152,7 @@ ul.content-list {
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
&.has-tooltip,
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
|
||||||
|
|
|
@ -73,13 +73,17 @@ class Projects::BranchesController < Projects::ApplicationController
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@branch_name = Addressable::URI.unescape(params[:id])
|
@branch_name = Addressable::URI.unescape(params[:id])
|
||||||
status = DeleteBranchService.new(project, current_user).execute(@branch_name)
|
result = DeleteBranchService.new(project, current_user).execute(@branch_name)
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
redirect_to namespace_project_branches_path(@project.namespace,
|
flash_type = result[:status] == :error ? :alert : :notice
|
||||||
@project), status: 303
|
flash[flash_type] = result[:message]
|
||||||
|
|
||||||
|
redirect_to namespace_project_branches_path(@project.namespace, @project), status: 303
|
||||||
end
|
end
|
||||||
format.js { render nothing: true, status: status[:return_code] }
|
|
||||||
|
format.js { render nothing: true, status: result[:return_code] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ class Projects::TagsController < Projects::ApplicationController
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if result[:status] == :success
|
if result[:status] == :success
|
||||||
format.html do
|
format.html do
|
||||||
redirect_to namespace_project_tags_path(@project.namespace, @project)
|
redirect_to namespace_project_tags_path(@project.namespace, @project), status: 303
|
||||||
end
|
end
|
||||||
|
|
||||||
format.js
|
format.js
|
||||||
|
@ -57,7 +57,7 @@ class Projects::TagsController < Projects::ApplicationController
|
||||||
|
|
||||||
format.html do
|
format.html do
|
||||||
redirect_to namespace_project_tags_path(@project.namespace, @project),
|
redirect_to namespace_project_tags_path(@project.namespace, @project),
|
||||||
alert: @error
|
alert: @error, status: 303
|
||||||
end
|
end
|
||||||
|
|
||||||
format.js do
|
format.js do
|
||||||
|
|
|
@ -1,14 +1,4 @@
|
||||||
module BranchesHelper
|
module BranchesHelper
|
||||||
def can_remove_branch?(project, branch_name)
|
|
||||||
if ProtectedBranch.protected?(project, branch_name)
|
|
||||||
false
|
|
||||||
elsif branch_name == project.repository.root_ref
|
|
||||||
false
|
|
||||||
else
|
|
||||||
can?(current_user, :push_code, project)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def filter_branches_path(options = {})
|
def filter_branches_path(options = {})
|
||||||
exist_opts = {
|
exist_opts = {
|
||||||
search: params[:search],
|
search: params[:search],
|
||||||
|
|
|
@ -98,7 +98,7 @@ class ProjectPolicy < BasePolicy
|
||||||
end
|
end
|
||||||
|
|
||||||
def master_access!
|
def master_access!
|
||||||
can! :push_code_to_protected_branches
|
can! :delete_protected_branch
|
||||||
can! :update_project_snippet
|
can! :update_project_snippet
|
||||||
can! :update_environment
|
can! :update_environment
|
||||||
can! :update_deployment
|
can! :update_deployment
|
||||||
|
@ -173,7 +173,7 @@ class ProjectPolicy < BasePolicy
|
||||||
def archived_access!
|
def archived_access!
|
||||||
cannot! :create_merge_request
|
cannot! :create_merge_request
|
||||||
cannot! :push_code
|
cannot! :push_code
|
||||||
cannot! :push_code_to_protected_branches
|
cannot! :delete_protected_branch
|
||||||
cannot! :update_merge_request
|
cannot! :update_merge_request
|
||||||
cannot! :admin_merge_request
|
cannot! :admin_merge_request
|
||||||
end
|
end
|
||||||
|
@ -211,7 +211,7 @@ class ProjectPolicy < BasePolicy
|
||||||
|
|
||||||
unless repository_enabled
|
unless repository_enabled
|
||||||
cannot! :push_code
|
cannot! :push_code
|
||||||
cannot! :push_code_to_protected_branches
|
cannot! :delete_protected_branch
|
||||||
cannot! :download_code
|
cannot! :download_code
|
||||||
cannot! :fork_project
|
cannot! :fork_project
|
||||||
cannot! :read_commit_status
|
cannot! :read_commit_status
|
||||||
|
|
|
@ -3,22 +3,14 @@ class DeleteBranchService < BaseService
|
||||||
repository = project.repository
|
repository = project.repository
|
||||||
branch = repository.find_branch(branch_name)
|
branch = repository.find_branch(branch_name)
|
||||||
|
|
||||||
unless branch
|
|
||||||
return error('No such branch', 404)
|
|
||||||
end
|
|
||||||
|
|
||||||
if branch_name == repository.root_ref
|
|
||||||
return error('Cannot remove HEAD branch', 405)
|
|
||||||
end
|
|
||||||
|
|
||||||
if ProtectedBranch.protected?(project, branch_name)
|
|
||||||
return error('Protected branch cant be removed', 405)
|
|
||||||
end
|
|
||||||
|
|
||||||
unless current_user.can?(:push_code, project)
|
unless current_user.can?(:push_code, project)
|
||||||
return error('You dont have push access to repo', 405)
|
return error('You dont have push access to repo', 405)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unless branch
|
||||||
|
return error('No such branch', 404)
|
||||||
|
end
|
||||||
|
|
||||||
if repository.rm_branch(current_user, branch_name)
|
if repository.rm_branch(current_user, branch_name)
|
||||||
success('Branch was removed')
|
success('Branch was removed')
|
||||||
else
|
else
|
||||||
|
|
|
@ -30,8 +30,29 @@
|
||||||
= render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name]
|
= render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name]
|
||||||
|
|
||||||
- if can?(current_user, :push_code, @project)
|
- if can?(current_user, :push_code, @project)
|
||||||
|
- if branch.name == @project.repository.root_ref
|
||||||
|
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
|
||||||
|
disabled: true,
|
||||||
|
title: "The default branch cannot be deleted" }
|
||||||
|
= icon("trash-o")
|
||||||
|
- elsif protected_branch?(@project, branch)
|
||||||
|
- if can?(current_user, :delete_protected_branch, @project)
|
||||||
|
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
|
||||||
|
title: "Delete protected branch",
|
||||||
|
data: { toggle: "modal",
|
||||||
|
target: "#modal-delete-branch",
|
||||||
|
delete_path: namespace_project_branch_path(@project.namespace, @project, branch.name),
|
||||||
|
branch_name: branch.name } }
|
||||||
|
= icon("trash-o")
|
||||||
|
- else
|
||||||
|
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
|
||||||
|
disabled: true,
|
||||||
|
title: "Only a project master or owner can delete a protected branch" }
|
||||||
|
= icon("trash-o")
|
||||||
|
- else
|
||||||
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name),
|
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name),
|
||||||
class: "btn btn-remove remove-row js-ajax-loading-spinner #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}",
|
class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
|
||||||
|
title: "Delete branch",
|
||||||
method: :delete,
|
method: :delete,
|
||||||
data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" },
|
data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" },
|
||||||
remote: true,
|
remote: true,
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
#modal-delete-branch.modal{ tabindex: -1 }
|
||||||
|
.modal-dialog
|
||||||
|
.modal-content
|
||||||
|
.modal-header
|
||||||
|
%button.close{ data: { dismiss: 'modal' } } ×
|
||||||
|
%h3.page-title
|
||||||
|
Delete protected branch
|
||||||
|
= surround "'", "'?" do
|
||||||
|
%span.js-branch-name>[branch name]
|
||||||
|
|
||||||
|
.modal-body
|
||||||
|
%p
|
||||||
|
You’re about to permanently delete the protected branch
|
||||||
|
= succeed '.' do
|
||||||
|
%strong.js-branch-name [branch name]
|
||||||
|
%p
|
||||||
|
Once you confirm and press
|
||||||
|
= succeed ',' do
|
||||||
|
%strong Delete protected branch
|
||||||
|
it cannot be undone or recovered.
|
||||||
|
%p
|
||||||
|
%strong To confirm, type
|
||||||
|
%kbd.js-branch-name [branch name]
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
= text_field_tag 'delete_branch_input', '', class: 'form-control js-delete-branch-input'
|
||||||
|
|
||||||
|
.modal-footer
|
||||||
|
%button.btn{ data: { dismiss: 'modal' } } Cancel
|
||||||
|
= link_to 'Delete protected branch', '',
|
||||||
|
class: "btn btn-danger js-delete-branch",
|
||||||
|
title: 'Delete branch',
|
||||||
|
method: :delete,
|
||||||
|
"aria-label" => "Delete"
|
|
@ -37,3 +37,5 @@
|
||||||
= paginate @branches, theme: 'gitlab'
|
= paginate @branches, theme: 'gitlab'
|
||||||
- else
|
- else
|
||||||
.nothing-here-block No branches to show
|
.nothing-here-block No branches to show
|
||||||
|
|
||||||
|
= render 'projects/branches/delete_protected_modal'
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
module Gitlab
|
module Gitlab
|
||||||
module Checks
|
module Checks
|
||||||
class ChangeAccess
|
class ChangeAccess
|
||||||
# protocol is currently used only in EE
|
|
||||||
attr_reader :user_access, :project, :skip_authorization, :protocol
|
attr_reader :user_access, :project, :skip_authorization, :protocol
|
||||||
|
|
||||||
def initialize(
|
def initialize(
|
||||||
|
@ -18,7 +17,9 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def exec
|
def exec
|
||||||
error = push_checks || tag_checks || protected_branch_checks
|
return GitAccessStatus.new(true) if skip_authorization
|
||||||
|
|
||||||
|
error = push_checks || branch_checks || tag_checks
|
||||||
|
|
||||||
if error
|
if error
|
||||||
GitAccessStatus.new(false, error)
|
GitAccessStatus.new(false, error)
|
||||||
|
@ -29,35 +30,59 @@ module Gitlab
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def protected_branch_checks
|
def push_checks
|
||||||
return if skip_authorization
|
if user_access.cannot_do_action?(:push_code)
|
||||||
|
"You are not allowed to push code to this project."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def branch_checks
|
||||||
return unless @branch_name
|
return unless @branch_name
|
||||||
|
|
||||||
|
if deletion? && @branch_name == project.default_branch
|
||||||
|
return "The default branch of a project cannot be deleted."
|
||||||
|
end
|
||||||
|
|
||||||
|
protected_branch_checks
|
||||||
|
end
|
||||||
|
|
||||||
|
def protected_branch_checks
|
||||||
return unless ProtectedBranch.protected?(project, @branch_name)
|
return unless ProtectedBranch.protected?(project, @branch_name)
|
||||||
|
|
||||||
if forced_push?
|
if forced_push?
|
||||||
return "You are not allowed to force push code to a protected branch on this project."
|
return "You are not allowed to force push code to a protected branch on this project."
|
||||||
elsif deletion?
|
|
||||||
return "You are not allowed to delete protected branches from this project."
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if matching_merge_request?
|
if deletion?
|
||||||
if user_access.can_merge_to_branch?(@branch_name) || user_access.can_push_to_branch?(@branch_name)
|
protected_branch_deletion_checks
|
||||||
return
|
|
||||||
else
|
else
|
||||||
|
protected_branch_push_checks
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def protected_branch_deletion_checks
|
||||||
|
unless user_access.can_delete_branch?(@branch_name)
|
||||||
|
return 'You are not allowed to delete protected branches from this project. Only a project master or owner can delete a protected branch.'
|
||||||
|
end
|
||||||
|
|
||||||
|
unless protocol == 'web'
|
||||||
|
'You can only delete protected branches using the web interface.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def protected_branch_push_checks
|
||||||
|
if matching_merge_request?
|
||||||
|
unless user_access.can_merge_to_branch?(@branch_name) || user_access.can_push_to_branch?(@branch_name)
|
||||||
"You are not allowed to merge code into protected branches on this project."
|
"You are not allowed to merge code into protected branches on this project."
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if user_access.can_push_to_branch?(@branch_name)
|
unless user_access.can_push_to_branch?(@branch_name)
|
||||||
return
|
|
||||||
else
|
|
||||||
"You are not allowed to push code to protected branches on this project."
|
"You are not allowed to push code to protected branches on this project."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def tag_checks
|
def tag_checks
|
||||||
return if skip_authorization
|
|
||||||
|
|
||||||
return unless @tag_name
|
return unless @tag_name
|
||||||
|
|
||||||
if tag_exists? && user_access.cannot_do_action?(:admin_project)
|
if tag_exists? && user_access.cannot_do_action?(:admin_project)
|
||||||
|
@ -68,7 +93,8 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def protected_tag_checks
|
def protected_tag_checks
|
||||||
return unless tag_protected?
|
return unless ProtectedTag.protected?(project, @tag_name)
|
||||||
|
|
||||||
return "Protected tags cannot be updated." if update?
|
return "Protected tags cannot be updated." if update?
|
||||||
return "Protected tags cannot be deleted." if deletion?
|
return "Protected tags cannot be deleted." if deletion?
|
||||||
|
|
||||||
|
@ -77,18 +103,6 @@ module Gitlab
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def tag_protected?
|
|
||||||
ProtectedTag.protected?(project, @tag_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def push_checks
|
|
||||||
return if skip_authorization
|
|
||||||
|
|
||||||
if user_access.cannot_do_action?(:push_code)
|
|
||||||
"You are not allowed to push code to this project."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def tag_exists?
|
def tag_exists?
|
||||||
|
|
|
@ -38,6 +38,16 @@ module Gitlab
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def can_delete_branch?(ref)
|
||||||
|
return false unless can_access_git?
|
||||||
|
|
||||||
|
if ProtectedBranch.protected?(project, ref)
|
||||||
|
user.can?(:delete_protected_branch, project)
|
||||||
|
else
|
||||||
|
user.can?(:push_code, project)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def can_push_to_branch?(ref)
|
def can_push_to_branch?(ref)
|
||||||
return false unless can_access_git?
|
return false unless can_access_git?
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,13 @@ describe 'Branches', feature: true do
|
||||||
let(:project) { create(:project, :public) }
|
let(:project) { create(:project, :public) }
|
||||||
let(:repository) { project.repository }
|
let(:repository) { project.repository }
|
||||||
|
|
||||||
context 'logged in' do
|
def set_protected_branch_name(branch_name)
|
||||||
|
find(".js-protected-branch-select").click
|
||||||
|
find(".dropdown-input-field").set(branch_name)
|
||||||
|
click_on("Create wildcard #{branch_name}")
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'logged in as developer' do
|
||||||
before do
|
before do
|
||||||
login_as :user
|
login_as :user
|
||||||
project.team << [@user, :developer]
|
project.team << [@user, :developer]
|
||||||
|
@ -38,6 +44,83 @@ describe 'Branches', feature: true do
|
||||||
expect(find('.all-branches')).to have_selector('li', count: 1)
|
expect(find('.all-branches')).to have_selector('li', count: 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'Delete unprotected branch' do
|
||||||
|
it 'removes branch after confirmation', js: true do
|
||||||
|
visit namespace_project_branches_path(project.namespace, project)
|
||||||
|
|
||||||
|
fill_in 'branch-search', with: 'fix'
|
||||||
|
|
||||||
|
find('#branch-search').native.send_keys(:enter)
|
||||||
|
|
||||||
|
expect(page).to have_content('fix')
|
||||||
|
expect(find('.all-branches')).to have_selector('li', count: 1)
|
||||||
|
find('.js-branch-fix .btn-remove').trigger(:click)
|
||||||
|
|
||||||
|
expect(page).not_to have_content('fix')
|
||||||
|
expect(find('.all-branches')).to have_selector('li', count: 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'Delete protected branch' do
|
||||||
|
before do
|
||||||
|
project.add_user(@user, :master)
|
||||||
|
visit namespace_project_protected_branches_path(project.namespace, project)
|
||||||
|
set_protected_branch_name('fix')
|
||||||
|
click_on "Protect"
|
||||||
|
|
||||||
|
within(".protected-branches-list") { expect(page).to have_content('fix') }
|
||||||
|
expect(ProtectedBranch.count).to eq(1)
|
||||||
|
project.add_user(@user, :developer)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not allow devleoper to removes protected branch', js: true do
|
||||||
|
visit namespace_project_branches_path(project.namespace, project)
|
||||||
|
|
||||||
|
fill_in 'branch-search', with: 'fix'
|
||||||
|
find('#branch-search').native.send_keys(:enter)
|
||||||
|
|
||||||
|
expect(page).to have_css('.btn-remove.disabled')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'logged in as master' do
|
||||||
|
before do
|
||||||
|
login_as :user
|
||||||
|
project.team << [@user, :master]
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'Delete protected branch' do
|
||||||
|
before do
|
||||||
|
visit namespace_project_protected_branches_path(project.namespace, project)
|
||||||
|
set_protected_branch_name('fix')
|
||||||
|
click_on "Protect"
|
||||||
|
|
||||||
|
within(".protected-branches-list") { expect(page).to have_content('fix') }
|
||||||
|
expect(ProtectedBranch.count).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes branch after modal confirmation', js: true do
|
||||||
|
visit namespace_project_branches_path(project.namespace, project)
|
||||||
|
|
||||||
|
fill_in 'branch-search', with: 'fix'
|
||||||
|
find('#branch-search').native.send_keys(:enter)
|
||||||
|
|
||||||
|
expect(page).to have_content('fix')
|
||||||
|
expect(find('.all-branches')).to have_selector('li', count: 1)
|
||||||
|
page.find('[data-target="#modal-delete-branch"]').trigger(:click)
|
||||||
|
|
||||||
|
expect(page).to have_css('.js-delete-branch[disabled]')
|
||||||
|
fill_in 'delete_branch_input', with: 'fix'
|
||||||
|
click_link 'Delete protected branch'
|
||||||
|
|
||||||
|
fill_in 'branch-search', with: 'fix'
|
||||||
|
find('#branch-search').native.send_keys(:enter)
|
||||||
|
|
||||||
|
expect(page).to have_content('No branches to show')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'logged out' do
|
context 'logged out' do
|
||||||
|
|
|
@ -96,9 +96,21 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'branches check' do
|
||||||
|
context 'trying to delete the default branch' do
|
||||||
|
let(:newrev) { '0000000000000000000000000000000000000000' }
|
||||||
|
let(:ref) { 'refs/heads/master' }
|
||||||
|
|
||||||
|
it 'returns an error' do
|
||||||
|
expect(subject.status).to be(false)
|
||||||
|
expect(subject.message).to eq('The default branch of a project cannot be deleted.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'protected branches check' do
|
context 'protected branches check' do
|
||||||
before do
|
before do
|
||||||
allow(ProtectedBranch).to receive(:protected?).with(project, 'master').and_return(true)
|
allow(ProtectedBranch).to receive(:protected?).with(project, 'master').and_return(true)
|
||||||
|
allow(ProtectedBranch).to receive(:protected?).with(project, 'feature').and_return(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns an error if the user is not allowed to do forced pushes to protected branches' do
|
it 'returns an error if the user is not allowed to do forced pushes to protected branches' do
|
||||||
|
@ -126,10 +138,35 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
|
||||||
|
|
||||||
context 'branch deletion' do
|
context 'branch deletion' do
|
||||||
let(:newrev) { '0000000000000000000000000000000000000000' }
|
let(:newrev) { '0000000000000000000000000000000000000000' }
|
||||||
|
let(:ref) { 'refs/heads/feature' }
|
||||||
|
|
||||||
it 'returns an error if the user is not allowed to delete protected branches' do
|
context 'if the user is not allowed to delete protected branches' do
|
||||||
|
it 'returns an error' do
|
||||||
expect(subject.status).to be(false)
|
expect(subject.status).to be(false)
|
||||||
expect(subject.message).to eq('You are not allowed to delete protected branches from this project.')
|
expect(subject.message).to eq('You are not allowed to delete protected branches from this project. Only a project master or owner can delete a protected branch.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'if the user is allowed to delete protected branches' do
|
||||||
|
before do
|
||||||
|
project.add_master(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'through the web interface' do
|
||||||
|
let(:protocol) { 'web' }
|
||||||
|
|
||||||
|
it 'allows branch deletion' do
|
||||||
|
expect(subject.status).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'over SSH or HTTP' do
|
||||||
|
it 'returns an error' do
|
||||||
|
expect(subject.status).to be(false)
|
||||||
|
expect(subject.message).to eq('You can only delete protected branches using the web interface.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Gitlab::GitAccess, lib: true do
|
describe Gitlab::GitAccess, lib: true do
|
||||||
let(:access) { Gitlab::GitAccess.new(actor, project, 'web', authentication_abilities: authentication_abilities) }
|
let(:access) { Gitlab::GitAccess.new(actor, project, 'ssh', authentication_abilities: authentication_abilities) }
|
||||||
let(:project) { create(:project, :repository) }
|
let(:project) { create(:project, :repository) }
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
let(:actor) { user }
|
let(:actor) { user }
|
||||||
|
|
|
@ -5,7 +5,7 @@ describe Gitlab::UserAccess, lib: true do
|
||||||
let(:project) { create(:project) }
|
let(:project) { create(:project) }
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
describe 'can_push_to_branch?' do
|
describe '#can_push_to_branch?' do
|
||||||
describe 'push to none protected branch' do
|
describe 'push to none protected branch' do
|
||||||
it 'returns true if user is a master' do
|
it 'returns true if user is a master' do
|
||||||
project.team << [user, :master]
|
project.team << [user, :master]
|
||||||
|
@ -143,7 +143,7 @@ describe Gitlab::UserAccess, lib: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'can_create_tag?' do
|
describe '#can_create_tag?' do
|
||||||
describe 'push to none protected tag' do
|
describe 'push to none protected tag' do
|
||||||
it 'returns true if user is a master' do
|
it 'returns true if user is a master' do
|
||||||
project.add_user(user, :master)
|
project.add_user(user, :master)
|
||||||
|
@ -211,4 +211,48 @@ describe Gitlab::UserAccess, lib: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#can_delete_branch?' do
|
||||||
|
describe 'delete unprotected branch' do
|
||||||
|
it 'returns true if user is a master' do
|
||||||
|
project.add_user(user, :master)
|
||||||
|
|
||||||
|
expect(access.can_delete_branch?('random_branch')).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true if user is a developer' do
|
||||||
|
project.add_user(user, :developer)
|
||||||
|
|
||||||
|
expect(access.can_delete_branch?('random_branch')).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false if user is a reporter' do
|
||||||
|
project.add_user(user, :reporter)
|
||||||
|
|
||||||
|
expect(access.can_delete_branch?('random_branch')).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'delete protected branch' do
|
||||||
|
let(:branch) { create(:protected_branch, project: project, name: "test") }
|
||||||
|
|
||||||
|
it 'returns true if user is a master' do
|
||||||
|
project.add_user(user, :master)
|
||||||
|
|
||||||
|
expect(access.can_delete_branch?(branch.name)).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false if user is a developer' do
|
||||||
|
project.add_user(user, :developer)
|
||||||
|
|
||||||
|
expect(access.can_delete_branch?(branch.name)).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false if user is a reporter' do
|
||||||
|
project.add_user(user, :reporter)
|
||||||
|
|
||||||
|
expect(access.can_delete_branch?(branch.name)).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -43,7 +43,7 @@ describe ProjectPolicy, models: true do
|
||||||
|
|
||||||
let(:master_permissions) do
|
let(:master_permissions) do
|
||||||
%i[
|
%i[
|
||||||
push_code_to_protected_branches update_project_snippet update_environment
|
delete_protected_branch update_project_snippet update_environment
|
||||||
update_deployment admin_milestone admin_project_snippet
|
update_deployment admin_milestone admin_project_snippet
|
||||||
admin_project_member admin_note admin_wiki admin_project
|
admin_project_member admin_note admin_wiki admin_project
|
||||||
admin_commit_status admin_build admin_container_image
|
admin_commit_status admin_build admin_container_image
|
||||||
|
|
|
@ -406,19 +406,6 @@ describe API::Branches do
|
||||||
delete api("/projects/#{project.id}/repository/branches/foobar", user)
|
delete api("/projects/#{project.id}/repository/branches/foobar", user)
|
||||||
expect(response).to have_http_status(404)
|
expect(response).to have_http_status(404)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "removes protected branch" do
|
|
||||||
create(:protected_branch, project: project, name: branch_name)
|
|
||||||
delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
|
|
||||||
expect(response).to have_http_status(405)
|
|
||||||
expect(json_response['message']).to eq('Protected branch cant be removed')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not remove HEAD branch" do
|
|
||||||
delete api("/projects/#{project.id}/repository/branches/master", user)
|
|
||||||
expect(response).to have_http_status(405)
|
|
||||||
expect(json_response['message']).to eq('Cannot remove HEAD branch')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "DELETE /projects/:id/repository/merged_branches" do
|
describe "DELETE /projects/:id/repository/merged_branches" do
|
||||||
|
|
|
@ -47,19 +47,6 @@ describe API::V3::Branches do
|
||||||
delete v3_api("/projects/#{project.id}/repository/branches/foobar", user)
|
delete v3_api("/projects/#{project.id}/repository/branches/foobar", user)
|
||||||
expect(response).to have_http_status(404)
|
expect(response).to have_http_status(404)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "removes protected branch" do
|
|
||||||
create(:protected_branch, project: project, name: branch_name)
|
|
||||||
delete v3_api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
|
|
||||||
expect(response).to have_http_status(405)
|
|
||||||
expect(json_response['message']).to eq('Protected branch cant be removed')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not remove HEAD branch" do
|
|
||||||
delete v3_api("/projects/#{project.id}/repository/branches/master", user)
|
|
||||||
expect(response).to have_http_status(405)
|
|
||||||
expect(json_response['message']).to eq('Cannot remove HEAD branch')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "DELETE /projects/:id/repository/merged_branches" do
|
describe "DELETE /projects/:id/repository/merged_branches" do
|
||||||
|
|
|
@ -6,33 +6,22 @@ describe DeleteMergedBranchesService, services: true do
|
||||||
let(:project) { create(:project, :repository) }
|
let(:project) { create(:project, :repository) }
|
||||||
|
|
||||||
context '#execute' do
|
context '#execute' do
|
||||||
context 'unprotected branches' do
|
|
||||||
before do
|
|
||||||
service.execute
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'deletes a branch that was merged' do
|
it 'deletes a branch that was merged' do
|
||||||
|
service.execute
|
||||||
|
|
||||||
expect(project.repository.branch_names).not_to include('improve/awesome')
|
expect(project.repository.branch_names).not_to include('improve/awesome')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'keeps branch that is unmerged' do
|
it 'keeps branch that is unmerged' do
|
||||||
|
service.execute
|
||||||
|
|
||||||
expect(project.repository.branch_names).to include('feature')
|
expect(project.repository.branch_names).to include('feature')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'keeps "master"' do
|
it 'keeps "master"' do
|
||||||
expect(project.repository.branch_names).to include('master')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'protected branches' do
|
|
||||||
before do
|
|
||||||
create(:protected_branch, name: 'improve/awesome', project: project)
|
|
||||||
service.execute
|
service.execute
|
||||||
end
|
|
||||||
|
|
||||||
it 'keeps protected branch' do
|
expect(project.repository.branch_names).to include('master')
|
||||||
expect(project.repository.branch_names).to include('improve/awesome')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'user without rights' do
|
context 'user without rights' do
|
||||||
|
|
Loading…
Reference in a new issue