Show More
@@ -0,0 +1,36 b'' | |||
|
1 | import logging | |
|
2 | ||
|
3 | from sqlalchemy import * | |
|
4 | ||
|
5 | from rhodecode.model import meta | |
|
6 | from rhodecode.lib.dbmigrate.versions import _reset_base, notify | |
|
7 | ||
|
8 | log = logging.getLogger(__name__) | |
|
9 | ||
|
10 | ||
|
11 | def upgrade(migrate_engine): | |
|
12 | """ | |
|
13 | Upgrade operations go here. | |
|
14 | Don't create your own engine; bind migrate_engine to your metadata | |
|
15 | """ | |
|
16 | _reset_base(migrate_engine) | |
|
17 | from rhodecode.lib.dbmigrate.schema import db_4_13_0_0 as db | |
|
18 | ||
|
19 | repository_table = db.Repository.__table__ | |
|
20 | ||
|
21 | archived = Column('archived', Boolean(), nullable=True) | |
|
22 | archived.create(table=repository_table) | |
|
23 | ||
|
24 | # issue fixups | |
|
25 | fixups(db, meta.Session) | |
|
26 | ||
|
27 | ||
|
28 | def downgrade(migrate_engine): | |
|
29 | meta = MetaData() | |
|
30 | meta.bind = migrate_engine | |
|
31 | ||
|
32 | ||
|
33 | def fixups(models, _SESSION): | |
|
34 | pass | |
|
35 | ||
|
36 |
@@ -51,7 +51,7 b' PYRAMID_SETTINGS = {}' | |||
|
51 | 51 | EXTENSIONS = {} |
|
52 | 52 | |
|
53 | 53 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) |
|
54 |
__dbversion__ = 9 |
|
|
54 | __dbversion__ = 91 # defines current db version for migrations | |
|
55 | 55 | __platform__ = platform.system() |
|
56 | 56 | __license__ = 'AGPLv3, and Commercial License' |
|
57 | 57 | __author__ = 'RhodeCode GmbH' |
@@ -505,6 +505,36 b' class RepoRoutePredicate(object):' | |||
|
505 | 505 | return False |
|
506 | 506 | |
|
507 | 507 | |
|
508 | class RepoForbidArchivedRoutePredicate(object): | |
|
509 | def __init__(self, val, config): | |
|
510 | self.val = val | |
|
511 | ||
|
512 | def text(self): | |
|
513 | return 'repo_forbid_archived = %s' % self.val | |
|
514 | ||
|
515 | phash = text | |
|
516 | ||
|
517 | def __call__(self, info, request): | |
|
518 | _ = request.translate | |
|
519 | rhodecode_db_repo = request.db_repo | |
|
520 | ||
|
521 | log.debug( | |
|
522 | '%s checking if archived flag for repo for %s', | |
|
523 | self.__class__.__name__, rhodecode_db_repo.repo_name) | |
|
524 | ||
|
525 | if rhodecode_db_repo.archived: | |
|
526 | log.warning('Current view is not supported for archived repo:%s', | |
|
527 | rhodecode_db_repo.repo_name) | |
|
528 | ||
|
529 | h.flash( | |
|
530 | h.literal(_('Action not supported for archived repository.')), | |
|
531 | category='warning') | |
|
532 | summary_url = request.route_path( | |
|
533 | 'repo_summary', repo_name=rhodecode_db_repo.repo_name) | |
|
534 | raise HTTPFound(summary_url) | |
|
535 | return True | |
|
536 | ||
|
537 | ||
|
508 | 538 | class RepoTypeRoutePredicate(object): |
|
509 | 539 | def __init__(self, val, config): |
|
510 | 540 | self.val = val or ['hg', 'git', 'svn'] |
@@ -530,13 +560,6 b' class RepoTypeRoutePredicate(object):' | |||
|
530 | 560 | else: |
|
531 | 561 | log.warning('Current view is not supported for repo type:%s', |
|
532 | 562 | rhodecode_db_repo.repo_type) |
|
533 | ||
|
534 | # h.flash(h.literal( | |
|
535 | # _('Action not supported for %s.' % rhodecode_repo.alias)), | |
|
536 | # category='warning') | |
|
537 | # return redirect( | |
|
538 | # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name)) | |
|
539 | ||
|
540 | 563 | return False |
|
541 | 564 | |
|
542 | 565 | |
@@ -643,10 +666,12 b' def includeme(config):' | |||
|
643 | 666 | config.add_route_predicate( |
|
644 | 667 | 'repo_accepted_types', RepoTypeRoutePredicate) |
|
645 | 668 | config.add_route_predicate( |
|
669 | 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate) | |
|
670 | config.add_route_predicate( | |
|
646 | 671 | 'repo_group_route', RepoGroupRoutePredicate) |
|
647 | 672 | config.add_route_predicate( |
|
648 | 673 | 'user_group_route', UserGroupRoutePredicate) |
|
649 | 674 | config.add_route_predicate( |
|
650 | 675 | 'user_route_with_default', UserRouteWithDefaultPredicate) |
|
651 | 676 | config.add_route_predicate( |
|
652 | 'user_route', UserRoutePredicate) No newline at end of file | |
|
677 | 'user_route', UserRoutePredicate) |
@@ -33,7 +33,7 b' from rhodecode.lib.index import searcher' | |||
|
33 | 33 | from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int |
|
34 | 34 | from rhodecode.lib.ext_json import json |
|
35 | 35 | from rhodecode.model.db import ( |
|
36 | func, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup) | |
|
36 | func, true, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup) | |
|
37 | 37 | from rhodecode.model.repo import RepoModel |
|
38 | 38 | from rhodecode.model.repo_group import RepoGroupModel |
|
39 | 39 | from rhodecode.model.scm import RepoGroupList, RepoList |
@@ -114,6 +114,7 b' class HomeView(BaseAppView):' | |||
|
114 | 114 | query = Repository.query()\ |
|
115 | 115 | .order_by(func.length(Repository.repo_name))\ |
|
116 | 116 | .order_by(Repository.repo_name)\ |
|
117 | .filter(Repository.archived.isnot(true()))\ | |
|
117 | 118 | .filter(or_( |
|
118 | 119 | # generate multiple IN to fix limitation problems |
|
119 | 120 | *in_filter_generator(Repository.repo_id, allowed_ids) |
@@ -231,11 +231,13 b' def includeme(config):' | |||
|
231 | 231 | config.add_route( |
|
232 | 232 | name='repo_fork_new', |
|
233 | 233 | pattern='/{repo_name:.*?[^/]}/fork', repo_route=True, |
|
234 | repo_forbid_when_archived=True, | |
|
234 | 235 | repo_accepted_types=['hg', 'git']) |
|
235 | 236 | |
|
236 | 237 | config.add_route( |
|
237 | 238 | name='repo_fork_create', |
|
238 | 239 | pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True, |
|
240 | repo_forbid_when_archived=True, | |
|
239 | 241 | repo_accepted_types=['hg', 'git']) |
|
240 | 242 | |
|
241 | 243 | config.add_route( |
@@ -276,27 +278,29 b' def includeme(config):' | |||
|
276 | 278 | config.add_route( |
|
277 | 279 | name='pullrequest_new', |
|
278 | 280 | pattern='/{repo_name:.*?[^/]}/pull-request/new', |
|
279 |
repo_route=True, repo_accepted_types=['hg', 'git'] |
|
|
281 | repo_route=True, repo_accepted_types=['hg', 'git'], | |
|
282 | repo_forbid_when_archived=True) | |
|
280 | 283 | |
|
281 | 284 | config.add_route( |
|
282 | 285 | name='pullrequest_create', |
|
283 | 286 | pattern='/{repo_name:.*?[^/]}/pull-request/create', |
|
284 |
repo_route=True, repo_accepted_types=['hg', 'git'] |
|
|
287 | repo_route=True, repo_accepted_types=['hg', 'git'], | |
|
288 | repo_forbid_when_archived=True) | |
|
285 | 289 | |
|
286 | 290 | config.add_route( |
|
287 | 291 | name='pullrequest_update', |
|
288 | 292 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update', |
|
289 | repo_route=True) | |
|
293 | repo_route=True, repo_forbid_when_archived=True) | |
|
290 | 294 | |
|
291 | 295 | config.add_route( |
|
292 | 296 | name='pullrequest_merge', |
|
293 | 297 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge', |
|
294 | repo_route=True) | |
|
298 | repo_route=True, repo_forbid_when_archived=True) | |
|
295 | 299 | |
|
296 | 300 | config.add_route( |
|
297 | 301 | name='pullrequest_delete', |
|
298 | 302 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete', |
|
299 | repo_route=True) | |
|
303 | repo_route=True, repo_forbid_when_archived=True) | |
|
300 | 304 | |
|
301 | 305 | config.add_route( |
|
302 | 306 | name='pullrequest_comment_create', |
@@ -319,6 +323,9 b' def includeme(config):' | |||
|
319 | 323 | name='edit_repo_advanced', |
|
320 | 324 | pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True) |
|
321 | 325 | config.add_route( |
|
326 | name='edit_repo_advanced_archive', | |
|
327 | pattern='/{repo_name:.*?[^/]}/settings/advanced/archive', repo_route=True) | |
|
328 | config.add_route( | |
|
322 | 329 | name='edit_repo_advanced_delete', |
|
323 | 330 | pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True) |
|
324 | 331 | config.add_route( |
@@ -303,6 +303,27 b' class TestRepoForkViewTests(TestControll' | |||
|
303 | 303 | assert response.json == {u'data': [], u'draw': None, |
|
304 | 304 | u'recordsFiltered': 0, u'recordsTotal': 0} |
|
305 | 305 | |
|
306 | @pytest.mark.parametrize('url_type', [ | |
|
307 | 'repo_fork_new', | |
|
308 | 'repo_fork_create' | |
|
309 | ]) | |
|
310 | def test_fork_is_forbidden_on_archived_repo(self, backend, xhr_header, user_util, url_type): | |
|
311 | user = user_util.create_user(password='qweqwe') | |
|
312 | self.log_user(user.username, 'qweqwe') | |
|
313 | ||
|
314 | # create a temporary repo | |
|
315 | source = user_util.create_repo(repo_type=backend.alias) | |
|
316 | repo_name = source.repo_name | |
|
317 | repo = Repository.get_by_repo_name(repo_name) | |
|
318 | repo.archived = True | |
|
319 | Session().commit() | |
|
320 | ||
|
321 | response = self.app.get( | |
|
322 | route_path(url_type, repo_name=repo_name), status=302) | |
|
323 | ||
|
324 | msg = 'Action not supported for archived repository.' | |
|
325 | assert_session_flash(response, msg) | |
|
326 | ||
|
306 | 327 | |
|
307 | 328 | class TestSVNFork(TestController): |
|
308 | 329 | @pytest.mark.parametrize('route_name', [ |
@@ -26,7 +26,7 b' from rhodecode.lib.vcs.nodes import File' | |||
|
26 | 26 | from rhodecode.lib import helpers as h |
|
27 | 27 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
28 | 28 | from rhodecode.model.db import ( |
|
29 | PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment) | |
|
29 | PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment, Repository) | |
|
30 | 30 | from rhodecode.model.meta import Session |
|
31 | 31 | from rhodecode.model.pull_request import PullRequestModel |
|
32 | 32 | from rhodecode.model.user import UserModel |
@@ -1191,6 +1191,28 b' class TestPullrequestsControllerDelete(o' | |||
|
1191 | 1191 | ) |
|
1192 | 1192 | assert response.body == 'true' |
|
1193 | 1193 | |
|
1194 | @pytest.mark.parametrize('url_type', [ | |
|
1195 | 'pullrequest_new', | |
|
1196 | 'pullrequest_create', | |
|
1197 | 'pullrequest_update', | |
|
1198 | 'pullrequest_merge', | |
|
1199 | ]) | |
|
1200 | def test_pull_request_is_forbidden_on_archived_repo( | |
|
1201 | self, autologin_user, backend, xhr_header, user_util, url_type): | |
|
1202 | ||
|
1203 | # create a temporary repo | |
|
1204 | source = user_util.create_repo(repo_type=backend.alias) | |
|
1205 | repo_name = source.repo_name | |
|
1206 | repo = Repository.get_by_repo_name(repo_name) | |
|
1207 | repo.archived = True | |
|
1208 | Session().commit() | |
|
1209 | ||
|
1210 | response = self.app.get( | |
|
1211 | route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302) | |
|
1212 | ||
|
1213 | msg = 'Action not supported for archived repository.' | |
|
1214 | assert_session_flash(response, msg) | |
|
1215 | ||
|
1194 | 1216 | |
|
1195 | 1217 | def assert_pull_request_status(pull_request, expected_status): |
|
1196 | 1218 | status = ChangesetStatusModel().calculated_review_status( |
@@ -39,6 +39,7 b' def route_path(name, params=None, **kwar' | |||
|
39 | 39 | 'repo_summary': '/{repo_name}', |
|
40 | 40 | 'edit_repo_advanced': '/{repo_name}/settings/advanced', |
|
41 | 41 | 'edit_repo_advanced_delete': '/{repo_name}/settings/advanced/delete', |
|
42 | 'edit_repo_advanced_archive': '/{repo_name}/settings/advanced/archive', | |
|
42 | 43 | 'edit_repo_advanced_fork': '/{repo_name}/settings/advanced/fork', |
|
43 | 44 | 'edit_repo_advanced_locking': '/{repo_name}/settings/advanced/locking', |
|
44 | 45 | 'edit_repo_advanced_journal': '/{repo_name}/settings/advanced/journal', |
@@ -133,7 +134,7 b' class TestAdminRepoSettingsAdvanced(obje' | |||
|
133 | 134 | "suffix", |
|
134 | 135 | ['', u'Δ ΔΕ' , '123'], |
|
135 | 136 | ids=no_newline_id_generator) |
|
136 | def test_advanced_delete(self, autologin_user, backend, suffix, csrf_token): | |
|
137 | def test_advanced_repo_delete(self, autologin_user, backend, suffix, csrf_token): | |
|
137 | 138 | repo = backend.create_repo(name_suffix=suffix) |
|
138 | 139 | repo_name = repo.repo_name |
|
139 | 140 | repo_name_str = safe_str(repo.repo_name) |
@@ -148,3 +149,25 b' class TestAdminRepoSettingsAdvanced(obje' | |||
|
148 | 149 | # check if repo was deleted from db |
|
149 | 150 | assert RepoModel().get_by_repo_name(repo_name) is None |
|
150 | 151 | assert not repo_on_filesystem(repo_name_str) |
|
152 | ||
|
153 | @pytest.mark.parametrize( | |
|
154 | "suffix", | |
|
155 | ['', u'Δ ΔΕ' , '123'], | |
|
156 | ids=no_newline_id_generator) | |
|
157 | def test_advanced_repo_archive(self, autologin_user, backend, suffix, csrf_token): | |
|
158 | repo = backend.create_repo(name_suffix=suffix) | |
|
159 | repo_name = repo.repo_name | |
|
160 | repo_name_str = safe_str(repo.repo_name) | |
|
161 | ||
|
162 | response = self.app.post( | |
|
163 | route_path('edit_repo_advanced_archive', repo_name=repo_name_str), | |
|
164 | params={'csrf_token': csrf_token}) | |
|
165 | ||
|
166 | assert_session_flash(response, | |
|
167 | u'Archived repository `{}`'.format(repo_name)) | |
|
168 | ||
|
169 | response = self.app.get(route_path('repo_summary', repo_name=repo_name_str)) | |
|
170 | response.mustcontain('This repository has been archived. It is now read-only.') | |
|
171 | ||
|
172 | # check if repo was deleted from db | |
|
173 | assert RepoModel().get_by_repo_name(repo_name).archived is True |
@@ -23,6 +23,7 b' import logging' | |||
|
23 | 23 | from pyramid.view import view_config |
|
24 | 24 | from pyramid.httpexceptions import HTTPFound |
|
25 | 25 | |
|
26 | from rhodecode import events | |
|
26 | 27 | from rhodecode.apps._base import RepoAppView |
|
27 | 28 | from rhodecode.lib import helpers as h |
|
28 | 29 | from rhodecode.lib import audit_logger |
@@ -45,6 +46,13 b' class RepoSettingsView(RepoAppView):' | |||
|
45 | 46 | c = self._get_local_tmpl_context() |
|
46 | 47 | return c |
|
47 | 48 | |
|
49 | def _get_users_with_permissions(self): | |
|
50 | user_permissions = {} | |
|
51 | for perm in self.db_repo.permissions(): | |
|
52 | user_permissions[perm.user_id] = perm | |
|
53 | ||
|
54 | return user_permissions | |
|
55 | ||
|
48 | 56 | @LoginRequired() |
|
49 | 57 | @HasRepoPermissionAnyDecorator('repository.admin') |
|
50 | 58 | @view_config( |
@@ -71,6 +79,49 b' class RepoSettingsView(RepoAppView):' | |||
|
71 | 79 | @HasRepoPermissionAnyDecorator('repository.admin') |
|
72 | 80 | @CSRFRequired() |
|
73 | 81 | @view_config( |
|
82 | route_name='edit_repo_advanced_archive', request_method='POST', | |
|
83 | renderer='rhodecode:templates/admin/repos/repo_edit.mako') | |
|
84 | def edit_advanced_archive(self): | |
|
85 | """ | |
|
86 | Archives the repository. It will become read-only, and not visible in search | |
|
87 | or other queries. But still visible for super-admins. | |
|
88 | """ | |
|
89 | ||
|
90 | _ = self.request.translate | |
|
91 | ||
|
92 | try: | |
|
93 | old_data = self.db_repo.get_api_data() | |
|
94 | RepoModel().archive(self.db_repo) | |
|
95 | ||
|
96 | repo = audit_logger.RepoWrap(repo_id=None, repo_name=self.db_repo.repo_name) | |
|
97 | audit_logger.store_web( | |
|
98 | 'repo.archive', action_data={'old_data': old_data}, | |
|
99 | user=self._rhodecode_user, repo=repo) | |
|
100 | ||
|
101 | ScmModel().mark_for_invalidation(self.db_repo_name, delete=True) | |
|
102 | h.flash( | |
|
103 | _('Archived repository `%s`') % self.db_repo_name, | |
|
104 | category='success') | |
|
105 | Session().commit() | |
|
106 | except Exception: | |
|
107 | log.exception("Exception during archiving of repository") | |
|
108 | h.flash(_('An error occurred during archiving of `%s`') | |
|
109 | % self.db_repo_name, category='error') | |
|
110 | # redirect to advanced for more deletion options | |
|
111 | raise HTTPFound( | |
|
112 | h.route_path('edit_repo_advanced', repo_name=self.db_repo_name, | |
|
113 | _anchor='advanced-archive')) | |
|
114 | ||
|
115 | # flush permissions for all users defined in permissions | |
|
116 | affected_user_ids = self._get_users_with_permissions().keys() | |
|
117 | events.trigger(events.UserPermissionsChange(affected_user_ids)) | |
|
118 | ||
|
119 | raise HTTPFound(h.route_path('home')) | |
|
120 | ||
|
121 | @LoginRequired() | |
|
122 | @HasRepoPermissionAnyDecorator('repository.admin') | |
|
123 | @CSRFRequired() | |
|
124 | @view_config( | |
|
74 | 125 | route_name='edit_repo_advanced_delete', request_method='POST', |
|
75 | 126 | renderer='rhodecode:templates/admin/repos/repo_edit.mako') |
|
76 | 127 | def edit_advanced_delete(self): |
@@ -65,6 +65,7 b' ACTIONS_V1 = {' | |||
|
65 | 65 | 'repo.edit': {'old_data': {}}, |
|
66 | 66 | 'repo.edit.permissions': {}, |
|
67 | 67 | 'repo.edit.permissions.branch': {}, |
|
68 | 'repo.archive': {'old_data': {}}, | |
|
68 | 69 | 'repo.delete': {'old_data': {}}, |
|
69 | 70 | |
|
70 | 71 | 'repo.archive.download': {'user_agent': '', 'archive_name': '', |
@@ -322,6 +322,7 b' def _cached_perms_data(user_id, scope, u' | |||
|
322 | 322 | |
|
323 | 323 | class PermOrigin(object): |
|
324 | 324 | SUPER_ADMIN = 'superadmin' |
|
325 | ARCHIVED = 'archived' | |
|
325 | 326 | |
|
326 | 327 | REPO_USER = 'user:%s' |
|
327 | 328 | REPO_USERGROUP = 'usergroup:%s' |
@@ -463,8 +464,14 b' class PermissionCalculator(object):' | |||
|
463 | 464 | # repositories |
|
464 | 465 | for perm in self.default_repo_perms: |
|
465 | 466 | r_k = perm.UserRepoToPerm.repository.repo_name |
|
467 | archived = perm.UserRepoToPerm.repository.archived | |
|
466 | 468 | p = 'repository.admin' |
|
467 | 469 | self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN |
|
470 | # special case for archived repositories, which we block still even for | |
|
471 | # super admins | |
|
472 | if archived: | |
|
473 | p = 'repository.read' | |
|
474 | self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED | |
|
468 | 475 | |
|
469 | 476 | # repository groups |
|
470 | 477 | for perm in self.default_repo_groups_perms: |
@@ -572,6 +579,7 b' class PermissionCalculator(object):' | |||
|
572 | 579 | def _calculate_default_permissions_repositories(self, user_inherit_object_permissions): |
|
573 | 580 | for perm in self.default_repo_perms: |
|
574 | 581 | r_k = perm.UserRepoToPerm.repository.repo_name |
|
582 | archived = perm.UserRepoToPerm.repository.archived | |
|
575 | 583 | p = perm.Permission.permission_name |
|
576 | 584 | o = PermOrigin.REPO_DEFAULT |
|
577 | 585 | self.permissions_repositories[r_k] = p, o |
@@ -602,6 +610,15 b' class PermissionCalculator(object):' | |||
|
602 | 610 | o = PermOrigin.SUPER_ADMIN |
|
603 | 611 | self.permissions_repositories[r_k] = p, o |
|
604 | 612 | |
|
613 | # finally in case of archived repositories, we downgrade higher | |
|
614 | # permissions to read | |
|
615 | if archived: | |
|
616 | current_perm = self.permissions_repositories[r_k] | |
|
617 | if current_perm in ['repository.write', 'repository.admin']: | |
|
618 | p = 'repository.read' | |
|
619 | o = PermOrigin.ARCHIVED | |
|
620 | self.permissions_repositories[r_k] = p, o | |
|
621 | ||
|
605 | 622 | def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions): |
|
606 | 623 | for perm in self.default_branch_repo_perms: |
|
607 | 624 |
@@ -1585,6 +1585,8 b' class Repository(Base, BaseModel):' | |||
|
1585 | 1585 | unique=False, default=None) |
|
1586 | 1586 | private = Column( |
|
1587 | 1587 | "private", Boolean(), nullable=True, unique=None, default=None) |
|
1588 | archived = Column( | |
|
1589 | "archived", Boolean(), nullable=True, unique=None, default=None) | |
|
1588 | 1590 | enable_statistics = Column( |
|
1589 | 1591 | "statistics", Boolean(), nullable=True, unique=None, default=True) |
|
1590 | 1592 | enable_downloads = Column( |
@@ -1783,9 +1785,12 b' class Repository(Base, BaseModel):' | |||
|
1783 | 1785 | |
|
1784 | 1786 | @classmethod |
|
1785 | 1787 | def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None), |
|
1786 | case_insensitive=True): | |
|
1788 | case_insensitive=True, archived=False): | |
|
1787 | 1789 | q = Repository.query() |
|
1788 | 1790 | |
|
1791 | if not archived: | |
|
1792 | q = q.filter(Repository.archived.isnot(true())) | |
|
1793 | ||
|
1789 | 1794 | if not isinstance(user_id, Optional): |
|
1790 | 1795 | q = q.filter(Repository.user_id == user_id) |
|
1791 | 1796 | |
@@ -1796,6 +1801,7 b' class Repository(Base, BaseModel):' | |||
|
1796 | 1801 | q = q.order_by(func.lower(Repository.repo_name)) |
|
1797 | 1802 | else: |
|
1798 | 1803 | q = q.order_by(Repository.repo_name) |
|
1804 | ||
|
1799 | 1805 | return q.all() |
|
1800 | 1806 | |
|
1801 | 1807 | @property |
@@ -207,8 +207,8 b' class RepoModel(BaseModel):' | |||
|
207 | 207 | def quick_menu(repo_name): |
|
208 | 208 | return _render('quick_menu', repo_name) |
|
209 | 209 | |
|
210 | def repo_lnk(name, rtype, rstate, private, fork_of): | |
|
211 | return _render('repo_name', name, rtype, rstate, private, fork_of, | |
|
210 | def repo_lnk(name, rtype, rstate, private, archived, fork_of): | |
|
211 | return _render('repo_name', name, rtype, rstate, private, archived, fork_of, | |
|
212 | 212 | short_name=not admin, admin=False) |
|
213 | 213 | |
|
214 | 214 | def last_change(last_change): |
@@ -246,8 +246,8 b' class RepoModel(BaseModel):' | |||
|
246 | 246 | row = { |
|
247 | 247 | "menu": quick_menu(repo.repo_name), |
|
248 | 248 | |
|
249 | "name": repo_lnk(repo.repo_name, repo.repo_type, | |
|
250 |
repo. |
|
|
249 | "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state, | |
|
250 | repo.private, repo.archived, repo.fork), | |
|
251 | 251 | "name_raw": repo.repo_name.lower(), |
|
252 | 252 | |
|
253 | 253 | "last_change": last_change(repo.last_db_change), |
@@ -427,6 +427,7 b' class RepoModel(BaseModel):' | |||
|
427 | 427 | new_repo.group = repo_group |
|
428 | 428 | new_repo.description = description or repo_name |
|
429 | 429 | new_repo.private = private |
|
430 | new_repo.archived = False | |
|
430 | 431 | new_repo.clone_uri = clone_uri |
|
431 | 432 | new_repo.landing_rev = landing_rev |
|
432 | 433 | |
@@ -608,6 +609,23 b' class RepoModel(BaseModel):' | |||
|
608 | 609 | from rhodecode.lib.celerylib import tasks, run_task |
|
609 | 610 | return run_task(tasks.create_repo_fork, form_data, cur_user) |
|
610 | 611 | |
|
612 | def archive(self, repo): | |
|
613 | """ | |
|
614 | Archive given repository. Set archive flag. | |
|
615 | ||
|
616 | :param repo: | |
|
617 | """ | |
|
618 | repo = self._get_repo(repo) | |
|
619 | if repo: | |
|
620 | ||
|
621 | try: | |
|
622 | repo.archived = True | |
|
623 | self.sa.add(repo) | |
|
624 | self.sa.commit() | |
|
625 | except Exception: | |
|
626 | log.error(traceback.format_exc()) | |
|
627 | raise | |
|
628 | ||
|
611 | 629 | def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None): |
|
612 | 630 | """ |
|
613 | 631 | Delete given repository, forks parameter defines what do do with |
@@ -616,6 +634,7 b' class RepoModel(BaseModel):' | |||
|
616 | 634 | |
|
617 | 635 | :param repo: |
|
618 | 636 | :param forks: str 'delete' or 'detach' |
|
637 | :param pull_requests: str 'delete' or None | |
|
619 | 638 | :param fs_remove: remove(archive) repo from filesystem |
|
620 | 639 | """ |
|
621 | 640 | if not cur_user: |
@@ -228,6 +228,7 b' function registerRCRoutes() {' | |||
|
228 | 228 | pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']); |
|
229 | 229 | pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); |
|
230 | 230 | pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); |
|
231 | pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']); | |
|
231 | 232 | pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']); |
|
232 | 233 | pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']); |
|
233 | 234 | pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']); |
@@ -98,7 +98,7 b'' | |||
|
98 | 98 | <button class="btn btn-small" type="submit" |
|
99 | 99 | onclick="return confirm('${_('Confirm to lock repository.')}');"> |
|
100 | 100 | <i class="icon-lock"></i> |
|
101 |
${_('Lock |
|
|
101 | ${_('Lock repository')} | |
|
102 | 102 | </button> |
|
103 | 103 | %endif |
|
104 | 104 | </div> |
@@ -111,6 +111,35 b'' | |||
|
111 | 111 | </div> |
|
112 | 112 | </div> |
|
113 | 113 | |
|
114 | ||
|
115 | <div class="panel panel-warning"> | |
|
116 | <div class="panel-heading" id="advanced-archive"> | |
|
117 | <h3 class="panel-title">${_('Archive repository')} <a class="permalink" href="#advanced-archive"> ΒΆ</a></h3> | |
|
118 | </div> | |
|
119 | <div class="panel-body"> | |
|
120 | ${h.secure_form(h.route_path('edit_repo_advanced_archive', repo_name=c.repo_name), request=request)} | |
|
121 | ||
|
122 | <div style="margin: 0 0 20px 0" class="fake-space"></div> | |
|
123 | ||
|
124 | <div class="field"> | |
|
125 | <button class="btn btn-small btn-danger" type="submit" | |
|
126 | onclick="return confirm('${_('Confirm to archive this repository: %s') % c.repo_name}');"> | |
|
127 | <i class="icon-remove-sign"></i> | |
|
128 | ${_('Archive this repository')} | |
|
129 | </button> | |
|
130 | </div> | |
|
131 | <div class="field"> | |
|
132 | <span class="help-block"> | |
|
133 | ${_('Archiving the repository will make it entirely read-only. The repository cannot be committed to.' | |
|
134 | 'It is hidden from the search results and dashboard. ')} | |
|
135 | </span> | |
|
136 | </div> | |
|
137 | ||
|
138 | ${h.end_form()} | |
|
139 | </div> | |
|
140 | </div> | |
|
141 | ||
|
142 | ||
|
114 | 143 | <div class="panel panel-danger"> |
|
115 | 144 | <div class="panel-heading" id="advanced-delete"> |
|
116 | 145 | <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"> ΒΆ</a></h3> |
@@ -152,7 +181,7 b'' | |||
|
152 | 181 | <button class="btn btn-small btn-danger" type="submit" |
|
153 | 182 | onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');"> |
|
154 | 183 | <i class="icon-remove-sign"></i> |
|
155 |
${_('Delete |
|
|
184 | ${_('Delete this repository')} | |
|
156 | 185 | </button> |
|
157 | 186 | </div> |
|
158 | 187 | <div class="field"> |
@@ -287,6 +287,11 b'' | |||
|
287 | 287 | </div> |
|
288 | 288 | <div class="clear"></div> |
|
289 | 289 | </div> |
|
290 | % if c.rhodecode_db_repo.archived: | |
|
291 | <div class="alert alert-warning text-center"> | |
|
292 | <strong>${_('This repository has been archived. It is now read-only.')}</strong> | |
|
293 | </div> | |
|
294 | % endif | |
|
290 | 295 | <!--- END CONTEXT BAR --> |
|
291 | 296 | |
|
292 | 297 | </%def> |
@@ -66,7 +66,7 b'' | |||
|
66 | 66 | </div> |
|
67 | 67 | </%def> |
|
68 | 68 | |
|
69 | <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)"> | |
|
69 | <%def name="repo_name(name,rtype,rstate,private,archived,fork_of,short_name=False,admin=False)"> | |
|
70 | 70 | <% |
|
71 | 71 | def get_name(name,short_name=short_name): |
|
72 | 72 | if short_name: |
@@ -105,6 +105,7 b'' | |||
|
105 | 105 | (${_('creating...')}) |
|
106 | 106 | </span> |
|
107 | 107 | %endif |
|
108 | ||
|
108 | 109 | </div> |
|
109 | 110 | </%def> |
|
110 | 111 |
@@ -561,7 +561,7 b' def test_get_repo_by_id(test, expected):' | |||
|
561 | 561 | |
|
562 | 562 | |
|
563 | 563 | def test_invalidation_context(baseapp): |
|
564 | repo_id = 999 | |
|
564 | repo_id = 9999 | |
|
565 | 565 | |
|
566 | 566 | cache_namespace_uid = 'cache_repo_instance.{}_{}'.format( |
|
567 | 567 | repo_id, CacheKey.CACHE_TYPE_README) |
@@ -25,10 +25,13 b' import pytest' | |||
|
25 | 25 | from rhodecode.lib.vcs.backends.git.repository import GitRepository |
|
26 | 26 | from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository |
|
27 | 27 | from rhodecode.lib.vcs.nodes import FileNode |
|
28 | from rhodecode.model.db import Repository | |
|
28 | 29 | from rhodecode.model.meta import Session |
|
30 | from rhodecode.tests import GIT_REPO, HG_REPO | |
|
29 | 31 | |
|
30 | 32 | from rhodecode.tests.vcs_operations import ( |
|
31 |
Command, _check_proper_clone, _check_proper_git_push, _check_proper_hg_push |
|
|
33 | Command, _check_proper_clone, _check_proper_git_push, _check_proper_hg_push, | |
|
34 | _add_files_and_push) | |
|
32 | 35 | |
|
33 | 36 | |
|
34 | 37 | @pytest.mark.usefixtures("disable_locking") |
@@ -242,3 +245,37 b' class TestVCSOperationsSpecial(object):' | |||
|
242 | 245 | assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout |
|
243 | 246 | assert 'remote: RhodeCode: push completed' in stdout |
|
244 | 247 | assert 'exporting bookmark feature2' in stdout |
|
248 | ||
|
249 | def test_push_is_forbidden_on_archived_repo_hg(self, backend_hg, rc_web_server, tmpdir): | |
|
250 | empty_repo = backend_hg.create_repo() | |
|
251 | repo_name = empty_repo.repo_name | |
|
252 | ||
|
253 | repo = Repository.get_by_repo_name(repo_name) | |
|
254 | repo.archived = True | |
|
255 | Session().commit() | |
|
256 | ||
|
257 | clone_url = rc_web_server.repo_clone_url(repo_name) | |
|
258 | stdout, stderr = Command('/tmp').execute( | |
|
259 | 'hg clone', clone_url, tmpdir.strpath) | |
|
260 | ||
|
261 | stdout, stderr = _add_files_and_push( | |
|
262 | 'hg', tmpdir.strpath, clone_url=clone_url) | |
|
263 | ||
|
264 | assert 'abort: HTTP Error 403: Forbidden' in stderr | |
|
265 | ||
|
266 | def test_push_is_forbidden_on_archived_repo_git(self, backend_git, rc_web_server, tmpdir): | |
|
267 | empty_repo = backend_git.create_repo() | |
|
268 | repo_name = empty_repo.repo_name | |
|
269 | ||
|
270 | repo = Repository.get_by_repo_name(repo_name) | |
|
271 | repo.archived = True | |
|
272 | Session().commit() | |
|
273 | ||
|
274 | clone_url = rc_web_server.repo_clone_url(repo_name) | |
|
275 | stdout, stderr = Command('/tmp').execute( | |
|
276 | 'git clone', clone_url, tmpdir.strpath) | |
|
277 | ||
|
278 | stdout, stderr = _add_files_and_push( | |
|
279 | 'git', tmpdir.strpath, clone_url=clone_url) | |
|
280 | ||
|
281 | assert "The requested URL returned error: 403" in stderr |
General Comments 0
You need to be logged in to leave comments.
Login now