diff --git a/docs/changelog.rst b/docs/changelog.rst --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,45 @@ Changelog ========= + +1.4.1 (**2012-09-07**) +---------------------- + +:status: in-progress +:branch: beta + +news +++++ + +- always put a comment about code-review status change even if user send + empty data +- modified_on column saves repository update and it's going to be used + later for light version of main page ref #500 +- pull request notifications send much nicer emails with details about pull + request +- #551 show breadcrumbs in summary view for repositories inside a group + +fixes ++++++ + +- fixed migrations of permissions that can lead to inconsistency. + Some users sent feedback that after upgrading from older versions issues + with updating default permissions occurred. RhodeCode detects that now and + resets default user permission to initial state if there is a need for that. + Also forces users to set the default value for new forking permission. +- #535 improved apache wsgi example configuration in docs +- fixes #550 mercurial repositories comparision failed when origin repo had + additional not-common changesets +- fixed status of code-review in preview windows of pull request +- git forks were not initialized at bare repos +- fixes #555 fixes issues with comparing non-related repositories +- fixes #557 follower counter always counts up +- fixed issue #560 require push ssl checkbox wasn't shown when option was + enabled +- fixed #559 +- fixed issue #559 fixed bug in routing that mapped repo names with _ in name as + if it was a request to url by repository ID + 1.4.0 (**2012-09-03**) ---------------------- diff --git a/docs/installation.rst b/docs/installation.rst --- a/docs/installation.rst +++ b/docs/installation.rst @@ -11,8 +11,8 @@ clients. Minimal version of hg client kn **1.6**. If you're using older client, please upgrade. -Installing RhodeCode from Cheese Shop -------------------------------------- +Installing RhodeCode from PyPI (aka "Cheeseshop") +------------------------------------------------- Rhodecode requires python version 2.5 or higher. @@ -126,4 +126,4 @@ You can now proceed to :ref:`setup` .. _python: http://www.python.org/ .. _mercurial: http://mercurial.selenic.com/ .. _celery: http://celeryproject.org/ -.. _rabbitmq: http://www.rabbitmq.com/ \ No newline at end of file +.. _rabbitmq: http://www.rabbitmq.com/ diff --git a/docs/setup.rst b/docs/setup.rst --- a/docs/setup.rst +++ b/docs/setup.rst @@ -667,12 +667,21 @@ that, you'll need to: Here is a sample excerpt from an Apache Virtual Host configuration file:: - WSGIDaemonProcess pylons user=www-data group=www-data processes=1 \ + WSGIDaemonProcess pylons \ threads=4 \ python-path=/home/web/rhodecode/pyenv/lib/python2.6/site-packages WSGIScriptAlias / /home/web/rhodecode/dispatch.wsgi WSGIPassAuthorization On +.. note:: + when running apache as root please add: `user=www-data group=www-data` + into above configuration + +.. note:: + RhodeCode cannot be runned in multiprocess mode in apache, make sure + you don't specify `processes=num` directive in the config + + Example wsgi dispatch script:: import os diff --git a/docs/upgrade.rst b/docs/upgrade.rst --- a/docs/upgrade.rst +++ b/docs/upgrade.rst @@ -4,14 +4,43 @@ Upgrade ======= -Upgrading from Cheese Shop --------------------------- +Upgrading from PyPI (aka "Cheeseshop") +--------------------------------------- .. note:: - Firstly, it is recommended that you **always** perform a database backup - before doing an upgrade. + Firstly, it is recommended that you **always** perform a database and + configuration backup before doing an upgrade. + + (These directions will use '{version}' to note that this is the version of + Rhodecode that these files were used with. If backing up your RhodeCode + instance from version 1.3.6 to 1.4.0, the ``production.ini`` file would be + backed up to ``production.ini.1-3-6``.) + + +If using a sqlite database, stop the Rhodecode process/daemon/service, and +then make a copy of the database file:: + + service rhodecode stop + cp rhodecode.db rhodecode.db.{version} + -The easiest way to upgrade ``rhodecode`` is to run:: +Back up your configuration file:: + + cp production.ini production.ini.{version} + + +Ensure that you are using the Python Virtual Environment that you'd originally +installed Rhodecode in:: + + pip freeze + +will list all packages installed in the current environment. If Rhodecode +isn't listed, change virtual environments to your venv location:: + + source /opt/rhodecode-venv/bin/activate + + +Once you have verified the environment you can upgrade ``Rhodecode`` with:: easy_install -U rhodecode @@ -20,14 +49,13 @@ Or:: pip install --upgrade rhodecode -Then make sure you run the following command from the installation directory:: +Then run the following command from the installation directory:: paster make-config RhodeCode production.ini This will display any changes made by the new version of RhodeCode to your -current configuration. It will try to perform an automerge. It's always better -to make a backup of your configuration file before hand and re check the -content after the automerge. +current configuration. It will try to perform an automerge. It's recommended +that you re-check the content after the automerge. .. note:: Please always make sure your .ini files are up to date. Often errors are @@ -41,12 +69,25 @@ Read the changelog to see if there were The final step is to upgrade the database. To do this simply run:: - paster upgrade-db production.ini + paster upgrade-db production.ini This will upgrade the schema and update some of the defaults in the database, and will always recheck the settings of the application, if there are no new options that need to be set. +You may find it helpful to clear out your log file so that new errors are +readily apparent:: + + echo > rhodecode.log + +Once that is complete, you may now start your upgraded Rhodecode Instance:: + + service rhodecode start + +Or:: + + paster serve /var/www/rhodecode/production.ini + .. note:: If you're using Celery, make sure you restart all instances of it after upgrade. @@ -55,4 +96,4 @@ options that need to be set. .. _python: http://www.python.org/ .. _mercurial: http://mercurial.selenic.com/ .. _celery: http://celeryproject.org/ -.. _rabbitmq: http://www.rabbitmq.com/ \ No newline at end of file +.. _rabbitmq: http://www.rabbitmq.com/ diff --git a/rhodecode/__init__.py b/rhodecode/__init__.py --- a/rhodecode/__init__.py +++ b/rhodecode/__init__.py @@ -26,7 +26,7 @@ import sys import platform -VERSION = (1, 4, 0) +VERSION = (1, 4, 1) try: from rhodecode.lib import get_current_revision @@ -38,7 +38,7 @@ except ImportError: __version__ = ('.'.join((str(each) for each in VERSION[:3])) + '.'.join(VERSION[3:])) -__dbversion__ = 6 # defines current db version for migrations +__dbversion__ = 7 # defines current db version for migrations __platform__ = platform.system() __license__ = 'GPLv3' __py_version__ = sys.version_info diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -34,7 +34,7 @@ def make_map(config): try: by_id = repo_name.split('_') - if len(by_id) == 2 and by_id[1].isdigit(): + if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '': repo_name = Repository.get(by_id[1]).repo_name match_dict['repo_name'] = repo_name except: diff --git a/rhodecode/controllers/admin/repos_groups.py b/rhodecode/controllers/admin/repos_groups.py --- a/rhodecode/controllers/admin/repos_groups.py +++ b/rhodecode/controllers/admin/repos_groups.py @@ -45,6 +45,7 @@ from rhodecode.model.forms import ReposG from rhodecode.model.meta import Session from rhodecode.model.repo import RepoModel from webob.exc import HTTPInternalServerError, HTTPNotFound +from rhodecode.lib.utils2 import str2bool log = logging.getLogger(__name__) @@ -162,7 +163,7 @@ class ReposGroupsController(BaseControll Session().commit() h.flash(_('updated repos group %s') \ % form_result['group_name'], category='success') - #TODO: in futureaction_logger(, '', '', '', self.sa) + #TODO: in future action_logger(, '', '', '', self.sa) except formencode.Invalid, errors: return htmlfill.render( @@ -227,10 +228,11 @@ class ReposGroupsController(BaseControll :param group_name: """ - try: - ReposGroupModel().revoke_user_permission( - repos_group=group_name, user=request.POST['user_id'] + recursive = str2bool(request.POST.get('recursive', False)) + ReposGroupModel().delete_permission( + repos_group=group_name, obj=request.POST['user_id'], + obj_type='user', recursive=recursive ) Session().commit() except Exception: @@ -248,9 +250,10 @@ class ReposGroupsController(BaseControll """ try: - ReposGroupModel().revoke_users_group_permission( - repos_group=group_name, - group_name=request.POST['users_group_id'] + recursive = str2bool(request.POST.get('recursive', False)) + ReposGroupModel().delete_permission( + repos_group=group_name, obj=request.POST['users_group_id'], + obj_type='users_group', recursive=recursive ) Session().commit() except Exception: diff --git a/rhodecode/controllers/admin/settings.py b/rhodecode/controllers/admin/settings.py --- a/rhodecode/controllers/admin/settings.py +++ b/rhodecode/controllers/admin/settings.py @@ -51,6 +51,7 @@ from rhodecode.model.user import UserMod from rhodecode.model.db import User from rhodecode.model.notification import EmailNotificationModel from rhodecode.model.meta import Session +from rhodecode.lib.utils2 import str2bool log = logging.getLogger(__name__) @@ -471,6 +472,9 @@ class SettingsController(BaseController) if k == '/': k = 'root_path' + if k == 'push_ssl': + v = str2bool(v) + if k.find('.') != -1: k = k.replace('.', '_') @@ -478,5 +482,4 @@ class SettingsController(BaseController) v = each.ui_active settings[each.ui_section + '_' + k] = v - return settings diff --git a/rhodecode/controllers/changeset.py b/rhodecode/controllers/changeset.py --- a/rhodecode/controllers/changeset.py +++ b/rhodecode/controllers/changeset.py @@ -376,9 +376,13 @@ class ChangesetController(BaseRepoContro def comment(self, repo_name, revision): status = request.POST.get('changeset_status') change_status = request.POST.get('change_changeset_status') + text = request.POST.get('text') + if status and change_status: + text = text or (_('Status change -> %s') + % ChangesetStatus.get_status_lbl(status)) comm = ChangesetCommentsModel().create( - text=request.POST.get('text'), + text=text, repo=c.rhodecode_db_repo.repo_id, user=c.rhodecode_user.user_id, revision=revision, @@ -391,7 +395,7 @@ class ChangesetController(BaseRepoContro # get status if set ! if status and change_status: # if latest status was from pull request and it's closed - # disallow changing status ! + # disallow changing status ! # dont_allow_on_closed_pull_request = True ! try: diff --git a/rhodecode/controllers/pullrequests.py b/rhodecode/controllers/pullrequests.py --- a/rhodecode/controllers/pullrequests.py +++ b/rhodecode/controllers/pullrequests.py @@ -249,8 +249,7 @@ class PullrequestsController(BaseRepoCon org_repo, org_ref, other_repo, other_ref ) - c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in - c.cs_ranges]) + c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges]) # defines that we need hidden inputs with changesets c.as_form = request.GET.get('as_form', False) @@ -277,6 +276,7 @@ class PullrequestsController(BaseRepoCon c.users_array = repo_model.get_users_js() c.users_groups_array = repo_model.get_users_groups_js() c.pull_request = PullRequest.get_or_404(pull_request_id) + c.target_repo = c.pull_request.org_repo.repo_name cc_model = ChangesetCommentsModel() cs_model = ChangesetStatusModel() @@ -322,12 +322,20 @@ class PullrequestsController(BaseRepoCon c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id, pull_request=pull_request_id) - # changeset(pull-request) status - c.current_changeset_status = cs_model.calculate_status( - c.pull_request_reviewers - ) + try: + cur_status = c.statuses[c.pull_request.revisions[0]][0] + except: + log.error(traceback.format_exc()) + cur_status = 'undefined' + if c.pull_request.is_closed() and 0: + c.current_changeset_status = cur_status + else: + # changeset(pull-request) status calulation based on reviewers + c.current_changeset_status = cs_model.calculate_status( + c.pull_request_reviewers, + ) c.changeset_statuses = ChangesetStatus.STATUSES - c.target_repo = c.pull_request.org_repo.repo_name + return render('/pullrequests/pullrequest_show.html') @NotAnonymous() @@ -339,9 +347,12 @@ class PullrequestsController(BaseRepoCon status = request.POST.get('changeset_status') change_status = request.POST.get('change_changeset_status') - + text = request.POST.get('text') + if status and change_status: + text = text or (_('Status change -> %s') + % ChangesetStatus.get_status_lbl(status)) comm = ChangesetCommentsModel().create( - text=request.POST.get('text'), + text=text, repo=c.rhodecode_db_repo.repo_id, user=c.rhodecode_user.user_id, pull_request=pull_request_id, diff --git a/rhodecode/lib/celerylib/tasks.py b/rhodecode/lib/celerylib/tasks.py --- a/rhodecode/lib/celerylib/tasks.py +++ b/rhodecode/lib/celerylib/tasks.py @@ -400,9 +400,19 @@ def create_repo_fork(form_data, cur_user log.info('creating fork of %s as %s', source_repo_path, destination_fork_path) backend = get_backend(repo_type) - backend(safe_str(destination_fork_path), create=True, - src_url=safe_str(source_repo_path), - update_after_clone=update_after_clone) + + if repo_type == 'git': + backend(safe_str(destination_fork_path), create=True, + src_url=safe_str(source_repo_path), + update_after_clone=update_after_clone, + bare=True) + elif repo_type == 'hg': + backend(safe_str(destination_fork_path), create=True, + src_url=safe_str(source_repo_path), + update_after_clone=update_after_clone) + else: + raise Exception('Unknown backend type %s' % repo_type) + log_create_repository(fork_repo.get_dict(), created_by=cur_user.username) action_logger(cur_user, 'user_forked_repo:%s' % fork_name, diff --git a/rhodecode/lib/compat.py b/rhodecode/lib/compat.py --- a/rhodecode/lib/compat.py +++ b/rhodecode/lib/compat.py @@ -589,6 +589,3 @@ else: self.__cond.wait(timeout) finally: self.__cond.release() - - - diff --git a/rhodecode/lib/db_manage.py b/rhodecode/lib/db_manage.py --- a/rhodecode/lib/db_manage.py +++ b/rhodecode/lib/db_manage.py @@ -247,7 +247,22 @@ class DbManage(object): Session().add(hggit) notify('re-check default permissions') - self.klass.populate_default_permissions() + default_user = User.get_by_username(User.DEFAULT_USER) + perm = Permission.get_by_key('hg.fork.repository') + reg_perm = UserToPerm() + reg_perm.user = default_user + reg_perm.permission = perm + Session().add(reg_perm) + + def step_7(self): + perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER) + Session().commit() + if perm_fixes: + notify('There was an inconsistent state of permissions ' + 'detected for default user. Permissions are now ' + 'reset to the default value for default user. ' + 'Please validate and check default permissions ' + 'in admin panel') upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1) @@ -470,6 +485,28 @@ class DbManage(object): log.debug('missing default permission for group %s adding' % g) ReposGroupModel()._create_default_perms(g) + def reset_permissions(self, username): + """ + Resets permissions to default state, usefull when old systems had + bad permissions, we must clean them up + + :param username: + :type username: + """ + default_user = User.get_by_username(username) + if not default_user: + return + + u2p = UserToPerm.query()\ + .filter(UserToPerm.user == default_user).all() + fixed = False + if len(u2p) != len(User.DEFAULT_PERMISSIONS): + for p in u2p: + Session().delete(p) + fixed = True + self.populate_default_permissions() + return fixed + def config_prompt(self, test_repo_path='', retries=3, defaults={}): _path = defaults.get('repos_location') if retries == 3: @@ -506,7 +543,15 @@ class DbManage(object): retries -= 1 return self.config_prompt(test_repo_path, retries) - return path + real_path = os.path.realpath(path) + + if real_path != path: + if not ask_ok(('Path looks like a symlink, Rhodecode will store ' + 'given path as %s ? [y/n]') % (real_path)): + log.error('Canceled by user') + sys.exit(-1) + + return real_path def create_settings(self, path): @@ -597,8 +642,7 @@ class DbManage(object): default_user = User.get_by_username('default') - for def_perm in ['hg.register.manual_activate', 'hg.create.repository', - 'hg.fork.repository', 'repository.read']: + for def_perm in User.DEFAULT_PERMISSIONS: perm = self.sa.query(Permission)\ .filter(Permission.permission_name == def_perm)\ diff --git a/rhodecode/lib/dbmigrate/schema/db_1_3_0.py b/rhodecode/lib/dbmigrate/schema/db_1_3_0.py --- a/rhodecode/lib/dbmigrate/schema/db_1_3_0.py +++ b/rhodecode/lib/dbmigrate/schema/db_1_3_0.py @@ -1317,4 +1317,4 @@ class PullRequest(Base, BaseModel): org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) org_ref = Column('org_ref', Unicode(256), nullable=False) other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - other_ref = Column('other_ref', Unicode(256), nullable=False) \ No newline at end of file + other_ref = Column('other_ref', Unicode(256), nullable=False) diff --git a/rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py b/rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py --- a/rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py +++ b/rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py @@ -49,7 +49,7 @@ def upgrade(migrate_engine): tbl = ChangesetStatus.__table__ tbl.create() - ## RESET COMPLETLY THE metadata for sqlalchemy to use the 1_3_0 Base + ## RESET COMPLETLY THE metadata for sqlalchemy to use the 1_3_0 Base Base = declarative_base() Base.metadata.clear() Base.metadata = MetaData() diff --git a/rhodecode/lib/dbmigrate/versions/007_version_1_4_0.py b/rhodecode/lib/dbmigrate/versions/007_version_1_4_0.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/dbmigrate/versions/007_version_1_4_0.py @@ -0,0 +1,51 @@ +import logging +import datetime + +from sqlalchemy import * +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import relation, backref, class_mapper +from sqlalchemy.orm.session import Session +from sqlalchemy.ext.declarative import declarative_base + +from rhodecode.lib.dbmigrate.migrate import * +from rhodecode.lib.dbmigrate.migrate.changeset import * + +from rhodecode.model.meta import Base +from rhodecode.model import meta + +log = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + """ + Upgrade operations go here. + Don't create your own engine; bind migrate_engine to your metadata + """ + + #========================================================================== + # CHANGESET_COMMENTS + #========================================================================== + from rhodecode.lib.dbmigrate.schema.db_1_4_0 import ChangesetComment + tbl_name = ChangesetComment.__tablename__ + tbl = Table(tbl_name, + MetaData(bind=migrate_engine), autoload=True, + autoload_with=migrate_engine) + col = tbl.columns.revision + + # remove nullability from revision field + col.alter(nullable=True) + + #========================================================================== + # REPOSITORY + #========================================================================== + from rhodecode.lib.dbmigrate.schema.db_1_4_0 import Repository + tbl = Repository.__table__ + updated_on = Column('updated_on', DateTime(timezone=False), + nullable=True, unique=None) + # create created on column for future lightweight main page + updated_on.create(table=tbl) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine diff --git a/rhodecode/lib/diffs.py b/rhodecode/lib/diffs.py --- a/rhodecode/lib/diffs.py +++ b/rhodecode/lib/diffs.py @@ -610,7 +610,7 @@ def differ(org_repo, org_ref, other_repo other_repo.ui.setconfig('hooks', k, None) unbundle = other_repo.getbundle('incoming', common=common, - heads=rheads) + heads=None) buf = BytesIO() while True: diff --git a/rhodecode/lib/ext_json.py b/rhodecode/lib/ext_json.py --- a/rhodecode/lib/ext_json.py +++ b/rhodecode/lib/ext_json.py @@ -92,7 +92,7 @@ try: return _obj_dump(obj) except NotImplementedError: pass - return json.JSONEncoder.default(self, obj) + raise TypeError("%r is not JSON serializable" % (obj,)) # monkey-patch JSON encoder to use extended version json.dumps = functools.partial(json.dumps, cls=ExtendedEncoder) json.dump = functools.partial(json.dump, cls=ExtendedEncoder) diff --git a/rhodecode/lib/hooks.py b/rhodecode/lib/hooks.py --- a/rhodecode/lib/hooks.py +++ b/rhodecode/lib/hooks.py @@ -34,10 +34,9 @@ from rhodecode.lib import helpers as h from rhodecode.lib.utils import action_logger from rhodecode.lib.vcs.backends.base import EmptyChangeset from rhodecode.lib.compat import json -from rhodecode.model.db import Repository, User +from rhodecode.lib.exceptions import HTTPLockedRC from rhodecode.lib.utils2 import safe_str -from rhodecode.lib.exceptions import HTTPLockedRC - +from rhodecode.model.db import Repository, User def _get_scm_size(alias, root_path): @@ -330,7 +329,12 @@ def handle_git_receive(repo_path, revs, # fix if it's not a bare repo if repo_path.endswith('.git'): repo_path = repo_path[:-4] + repo = Repository.get_by_full_path(repo_path) + if not repo: + raise OSError('Repository %s not found in database' + % (safe_str(repo_path))) + _hooks = dict(baseui.configitems('hooks')) or {} extras = json.loads(env['RHODECODE_EXTRAS']) diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py --- a/rhodecode/lib/utils.py +++ b/rhodecode/lib/utils.py @@ -206,7 +206,7 @@ def get_repos(path, recursive=False): def is_valid_repo(repo_name, base_path, scm=None): """ Returns True if given path is a valid repository False otherwise. - If scm param is given also compare if given scm is the same as expected + If scm param is given also compare if given scm is the same as expected from scm parameter :param repo_name: @@ -413,6 +413,11 @@ def repo2db_mapper(initial_repo_list, re raise Exception('Missing administrative account !') added = [] +# # clear cache keys +# log.debug("Clearing cache keys now...") +# CacheInvalidation.clear_cache() +# sa.commit() + for name, repo in initial_repo_list.items(): group = map_groups(name) db_repo = rm.get_by_repo_name(name) @@ -438,6 +443,11 @@ def repo2db_mapper(initial_repo_list, re elif install_git_hook: if db_repo.repo_type == 'git': ScmModel().install_git_hook(db_repo.scm_instance) + # during starting install all cache keys for all repositories in the + # system, this will register all repos and multiple instances + key, _prefix, _org_key = CacheInvalidation._get_key(name) + log.debug("Creating cache key for %s instance_id:`%s`" % (name, _prefix)) + CacheInvalidation._get_or_create_key(key, _prefix, _org_key, commit=False) sa.commit() removed = [] if remove_obsolete: @@ -455,10 +465,6 @@ def repo2db_mapper(initial_repo_list, re log.error(traceback.format_exc()) sa.rollback() - # clear cache keys - log.debug("Clearing cache keys now...") - CacheInvalidation.clear_cache() - sa.commit() return added, removed diff --git a/rhodecode/lib/vcs/backends/git/repository.py b/rhodecode/lib/vcs/backends/git/repository.py --- a/rhodecode/lib/vcs/backends/git/repository.py +++ b/rhodecode/lib/vcs/backends/git/repository.py @@ -178,7 +178,7 @@ class GitRepository(BaseRepository): raise urllib2.URLError("[%s] %s" % (url, e)) def _get_repo(self, create, src_url=None, update_after_clone=False, - bare=False): + bare=False): if create and os.path.exists(self.path): raise RepositoryError("Location already exist") if src_url and not create: diff --git a/rhodecode/lib/vcs/utils/hgcompat.py b/rhodecode/lib/vcs/utils/hgcompat.py --- a/rhodecode/lib/vcs/utils/hgcompat.py +++ b/rhodecode/lib/vcs/utils/hgcompat.py @@ -14,4 +14,5 @@ from mercurial.node import hex from mercurial.encoding import tolocal from mercurial import discovery from mercurial import localrepo -from mercurial import scmutil \ No newline at end of file +from mercurial import scmutil +from mercurial.discovery import findcommonoutgoing diff --git a/rhodecode/model/changeset_status.py b/rhodecode/model/changeset_status.py --- a/rhodecode/model/changeset_status.py +++ b/rhodecode/model/changeset_status.py @@ -64,7 +64,7 @@ class ChangesetStatusModel(BaseModel): def calculate_status(self, statuses_by_reviewers): """ - leading one wins, if number of occurences are equal than weaker wins + leading one wins, if number of occurrences are equal than weaker wins :param statuses_by_reviewers: """ diff --git a/rhodecode/model/comment.py b/rhodecode/model/comment.py --- a/rhodecode/model/comment.py +++ b/rhodecode/model/comment.py @@ -123,18 +123,20 @@ class ChangesetCommentsModel(BaseModel): recipients = ChangesetComment.get_users(revision=revision) # add changeset author if it's in rhodecode system recipients += [User.get_by_email(author_email)] + email_kwargs = { + 'status_change': status_change, + } #pull request elif pull_request: + _url = h.url('pullrequest_show', + repo_name=pull_request.other_repo.repo_name, + pull_request_id=pull_request.pull_request_id, + anchor='comment-%s' % comment.comment_id, + qualified=True, + ) subj = safe_unicode( h.link_to('Re pull request: %(desc)s %(line)s' % \ - {'desc': desc, 'line': line}, - h.url('pullrequest_show', - repo_name=pull_request.other_repo.repo_name, - pull_request_id=pull_request.pull_request_id, - anchor='comment-%s' % comment.comment_id, - qualified=True, - ) - ) + {'desc': desc, 'line': line}, _url) ) notification_type = Notification.TYPE_PULL_REQUEST_COMMENT @@ -144,22 +146,36 @@ class ChangesetCommentsModel(BaseModel): # add pull request author recipients += [pull_request.author] + # add the reviewers to notification + recipients += [x.user for x in pull_request.reviewers] + + #set some variables for email notification + email_kwargs = { + 'pr_id': pull_request.pull_request_id, + 'status_change': status_change, + 'pr_comment_url': _url, + 'pr_comment_user': h.person(user.email), + 'pr_target_repo': h.url('summary_home', + repo_name=pull_request.other_repo.repo_name, + qualified=True) + } # create notification objects, and emails NotificationModel().create( - created_by=user, subject=subj, body=body, - recipients=recipients, type_=notification_type, - email_kwargs={'status_change': status_change} + created_by=user, subject=subj, body=body, + recipients=recipients, type_=notification_type, + email_kwargs=email_kwargs ) mention_recipients = set(self._extract_mentions(body))\ .difference(recipients) if mention_recipients: + email_kwargs.update({'pr_mention': True}) subj = _('[Mention]') + ' ' + subj NotificationModel().create( created_by=user, subject=subj, body=body, recipients=mention_recipients, type_=notification_type, - email_kwargs={'status_change': status_change} + email_kwargs=email_kwargs ) return comment diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -278,6 +278,10 @@ class RhodeCodeUi(Base, BaseModel): Session().add(new_ui) + def __repr__(self): + return '' % (self.__class__.__name__, self.ui_key, + self.ui_value) + class User(Base, BaseModel): __tablename__ = 'users' @@ -289,7 +293,10 @@ class User(Base, BaseModel): 'mysql_charset': 'utf8'} ) DEFAULT_USER = 'default' - + DEFAULT_PERMISSIONS = [ + 'hg.register.manual_activate', 'hg.create.repository', + 'hg.fork.repository', 'repository.read' + ] user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) @@ -602,6 +609,7 @@ class Repository(Base, BaseModel): enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) + updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) @@ -742,6 +750,16 @@ class Repository(Base, BaseModel): p += self.repo_name.split(Repository.url_sep()) return os.path.join(*p) + @property + def cache_keys(self): + """ + Returns associated cache keys for that repo + """ + return CacheInvalidation.query()\ + .filter(CacheInvalidation.cache_args == self.repo_name)\ + .order_by(CacheInvalidation.cache_key)\ + .all() + def get_new_name(self, repo_name): """ returns new full repository name based on assigned group and new new @@ -1398,6 +1416,13 @@ class CacheInvalidation(Base, BaseModel) return u"<%s('%s:%s')>" % (self.__class__.__name__, self.cache_id, self.cache_key) + @property + def prefix(self): + _split = self.cache_key.split(self.cache_args, 1) + if _split and len(_split) == 2: + return _split[0] + return '' + @classmethod def clear_cache(cls): cls.query().delete() @@ -1421,13 +1446,14 @@ class CacheInvalidation(Base, BaseModel) return cls.query().filter(cls.cache_key == key).scalar() @classmethod - def _get_or_create_key(cls, key, prefix, org_key): + def _get_or_create_key(cls, key, prefix, org_key, commit=True): inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar() if not inv_obj: try: inv_obj = CacheInvalidation(key, org_key) Session().add(inv_obj) - Session().commit() + if commit: + Session().commit() except Exception: log.error(traceback.format_exc()) Session().rollback() diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py --- a/rhodecode/model/forms.py +++ b/rhodecode/model/forms.py @@ -128,6 +128,7 @@ def ReposGroupForm(edit=False, old_data= testValueList=True, if_missing=None, not_empty=False) enable_locking = v.StringBoolean(if_missing=False) + recursive = v.StringBoolean(if_missing=False) chained_validators = [v.ValidReposGroup(edit, old_data), v.ValidPerms('group')] @@ -340,4 +341,4 @@ def PullRequestForm(): pullrequest_title = v.UnicodeString(strip=True, required=True, min=3) pullrequest_desc = v.UnicodeString(strip=True, required=False) - return _PullRequestForm \ No newline at end of file + return _PullRequestForm diff --git a/rhodecode/model/notification.py b/rhodecode/model/notification.py --- a/rhodecode/model/notification.py +++ b/rhodecode/model/notification.py @@ -245,6 +245,7 @@ class EmailNotificationModel(BaseModel): TYPE_PASSWORD_RESET = 'passoword_link' TYPE_REGISTRATION = Notification.TYPE_REGISTRATION TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST + TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT TYPE_DEFAULT = 'default' def __init__(self): @@ -255,7 +256,9 @@ class EmailNotificationModel(BaseModel): self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html', self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html', self.TYPE_REGISTRATION: 'email_templates/registration.html', - self.TYPE_DEFAULT: 'email_templates/default.html' + self.TYPE_DEFAULT: 'email_templates/default.html', + self.TYPE_PULL_REQUEST: 'email_templates/pull_request.html', + self.TYPE_PULL_REQUEST_COMMENT: 'email_templates/pull_request_comment.html', } def get_email_tmpl(self, type_, **kwargs): diff --git a/rhodecode/model/permission.py b/rhodecode/model/permission.py --- a/rhodecode/model/permission.py +++ b/rhodecode/model/permission.py @@ -77,7 +77,7 @@ class PermissionModel(BaseModel): form_result['perm_user_name']).scalar() u2p = self.sa.query(UserToPerm).filter(UserToPerm.user == perm_user).all() - if len(u2p) != 4: + if len(u2p) != len(User.DEFAULT_PERMISSIONS): raise Exception('Defined: %s should be 4 permissions for default' ' user. This should not happen please verify' ' your database' % len(u2p)) diff --git a/rhodecode/model/pull_request.py b/rhodecode/model/pull_request.py --- a/rhodecode/model/pull_request.py +++ b/rhodecode/model/pull_request.py @@ -36,7 +36,8 @@ from rhodecode.model.db import PullReque from rhodecode.model.notification import NotificationModel from rhodecode.lib.utils2 import safe_unicode -from rhodecode.lib.vcs.utils.hgcompat import discovery, localrepo, scmutil +from rhodecode.lib.vcs.utils.hgcompat import discovery, localrepo, scmutil, \ + findcommonoutgoing log = logging.getLogger(__name__) @@ -79,22 +80,30 @@ class PullRequestModel(BaseModel): #notification to reviewers notif = NotificationModel() + pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name, + pull_request_id=new.pull_request_id, + qualified=True, + ) subject = safe_unicode( h.link_to( _('%(user)s wants you to review pull request #%(pr_id)s') % \ {'user': created_by_user.username, 'pr_id': new.pull_request_id}, - h.url('pullrequest_show', repo_name=other_repo.repo_name, - pull_request_id=new.pull_request_id, - qualified=True, - ) + pr_url ) ) body = description + kwargs = { + 'pr_title': title, + 'pr_user_created': h.person(created_by_user.email), + 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name, + qualified=True,), + 'pr_url': pr_url, + 'pr_revisions': revisions + } notif.create(created_by=created_by_user, subject=subject, body=body, recipients=reviewers, - type_=Notification.TYPE_PULL_REQUEST,) - + type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs) return new def update_reviewers(self, pull_request, reviewers_ids): @@ -156,7 +165,10 @@ class PullRequestModel(BaseModel): #case two independent repos common, incoming, rheads = discovery_data if org_repo != other_repo and incoming: - revs = org_repo._repo.changelog.findmissing(common, rheads) + obj = findcommonoutgoing(org_repo._repo, + localrepo.locallegacypeer(other_repo._repo.local()), + force=True) + revs = obj.missing for cs in reversed(map(binascii.hexlify, revs)): changesets.append(org_repo.get_changeset(cs)) @@ -205,6 +217,7 @@ class PullRequestModel(BaseModel): log.debug('Doing discovery for %s@%s vs %s@%s' % ( org_repo, org_ref, other_repo, other_ref) ) + #log.debug('Filter heads are %s[%s]' % ('', org_ref[1])) org_peer = localrepo.locallegacypeer(_org_repo.local()) tmp = discovery.findcommonincoming( @@ -212,7 +225,7 @@ class PullRequestModel(BaseModel): remote=org_peer, # org_repo source for incoming heads=[_other_repo[other_rev].node(), _org_repo[org_rev].node()], - force=False + force=True ) return tmp diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py --- a/rhodecode/model/repo.py +++ b/rhodecode/model/repo.py @@ -368,6 +368,7 @@ class RepoModel(BaseModel): obj.user = user obj.permission = permission self.sa.add(obj) + log.debug('Granted perm %s to %s on %s' % (perm, user, repo)) def revoke_user_permission(self, repo, user): """ @@ -383,8 +384,10 @@ class RepoModel(BaseModel): obj = self.sa.query(UserRepoToPerm)\ .filter(UserRepoToPerm.repository == repo)\ .filter(UserRepoToPerm.user == user)\ - .one() - self.sa.delete(obj) + .scalar() + if obj: + self.sa.delete(obj) + log.debug('Revoked perm on %s on %s' % (repo, user)) def grant_users_group_permission(self, repo, group_name, perm): """ @@ -414,6 +417,7 @@ class RepoModel(BaseModel): obj.users_group = group_name obj.permission = permission self.sa.add(obj) + log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo)) def revoke_users_group_permission(self, repo, group_name): """ @@ -429,8 +433,10 @@ class RepoModel(BaseModel): obj = self.sa.query(UsersGroupRepoToPerm)\ .filter(UsersGroupRepoToPerm.repository == repo)\ .filter(UsersGroupRepoToPerm.users_group == group_name)\ - .one() - self.sa.delete(obj) + .scalar() + if obj: + self.sa.delete(obj) + log.debug('Revoked perm to %s on %s' % (repo, group_name)) def delete_stats(self, repo_name): """ diff --git a/rhodecode/model/repos_group.py b/rhodecode/model/repos_group.py --- a/rhodecode/model/repos_group.py +++ b/rhodecode/model/repos_group.py @@ -32,7 +32,7 @@ from rhodecode.lib.utils2 import LazyPro from rhodecode.model import BaseModel from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \ - User, Permission, UsersGroupRepoGroupToPerm, UsersGroup + User, Permission, UsersGroupRepoGroupToPerm, UsersGroup, Repository log = logging.getLogger(__name__) @@ -115,11 +115,12 @@ class ReposGroupModel(BaseModel): 'existing dir %s' % new_path) shutil.move(old_path, new_path) - def __delete_group(self, group): + def __delete_group(self, group, force_delete=False): """ Deletes a group from a filesystem :param group: instance of group from database + :param force_delete: use shutil rmtree to remove all objects """ paths = group.full_path.split(RepoGroup.url_sep()) paths = os.sep.join(paths) @@ -127,7 +128,10 @@ class ReposGroupModel(BaseModel): rm_path = os.path.join(self.repos_path, paths) if os.path.isdir(rm_path): # delete only if that path really exists - os.rmdir(rm_path) + if force_delete: + shutil.rmtree(rm_path) + else: + os.rmdir(rm_path) # this raises an exception when there are still objects inside def create(self, group_name, group_description, parent=None, just_db=False): try: @@ -150,32 +154,79 @@ class ReposGroupModel(BaseModel): log.error(traceback.format_exc()) raise + def _update_permissions(self, repos_group, perms_new=None, + perms_updates=None, recursive=False): + from rhodecode.model.repo import RepoModel + if not perms_new: + perms_new = [] + if not perms_updates: + perms_updates = [] + + def _set_perm_user(obj, user, perm): + if isinstance(obj, RepoGroup): + ReposGroupModel().grant_user_permission( + repos_group=obj, user=user, perm=perm + ) + elif isinstance(obj, Repository): + # we set group permission but we have to switch to repo + # permission + perm = perm.replace('group.', 'repository.') + RepoModel().grant_user_permission( + repo=obj, user=user, perm=perm + ) + + def _set_perm_group(obj, users_group, perm): + if isinstance(obj, RepoGroup): + ReposGroupModel().grant_users_group_permission( + repos_group=obj, group_name=users_group, perm=perm + ) + elif isinstance(obj, Repository): + # we set group permission but we have to switch to repo + # permission + perm = perm.replace('group.', 'repository.') + RepoModel().grant_users_group_permission( + repo=obj, group_name=users_group, perm=perm + ) + updates = [] + log.debug('Now updating permissions for %s in recursive mode:%s' + % (repos_group, recursive)) + + for obj in repos_group.recursive_groups_and_repos(): + if not recursive: + obj = repos_group + + # update permissions + for member, perm, member_type in perms_updates: + ## set for user + if member_type == 'user': + # this updates also current one if found + _set_perm_user(obj, user=member, perm=perm) + ## set for users group + else: + _set_perm_group(obj, users_group=member, perm=perm) + # set new permissions + for member, perm, member_type in perms_new: + if member_type == 'user': + _set_perm_user(obj, user=member, perm=perm) + else: + _set_perm_group(obj, users_group=member, perm=perm) + updates.append(obj) + #if it's not recursive call + # break the loop and don't proceed with other changes + if not recursive: + break + return updates + def update(self, repos_group_id, form_data): try: repos_group = RepoGroup.get(repos_group_id) - - # update permissions - for member, perm, member_type in form_data['perms_updates']: - if member_type == 'user': - # this updates also current one if found - ReposGroupModel().grant_user_permission( - repos_group=repos_group, user=member, perm=perm - ) - else: - ReposGroupModel().grant_users_group_permission( - repos_group=repos_group, group_name=member, perm=perm - ) - # set new permissions - for member, perm, member_type in form_data['perms_new']: - if member_type == 'user': - ReposGroupModel().grant_user_permission( - repos_group=repos_group, user=member, perm=perm - ) - else: - ReposGroupModel().grant_users_group_permission( - repos_group=repos_group, group_name=member, perm=perm - ) + recursive = form_data['recursive'] + # iterate over all members(if in recursive mode) of this groups and + # set the permissions ! + # this can be potentially heavy operation + self._update_permissions(repos_group, form_data['perms_new'], + form_data['perms_updates'], recursive) old_path = repos_group.full_path @@ -191,7 +242,6 @@ class ReposGroupModel(BaseModel): # iterate over all members of this groups and set the locking ! # this can be potentially heavy operation - for obj in repos_group.recursive_groups_and_repos(): #set the value from it's parent obj.enable_locking = repos_group.enable_locking @@ -210,15 +260,54 @@ class ReposGroupModel(BaseModel): log.error(traceback.format_exc()) raise - def delete(self, repos_group): + def delete(self, repos_group, force_delete=False): repos_group = self._get_repos_group(repos_group) try: self.sa.delete(repos_group) - self.__delete_group(repos_group) + self.__delete_group(repos_group, force_delete) except: log.exception('Error removing repos_group %s' % repos_group) raise + def delete_permission(self, repos_group, obj, obj_type, recursive): + """ + Revokes permission for repos_group for given obj(user or users_group), + obj_type can be user or users group + + :param repos_group: + :param obj: user or users group id + :param obj_type: user or users group type + :param recursive: recurse to all children of group + """ + from rhodecode.model.repo import RepoModel + repos_group = self._get_repos_group(repos_group) + + for el in repos_group.recursive_groups_and_repos(): + if not recursive: + # if we don't recurse set the permission on only the top level + # object + el = repos_group + + if isinstance(el, RepoGroup): + if obj_type == 'user': + ReposGroupModel().revoke_user_permission(el, user=obj) + elif obj_type == 'users_group': + ReposGroupModel().revoke_users_group_permission(el, group_name=obj) + else: + raise Exception('undefined object type %s' % obj_type) + elif isinstance(el, Repository): + if obj_type == 'user': + RepoModel().revoke_user_permission(el, user=obj) + elif obj_type == 'users_group': + RepoModel().revoke_users_group_permission(el, group_name=obj) + else: + raise Exception('undefined object type %s' % obj_type) + + #if it's not recursive call + # break the loop and don't proceed with other changes + if not recursive: + break + def grant_user_permission(self, repos_group, user, perm): """ Grant permission for user on given repositories group, or update @@ -246,6 +335,7 @@ class ReposGroupModel(BaseModel): obj.user = user obj.permission = permission self.sa.add(obj) + log.debug('Granted perm %s to %s on %s' % (perm, user, repos_group)) def revoke_user_permission(self, repos_group, user): """ @@ -262,8 +352,10 @@ class ReposGroupModel(BaseModel): obj = self.sa.query(UserRepoGroupToPerm)\ .filter(UserRepoGroupToPerm.user == user)\ .filter(UserRepoGroupToPerm.group == repos_group)\ - .one() - self.sa.delete(obj) + .scalar() + if obj: + self.sa.delete(obj) + log.debug('Revoked perm on %s on %s' % (repos_group, user)) def grant_users_group_permission(self, repos_group, group_name, perm): """ @@ -294,6 +386,7 @@ class ReposGroupModel(BaseModel): obj.users_group = group_name obj.permission = permission self.sa.add(obj) + log.debug('Granted perm %s to %s on %s' % (perm, group_name, repos_group)) def revoke_users_group_permission(self, repos_group, group_name): """ @@ -310,5 +403,7 @@ class ReposGroupModel(BaseModel): obj = self.sa.query(UsersGroupRepoGroupToPerm)\ .filter(UsersGroupRepoGroupToPerm.group == repos_group)\ .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\ - .one() - self.sa.delete(obj) + .scalar() + if obj: + self.sa.delete(obj) + log.debug('Revoked perm to %s on %s' % (repos_group, group_name)) diff --git a/rhodecode/model/user.py b/rhodecode/model/user.py --- a/rhodecode/model/user.py +++ b/rhodecode/model/user.py @@ -564,7 +564,7 @@ class UserModel(BaseModel): rg_k = perm.UserRepoGroupToPerm.group.group_name p = perm.Permission.permission_name cur_perm = user.permissions[GK][rg_k] - if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]: + if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm] or 1: # disable check user.permissions[GK][rg_k] = p # REPO GROUP + USER GROUP @@ -588,7 +588,7 @@ class UserModel(BaseModel): cur_perm = user.permissions[GK][g_k] # overwrite permission only if it's greater than permission # given from other sources - if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]: + if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm] or 1: # disable check user.permissions[GK][g_k] = p return user diff --git a/rhodecode/model/validators.py b/rhodecode/model/validators.py --- a/rhodecode/model/validators.py +++ b/rhodecode/model/validators.py @@ -499,9 +499,9 @@ def ValidPerms(type_='repo'): # fill new permissions in order of how they were added for k in sorted(map(int, new_perms_group.keys())): perm_dict = new_perms_group[str(k)] - new_member = perm_dict['name'] - new_perm = perm_dict['perm'] - new_type = perm_dict['type'] + new_member = perm_dict.get('name') + new_perm = perm_dict.get('perm') + new_type = perm_dict.get('type') if new_member and new_perm and new_type: perms_new.add((new_member, new_perm, new_type)) diff --git a/rhodecode/templates/admin/repos/repo_edit.html b/rhodecode/templates/admin/repos/repo_edit.html --- a/rhodecode/templates/admin/repos/repo_edit.html +++ b/rhodecode/templates/admin/repos/repo_edit.html @@ -115,7 +115,7 @@ ${h.checkbox('enable_locking',value="True")} ${_('Enable lock-by-pulling on repository.')} - +
@@ -188,6 +188,20 @@
${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")} +
+
    +
  • ${_('Manually invalidate cache for this repository. On first access repository will be cached again')} +
  • +
+
+
+ ${_('List of cached values')} +
    + %for cache in c.repo_info.cache_keys: +
  • INSTANCE ID:${cache.prefix or '-'} ${cache.cache_args} CACHED: ${h.bool2icon(cache.cache_active)}
  • + %endfor +
+
${h.end_form()} @@ -195,20 +209,20 @@

${_('Public journal')}

${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
- ${h.hidden('auth_token',str(h.get_token()))} -
- %if c.in_public_journal: - ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")} - %else: - ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")} - %endif -
-
-
    -
  • ${_('All actions made on this repository will be accessible to everyone in public journal')} -
  • -
-
+ ${h.hidden('auth_token',str(h.get_token()))} +
+ %if c.in_public_journal: + ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")} + %else: + ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")} + %endif +
+
+
    +
  • ${_('All actions made on this repository will be accessible to everyone in public journal')} +
  • +
+
${h.end_form()} @@ -229,7 +243,7 @@
  • ${_('Force locking on repository. Works only when anonymous access is disabled')}
  • -
    +
    ${h.end_form()} @@ -245,9 +259,9 @@
  • ${_('''Manually set this repository as a fork of another from the list''')}
  • - + ${h.end_form()} - +

    ${_('Delete')}

    ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
    @@ -262,7 +276,7 @@
    - ${h.end_form()} + ${h.end_form()} diff --git a/rhodecode/templates/admin/repos/repo_edit_perms.html b/rhodecode/templates/admin/repos/repo_edit_perms.html --- a/rhodecode/templates/admin/repos/repo_edit_perms.html +++ b/rhodecode/templates/admin/repos/repo_edit_perms.html @@ -74,8 +74,8 @@ \ \ '""") - %> - ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl' + %> + ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl' diff --git a/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html b/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html --- a/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html +++ b/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html @@ -58,8 +58,8 @@ \ \ '""") - %> - ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl' + %> + ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl' @@ -68,6 +68,12 @@ + + + ${h.checkbox('recursive',value="True", label=_('apply to parents'))} + ${_('Set or revoke permission to all children of that group, including repositories and other groups')} + +