feat(branch removal trough UI): Added ability to remove branches trough UI from git and hg repositories. Fixes: RCCE-75
This commit is contained in:
parent
3d0e81968b
commit
59e99e4368
10 changed files with 174 additions and 11 deletions
|
|
@ -715,6 +715,7 @@ class BaseReferencesView(RepoAppView):
|
|||
{
|
||||
"name": _render("name", ref_name, files_url, closed),
|
||||
"name_raw": ref_name,
|
||||
"closed": closed,
|
||||
"date": _render("date", commit.date),
|
||||
"date_raw": datetime_to_time(commit.date),
|
||||
"author": _render("author", commit.author),
|
||||
|
|
|
|||
|
|
@ -591,6 +591,15 @@ def includeme(config):
|
|||
route_name='branches_home', request_method='GET',
|
||||
renderer='rhodecode:templates/branches/branches.mako')
|
||||
|
||||
config.add_route(
|
||||
name='branch_remove',
|
||||
pattern='/{repo_name:.*?[^/]}/{branch_name:.*?[^/]}/remove', repo_route=True, repo_accepted_types=['hg', 'git'])
|
||||
config.add_view(
|
||||
RepoBranchesView,
|
||||
attr='remove_branch',
|
||||
route_name='branch_remove', request_method='POST'
|
||||
)
|
||||
|
||||
# Bookmarks
|
||||
config.add_route(
|
||||
name='bookmarks_home',
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
import pytest
|
||||
from rhodecode.model.db import Repository
|
||||
from rhodecode.tests.routes import route_path
|
||||
from rhodecode.tests import assert_session_flash
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('autologin_user', 'app')
|
||||
|
|
@ -33,3 +34,50 @@ class TestBranchesController(object):
|
|||
for commit_id, obj_name in repo.scm_instance().branches.items():
|
||||
assert commit_id in response
|
||||
assert obj_name in response
|
||||
|
||||
def test_landing_branch_delete(self, backend, csrf_token):
|
||||
if backend.alias == 'svn':
|
||||
pytest.skip("Not supported yet")
|
||||
branch_related_data_per_backend = {
|
||||
'git': {'name': 'master'},
|
||||
'hg': {'name': 'default'},
|
||||
}
|
||||
response = self.app.post(
|
||||
route_path('branch_remove', repo_name=backend.repo_name,
|
||||
branch_name=branch_related_data_per_backend[backend.alias]['name']),
|
||||
params={'csrf_token': csrf_token}, status=302)
|
||||
assert_session_flash(
|
||||
response,
|
||||
f"This branch {branch_related_data_per_backend[backend.alias]['name']} cannot be removed as it's currently set as landing branch"
|
||||
)
|
||||
|
||||
def test_delete_branch_by_repo_owner(self, backend, csrf_token):
|
||||
if backend.alias in ('svn', 'hg'):
|
||||
pytest.skip("Skipping for hg and svn")
|
||||
branch_to_be_removed = 'remove_me'
|
||||
repo = Repository.get_by_repo_name(backend.repo_name)
|
||||
repo.scm_instance()._create_branch(branch_to_be_removed, repo.scm_instance().commit_ids[1])
|
||||
response = self.app.post(
|
||||
route_path('branch_remove', repo_name=backend.repo_name,
|
||||
branch_name=branch_to_be_removed),
|
||||
params={'csrf_token': csrf_token}, status=302)
|
||||
assert_session_flash(response, f"Branch {branch_to_be_removed} has been successfully deleted")
|
||||
|
||||
def test_delete_branch_by_not_repo_owner(self, backend, csrf_token):
|
||||
username = 'test_regular'
|
||||
pwd = 'test12'
|
||||
branch_related_data_per_backend = {
|
||||
'git': {'name': 'master', 'action': 'deleted'},
|
||||
'hg': {'name': 'stable', 'action': 'closed'},
|
||||
}
|
||||
if backend.alias == 'svn':
|
||||
pytest.skip("Not supported yet")
|
||||
self.app.post(route_path('login'),
|
||||
{'username': username,
|
||||
'password': pwd})
|
||||
selected_branch = branch_related_data_per_backend[backend.alias]['name']
|
||||
response = self.app.post(
|
||||
route_path('branch_remove', repo_name=backend.repo_name,
|
||||
branch_name=selected_branch),
|
||||
params={'csrf_token': csrf_token, 'username': username, 'password': pwd}, status=404)
|
||||
assert response.status_code == 404
|
||||
|
|
|
|||
|
|
@ -18,11 +18,15 @@
|
|||
|
||||
import logging
|
||||
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
|
||||
from rhodecode.apps._base import BaseReferencesView
|
||||
from rhodecode.lib import ext_json
|
||||
from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
|
||||
from rhodecode.lib import helpers as h
|
||||
from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
|
||||
from rhodecode.model.scm import ScmModel
|
||||
from rhodecode.model.meta import Session
|
||||
from rhodecode.model.db import PullRequest
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -33,15 +37,71 @@ class RepoBranchesView(BaseReferencesView):
|
|||
@HasRepoPermissionAnyDecorator(
|
||||
'repository.read', 'repository.write', 'repository.admin')
|
||||
def branches(self):
|
||||
partial_render = self.request.get_partial_renderer(
|
||||
'rhodecode:templates/data_table/_dt_elements.mako')
|
||||
repo_name = self.db_repo_name
|
||||
c = self.load_default_context()
|
||||
self._prepare_and_set_clone_url(c)
|
||||
c.rhodecode_repo = self.rhodecode_vcs_repo
|
||||
c.repository_forks = ScmModel().get_forks(self.db_repo)
|
||||
|
||||
ref_items = self.rhodecode_vcs_repo.branches_all.items()
|
||||
data = self.load_refs_context(
|
||||
ref_items=ref_items, partials_template='branches/branches_data.mako')
|
||||
|
||||
data_with_actions = []
|
||||
if self.db_repo.repo_type != 'svn':
|
||||
for branch in data:
|
||||
branch['action'] = partial_render(
|
||||
f"branch_actions_{self.db_repo.repo_type}", branch['name_raw'], repo_name, closed=branch['closed']
|
||||
)
|
||||
data_with_actions.append(branch)
|
||||
data = data_with_actions
|
||||
c.has_references = bool(data)
|
||||
c.data = ext_json.str_json(data)
|
||||
return self._get_template_context(c)
|
||||
|
||||
@LoginRequired()
|
||||
@HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
|
||||
@CSRFRequired()
|
||||
def remove_branch(self):
|
||||
_ = self.request.translate
|
||||
self.load_default_context()
|
||||
repo = self.db_repo
|
||||
repo_name = self.db_repo_name
|
||||
repo_type = repo.repo_type
|
||||
action = _('deleted') if repo_type == 'git' else _('closed')
|
||||
redirect = HTTPFound(location=self.request.route_path('branches_home', repo_name=repo_name))
|
||||
branch_name = self.request.matchdict.get('branch_name')
|
||||
if repo.landing_ref_name == branch_name:
|
||||
h.flash(
|
||||
_("This branch {} cannot be removed as it's currently set as landing branch").format(branch_name),
|
||||
category='error'
|
||||
)
|
||||
return redirect
|
||||
if prs_related_to := Session().query(PullRequest).filter(PullRequest.target_repo_id == repo.repo_id,
|
||||
PullRequest.status != PullRequest.STATUS_CLOSED).filter(
|
||||
(PullRequest.source_ref.like(f'branch:{branch_name}:%')) | (
|
||||
PullRequest.target_ref.like(f'branch:{branch_name}:%'))
|
||||
).all():
|
||||
h.flash(_("Branch cannot be {} - it's used in following open Pull Request ids: {}").format(action, ','.join(
|
||||
map(str, prs_related_to))), category='error')
|
||||
return redirect
|
||||
|
||||
match repo_type:
|
||||
case 'git':
|
||||
self.rhodecode_vcs_repo.delete_branch(branch_name)
|
||||
case 'hg':
|
||||
from rhodecode.lib.vcs.backends.base import Reference
|
||||
self.rhodecode_vcs_repo._local_close(
|
||||
source_ref=Reference(type='branch', name=branch_name,
|
||||
commit_id=self.rhodecode_vcs_repo.branches[branch_name]),
|
||||
target_ref=Reference(type='branch', name='', commit_id=None),
|
||||
user_name=self.request.user.name,
|
||||
user_email=self.request.user.email)
|
||||
case _:
|
||||
raise NotImplementedError('Branch deleting functionality not yet implemented')
|
||||
ScmModel().mark_for_invalidation(repo_name)
|
||||
self.rhodecode_vcs_repo._invalidate_prop_cache('commit_ids')
|
||||
self.rhodecode_vcs_repo._invalidate_prop_cache('_refs')
|
||||
self.rhodecode_vcs_repo._invalidate_prop_cache('branches')
|
||||
h.flash(_("Branch {} has been successfully {}").format(branch_name, action), category='success')
|
||||
return redirect
|
||||
|
|
|
|||
|
|
@ -326,6 +326,9 @@ class GitRepository(BaseRepository):
|
|||
def _get_branches(self):
|
||||
return self._get_refs_entries(prefix='refs/heads/', strip_prefix=True)
|
||||
|
||||
def delete_branch(self, branch_name):
|
||||
return self._remote.delete_branch(branch_name)
|
||||
|
||||
@CachedProperty
|
||||
def branches(self):
|
||||
return self._get_branches()
|
||||
|
|
|
|||
|
|
@ -4585,6 +4585,12 @@ class PullRequest(Base, _PullRequestBase):
|
|||
else:
|
||||
return f'<DB:PullRequest at {id(self)!r}>'
|
||||
|
||||
def __str__(self):
|
||||
if self.pull_request_id:
|
||||
return f'#{self.pull_request_id}'
|
||||
else:
|
||||
return f'#{id(self)!r}'
|
||||
|
||||
reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan", back_populates='pull_request')
|
||||
statuses = relationship('ChangesetStatus', cascade="all, delete-orphan", back_populates='pull_request')
|
||||
comments = relationship('ChangesetComment', cascade="all, delete-orphan", back_populates='pull_request')
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ function registerRCRoutes() {
|
|||
pyroutes.register('auth_home', '/_admin/auth*traverse', []);
|
||||
pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
|
||||
pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
|
||||
pyroutes.register('branch_remove', '/%(repo_name)s/%(branch_name)s/remove', ['repo_name', 'branch_name']);
|
||||
pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
|
||||
pyroutes.register('channelstream_proxy', '/_channelstream', []);
|
||||
pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
|
||||
|
|
|
|||
|
|
@ -62,13 +62,8 @@ $(document).ready(function() {
|
|||
};
|
||||
|
||||
var branches_data = ${c.data|n};
|
||||
// object list
|
||||
$('#obj_list_table').DataTable({
|
||||
data: branches_data,
|
||||
dom: 'rtp',
|
||||
pageLength: ${c.visual.dashboard_items},
|
||||
order: [[ 0, "asc" ]],
|
||||
columns: [
|
||||
var repo_type = "${c.rhodecode_db_repo.repo_type}";
|
||||
var columns = [
|
||||
{ data: {"_": "name",
|
||||
"sort": "name_raw"}, title: "${_('Name')}", className: "td-tags" },
|
||||
{ data: {"_": "date",
|
||||
|
|
@ -80,7 +75,22 @@ $(document).ready(function() {
|
|||
"type": Number}, title: "${_('Commit')}", className: "td-hash" },
|
||||
{ data: {"_": "compare",
|
||||
"sort": "compare"}, title: "${_('Compare')}", className: "td-compare" }
|
||||
],
|
||||
];
|
||||
if (repo_type !== 'svn') {
|
||||
columns.push({
|
||||
data: { "_": "action", "sort": "action" },
|
||||
title: `${_('Action')}`,
|
||||
className: "td-action",
|
||||
orderable: false
|
||||
});
|
||||
}
|
||||
|
||||
$('#obj_list_table').DataTable({
|
||||
data: branches_data,
|
||||
dom: 'rtp',
|
||||
pageLength: ${c.visual.dashboard_items},
|
||||
order: [[ 0, "asc" ]],
|
||||
columns: columns,
|
||||
language: {
|
||||
paginate: DEFAULT_GRID_PAGINATION,
|
||||
emptyTable: _gettext("No branches available yet.")
|
||||
|
|
|
|||
|
|
@ -277,6 +277,30 @@ ${h.style_metatag(tag_type, tag)|n,trim}
|
|||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="branch_actions_git(branch_name, repo_name, **kwargs)">
|
||||
<div class="grid_delete">
|
||||
${h.secure_form(h.route_path('branch_remove', repo_name=repo_name, branch_name=branch_name), request=request)}
|
||||
<input class="btn btn-link btn-danger" id="remove_branch_${branch_name}" name="remove_branch_${branch_name}"
|
||||
onclick="submitConfirm(event, this, _gettext('Confirm to delete this branch'), _gettext('Delete'), '${branch_name}')"
|
||||
type="submit" value="Delete"
|
||||
>
|
||||
${h.end_form()}
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="branch_actions_hg(branch_name, repo_name, **kwargs)">
|
||||
<div class="grid_delete">
|
||||
%if not kwargs['closed']:
|
||||
${h.secure_form(h.route_path('branch_remove', repo_name=repo_name, branch_name=branch_name), request=request)}
|
||||
<input class="btn btn-link btn-danger" id="remove_branch_${branch_name}" name="remove_branch_${branch_name}"
|
||||
onclick="submitConfirm(event, this, _gettext('Confirm to close this branch'), _gettext('Close'), '${branch_name}')"
|
||||
type="submit" value="Close"
|
||||
>
|
||||
${h.end_form()}
|
||||
%endif
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="user_group_actions(user_group_id, user_group_name)">
|
||||
<div class="grid_edit">
|
||||
<a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
|
||||
|
|
|
|||
|
|
@ -252,6 +252,7 @@ def get_url_defs():
|
|||
"pullrequest_show_all_data": "/{repo_name}/pull-request-data",
|
||||
"bookmarks_home": "/{repo_name}/bookmarks",
|
||||
"branches_home": "/{repo_name}/branches",
|
||||
"branch_remove": "/{repo_name}/{branch_name}/remove",
|
||||
"tags_home": "/{repo_name}/tags",
|
||||
"repo_changelog": "/{repo_name}/changelog",
|
||||
"repo_commits": "/{repo_name}/commits",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue