repositories: added option to archive repositories instead of deleting them....
marcink -
r3090:bdd9dc16 default
Not Reviewed
Show More
Add another comment
TODOs: 0 unresolved 0 Resolved
COMMENTS: 0 General 0 Inline
@@ -0,0 +1,36
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
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 90 # defines current db version for migrations
54 __dbversion__ = 91 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
@@ -505,6 +505,36
505 return False
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 class RepoTypeRoutePredicate(object):
538 class RepoTypeRoutePredicate(object):
509 def __init__(self, val, config):
539 def __init__(self, val, config):
510 self.val = val or ['hg', 'git', 'svn']
540 self.val = val or ['hg', 'git', 'svn']
@@ -530,13 +560,6
530 else:
560 else:
531 log.warning('Current view is not supported for repo type:%s',
561 log.warning('Current view is not supported for repo type:%s',
532 rhodecode_db_repo.repo_type)
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 return False
563 return False
541
564
542
565
@@ -643,10 +666,12
643 config.add_route_predicate(
666 config.add_route_predicate(
644 'repo_accepted_types', RepoTypeRoutePredicate)
667 'repo_accepted_types', RepoTypeRoutePredicate)
645 config.add_route_predicate(
668 config.add_route_predicate(
669 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
670 config.add_route_predicate(
646 'repo_group_route', RepoGroupRoutePredicate)
671 'repo_group_route', RepoGroupRoutePredicate)
647 config.add_route_predicate(
672 config.add_route_predicate(
648 'user_group_route', UserGroupRoutePredicate)
673 'user_group_route', UserGroupRoutePredicate)
649 config.add_route_predicate(
674 config.add_route_predicate(
650 'user_route_with_default', UserRouteWithDefaultPredicate)
675 'user_route_with_default', UserRouteWithDefaultPredicate)
651 config.add_route_predicate(
676 config.add_route_predicate(
652 'user_route', UserRoutePredicate) No newline at end of file
677 'user_route', UserRoutePredicate)
@@ -33,7 +33,7
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.model.db import (
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 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.repo_group import RepoGroupModel
38 from rhodecode.model.repo_group import RepoGroupModel
39 from rhodecode.model.scm import RepoGroupList, RepoList
39 from rhodecode.model.scm import RepoGroupList, RepoList
@@ -114,6 +114,7
114 query = Repository.query()\
114 query = Repository.query()\
115 .order_by(func.length(Repository.repo_name))\
115 .order_by(func.length(Repository.repo_name))\
116 .order_by(Repository.repo_name)\
116 .order_by(Repository.repo_name)\
117 .filter(Repository.archived.isnot(true()))\
117 .filter(or_(
118 .filter(or_(
118 # generate multiple IN to fix limitation problems
119 # generate multiple IN to fix limitation problems
119 *in_filter_generator(Repository.repo_id, allowed_ids)
120 *in_filter_generator(Repository.repo_id, allowed_ids)
@@ -231,11 +231,13
231 config.add_route(
231 config.add_route(
232 name='repo_fork_new',
232 name='repo_fork_new',
233 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
233 pattern='/{repo_name:.*?[^/]}/fork', repo_route=True,
234 repo_forbid_when_archived=True,
234 repo_accepted_types=['hg', 'git'])
235 repo_accepted_types=['hg', 'git'])
235
236
236 config.add_route(
237 config.add_route(
237 name='repo_fork_create',
238 name='repo_fork_create',
238 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
239 pattern='/{repo_name:.*?[^/]}/fork/create', repo_route=True,
240 repo_forbid_when_archived=True,
239 repo_accepted_types=['hg', 'git'])
241 repo_accepted_types=['hg', 'git'])
240
242
241 config.add_route(
243 config.add_route(
@@ -276,27 +278,29
276 config.add_route(
278 config.add_route(
277 name='pullrequest_new',
279 name='pullrequest_new',
278 pattern='/{repo_name:.*?[^/]}/pull-request/new',
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 config.add_route(
284 config.add_route(
282 name='pullrequest_create',
285 name='pullrequest_create',
283 pattern='/{repo_name:.*?[^/]}/pull-request/create',
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 config.add_route(
290 config.add_route(
287 name='pullrequest_update',
291 name='pullrequest_update',
288 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
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 config.add_route(
295 config.add_route(
292 name='pullrequest_merge',
296 name='pullrequest_merge',
293 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
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 config.add_route(
300 config.add_route(
297 name='pullrequest_delete',
301 name='pullrequest_delete',
298 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
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 config.add_route(
305 config.add_route(
302 name='pullrequest_comment_create',
306 name='pullrequest_comment_create',
@@ -319,6 +323,9
319 name='edit_repo_advanced',
323 name='edit_repo_advanced',
320 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
324 pattern='/{repo_name:.*?[^/]}/settings/advanced', repo_route=True)
321 config.add_route(
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 name='edit_repo_advanced_delete',
329 name='edit_repo_advanced_delete',
323 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
330 pattern='/{repo_name:.*?[^/]}/settings/advanced/delete', repo_route=True)
324 config.add_route(
331 config.add_route(
@@ -303,6 +303,27
303 assert response.json == {u'data': [], u'draw': None,
303 assert response.json == {u'data': [], u'draw': None,
304 u'recordsFiltered': 0, u'recordsTotal': 0}
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 class TestSVNFork(TestController):
328 class TestSVNFork(TestController):
308 @pytest.mark.parametrize('route_name', [
329 @pytest.mark.parametrize('route_name', [
@@ -26,7 +26,7
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.model.changeset_status import ChangesetStatusModel
27 from rhodecode.model.changeset_status import ChangesetStatusModel
28 from rhodecode.model.db import (
28 from rhodecode.model.db import (
29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment)
29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment, Repository)
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31 from rhodecode.model.pull_request import PullRequestModel
31 from rhodecode.model.pull_request import PullRequestModel
32 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
@@ -1191,6 +1191,28
1191 )
1191 )
1192 assert response.body == 'true'
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 def assert_pull_request_status(pull_request, expected_status):
1217 def assert_pull_request_status(pull_request, expected_status):
1196 status = ChangesetStatusModel().calculated_review_status(
1218 status = ChangesetStatusModel().calculated_review_status(
@@ -39,6 +39,7
39 'repo_summary': '/{repo_name}',
39 'repo_summary': '/{repo_name}',
40 'edit_repo_advanced': '/{repo_name}/settings/advanced',
40 'edit_repo_advanced': '/{repo_name}/settings/advanced',
41 'edit_repo_advanced_delete': '/{repo_name}/settings/advanced/delete',
41 'edit_repo_advanced_delete': '/{repo_name}/settings/advanced/delete',
42 'edit_repo_advanced_archive': '/{repo_name}/settings/advanced/archive',
42 'edit_repo_advanced_fork': '/{repo_name}/settings/advanced/fork',
43 'edit_repo_advanced_fork': '/{repo_name}/settings/advanced/fork',
43 'edit_repo_advanced_locking': '/{repo_name}/settings/advanced/locking',
44 'edit_repo_advanced_locking': '/{repo_name}/settings/advanced/locking',
44 'edit_repo_advanced_journal': '/{repo_name}/settings/advanced/journal',
45 'edit_repo_advanced_journal': '/{repo_name}/settings/advanced/journal',
@@ -133,7 +134,7
133 "suffix",
134 "suffix",
134 ['', u'ąęł' , '123'],
135 ['', u'ąęł' , '123'],
135 ids=no_newline_id_generator)
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 repo = backend.create_repo(name_suffix=suffix)
138 repo = backend.create_repo(name_suffix=suffix)
138 repo_name = repo.repo_name
139 repo_name = repo.repo_name
139 repo_name_str = safe_str(repo.repo_name)
140 repo_name_str = safe_str(repo.repo_name)
@@ -148,3 +149,25
148 # check if repo was deleted from db
149 # check if repo was deleted from db
149 assert RepoModel().get_by_repo_name(repo_name) is None
150 assert RepoModel().get_by_repo_name(repo_name) is None
150 assert not repo_on_filesystem(repo_name_str)
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
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode import events
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib import audit_logger
@@ -45,6 +46,13
45 c = self._get_local_tmpl_context()
46 c = self._get_local_tmpl_context()
46 return c
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 @LoginRequired()
56 @LoginRequired()
49 @HasRepoPermissionAnyDecorator('repository.admin')
57 @HasRepoPermissionAnyDecorator('repository.admin')
50 @view_config(
58 @view_config(
@@ -71,6 +79,49
71 @HasRepoPermissionAnyDecorator('repository.admin')
79 @HasRepoPermissionAnyDecorator('repository.admin')
72 @CSRFRequired()
80 @CSRFRequired()
73 @view_config(
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 route_name='edit_repo_advanced_delete', request_method='POST',
125 route_name='edit_repo_advanced_delete', request_method='POST',
75 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
126 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
76 def edit_advanced_delete(self):
127 def edit_advanced_delete(self):
@@ -65,6 +65,7
65 'repo.edit': {'old_data': {}},
65 'repo.edit': {'old_data': {}},
66 'repo.edit.permissions': {},
66 'repo.edit.permissions': {},
67 'repo.edit.permissions.branch': {},
67 'repo.edit.permissions.branch': {},
68 'repo.archive': {'old_data': {}},
68 'repo.delete': {'old_data': {}},
69 'repo.delete': {'old_data': {}},
69
70
70 'repo.archive.download': {'user_agent': '', 'archive_name': '',
71 'repo.archive.download': {'user_agent': '', 'archive_name': '',
@@ -322,6 +322,7
322
322
323 class PermOrigin(object):
323 class PermOrigin(object):
324 SUPER_ADMIN = 'superadmin'
324 SUPER_ADMIN = 'superadmin'
325 ARCHIVED = 'archived'
325
326
326 REPO_USER = 'user:%s'
327 REPO_USER = 'user:%s'
327 REPO_USERGROUP = 'usergroup:%s'
328 REPO_USERGROUP = 'usergroup:%s'
@@ -463,8 +464,14
463 # repositories
464 # repositories
464 for perm in self.default_repo_perms:
465 for perm in self.default_repo_perms:
465 r_k = perm.UserRepoToPerm.repository.repo_name
466 r_k = perm.UserRepoToPerm.repository.repo_name
467 archived = perm.UserRepoToPerm.repository.archived
466 p = 'repository.admin'
468 p = 'repository.admin'
467 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
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 # repository groups
476 # repository groups
470 for perm in self.default_repo_groups_perms:
477 for perm in self.default_repo_groups_perms:
@@ -572,6 +579,7
572 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
579 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
573 for perm in self.default_repo_perms:
580 for perm in self.default_repo_perms:
574 r_k = perm.UserRepoToPerm.repository.repo_name
581 r_k = perm.UserRepoToPerm.repository.repo_name
582 archived = perm.UserRepoToPerm.repository.archived
575 p = perm.Permission.permission_name
583 p = perm.Permission.permission_name
576 o = PermOrigin.REPO_DEFAULT
584 o = PermOrigin.REPO_DEFAULT
577 self.permissions_repositories[r_k] = p, o
585 self.permissions_repositories[r_k] = p, o
@@ -602,6 +610,15
602 o = PermOrigin.SUPER_ADMIN
610 o = PermOrigin.SUPER_ADMIN
603 self.permissions_repositories[r_k] = p, o
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 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
622 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
606 for perm in self.default_branch_repo_perms:
623 for perm in self.default_branch_repo_perms:
607
624
@@ -1585,6 +1585,8
1585 unique=False, default=None)
1585 unique=False, default=None)
1586 private = Column(
1586 private = Column(
1587 "private", Boolean(), nullable=True, unique=None, default=None)
1587 "private", Boolean(), nullable=True, unique=None, default=None)
1588 archived = Column(
1589 "archived", Boolean(), nullable=True, unique=None, default=None)
1588 enable_statistics = Column(
1590 enable_statistics = Column(
1589 "statistics", Boolean(), nullable=True, unique=None, default=True)
1591 "statistics", Boolean(), nullable=True, unique=None, default=True)
1590 enable_downloads = Column(
1592 enable_downloads = Column(
@@ -1783,9 +1785,12
1783
1785
1784 @classmethod
1786 @classmethod
1785 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
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 q = Repository.query()
1789 q = Repository.query()
1788
1790
1791 if not archived:
1792 q = q.filter(Repository.archived.isnot(true()))
1793
1789 if not isinstance(user_id, Optional):
1794 if not isinstance(user_id, Optional):
1790 q = q.filter(Repository.user_id == user_id)
1795 q = q.filter(Repository.user_id == user_id)
1791
1796
@@ -1796,6 +1801,7
1796 q = q.order_by(func.lower(Repository.repo_name))
1801 q = q.order_by(func.lower(Repository.repo_name))
1797 else:
1802 else:
1798 q = q.order_by(Repository.repo_name)
1803 q = q.order_by(Repository.repo_name)
1804
1799 return q.all()
1805 return q.all()
1800
1806
1801 @property
1807 @property
@@ -207,8 +207,8
207 def quick_menu(repo_name):
207 def quick_menu(repo_name):
208 return _render('quick_menu', repo_name)
208 return _render('quick_menu', repo_name)
209
209
210 def repo_lnk(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, fork_of,
211 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
212 short_name=not admin, admin=False)
212 short_name=not admin, admin=False)
213
213
214 def last_change(last_change):
214 def last_change(last_change):
@@ -246,8 +246,8
246 row = {
246 row = {
247 "menu": quick_menu(repo.repo_name),
247 "menu": quick_menu(repo.repo_name),
248
248
249 "name": repo_lnk(repo.repo_name, repo.repo_type,
249 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
250 repo.repo_state, repo.private, repo.fork),
250 repo.private, repo.archived, repo.fork),
251 "name_raw": repo.repo_name.lower(),
251 "name_raw": repo.repo_name.lower(),
252
252
253 "last_change": last_change(repo.last_db_change),
253 "last_change": last_change(repo.last_db_change),
@@ -427,6 +427,7
427 new_repo.group = repo_group
427 new_repo.group = repo_group
428 new_repo.description = description or repo_name
428 new_repo.description = description or repo_name
429 new_repo.private = private
429 new_repo.private = private
430 new_repo.archived = False
430 new_repo.clone_uri = clone_uri
431 new_repo.clone_uri = clone_uri
431 new_repo.landing_rev = landing_rev
432 new_repo.landing_rev = landing_rev
432
433
@@ -608,6 +609,23
608 from rhodecode.lib.celerylib import tasks, run_task
609 from rhodecode.lib.celerylib import tasks, run_task
609 return run_task(tasks.create_repo_fork, form_data, cur_user)
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 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
629 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
612 """
630 """
613 Delete given repository, forks parameter defines what do do with
631 Delete given repository, forks parameter defines what do do with
@@ -616,6 +634,7
616
634
617 :param repo:
635 :param repo:
618 :param forks: str 'delete' or 'detach'
636 :param forks: str 'delete' or 'detach'
637 :param pull_requests: str 'delete' or None
619 :param fs_remove: remove(archive) repo from filesystem
638 :param fs_remove: remove(archive) repo from filesystem
620 """
639 """
621 if not cur_user:
640 if not cur_user:
@@ -228,6 +228,7
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']);
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 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
229 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
230 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
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 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
232 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
232 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
233 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
233 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
234 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
@@ -98,7 +98,7
98 <button class="btn btn-small" type="submit"
98 <button class="btn btn-small" type="submit"
99 onclick="return confirm('${_('Confirm to lock repository.')}');">
99 onclick="return confirm('${_('Confirm to lock repository.')}');">
100 <i class="icon-lock"></i>
100 <i class="icon-lock"></i>
101 ${_('Lock Repository')}
101 ${_('Lock repository')}
102 </button>
102 </button>
103 %endif
103 %endif
104 </div>
104 </div>
@@ -111,6 +111,35
111 </div>
111 </div>
112 </div>
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 <div class="panel panel-danger">
143 <div class="panel panel-danger">
115 <div class="panel-heading" id="advanced-delete">
144 <div class="panel-heading" id="advanced-delete">
116 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"></a></h3>
145 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"></a></h3>
@@ -152,7 +181,7
152 <button class="btn btn-small btn-danger" type="submit"
181 <button class="btn btn-small btn-danger" type="submit"
153 onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
182 onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
154 <i class="icon-remove-sign"></i>
183 <i class="icon-remove-sign"></i>
155 ${_('Delete This Repository')}
184 ${_('Delete this repository')}
156 </button>
185 </button>
157 </div>
186 </div>
158 <div class="field">
187 <div class="field">
@@ -287,6 +287,11
287 </div>
287 </div>
288 <div class="clear"></div>
288 <div class="clear"></div>
289 </div>
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 <!--- END CONTEXT BAR -->
295 <!--- END CONTEXT BAR -->
291
296
292 </%def>
297 </%def>
@@ -66,7 +66,7
66