1. Daniel LaMotte
  2. RhodeCode

Commits

Marcin Kuzminski  committed f7a52d5 Merge

merge with beta

  • Participants
  • Parent commits 4c13ced, 92822bd
  • Branches default

Comments (0)

Files changed (56)

File docs/changelog.rst

View file
 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 <name>_<num> in name as
+  if it was a request to url by repository ID
+
 1.4.0 (**2012-09-03**)
 ----------------------
 

File docs/installation.rst

View file
 **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.
 
 .. _python: http://www.python.org/
 .. _mercurial: http://mercurial.selenic.com/
 .. _celery: http://celeryproject.org/
-.. _rabbitmq: http://www.rabbitmq.com/
+.. _rabbitmq: http://www.rabbitmq.com/

File docs/setup.rst

View file
 
 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

File docs/upgrade.rst

View file
 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``.)
 
-The easiest way to upgrade ``rhodecode`` is to run::
+
+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}
+
+
+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
 
  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
 
 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.
 .. _python: http://www.python.org/
 .. _mercurial: http://mercurial.selenic.com/
 .. _celery: http://celeryproject.org/
-.. _rabbitmq: http://www.rabbitmq.com/
+.. _rabbitmq: http://www.rabbitmq.com/

File rhodecode/__init__.py

View file
 import sys
 import platform
 
-VERSION = (1, 4, 0)
+VERSION = (1, 4, 1)
 
 try:
     from rhodecode.lib import get_current_revision
 
 __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

File rhodecode/config/routing.py

View file
 
         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:

File rhodecode/controllers/admin/repos_groups.py

View file
 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__)
 
             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(
 
         :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:
         """
 
         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:

File rhodecode/controllers/admin/settings.py

View file
 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__)
 
             if k == '/':
                 k = 'root_path'
 
+            if k == 'push_ssl':
+                v = str2bool(v)
+
             if k.find('.') != -1:
                 k = k.replace('.', '_')
 
                 v = each.ui_active
 
             settings[each.ui_section + '_' + k] = v
-
         return settings

File rhodecode/controllers/changeset.py

View file
     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,
         # 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:

File rhodecode/controllers/pullrequests.py

View file
                                        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)
 
         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()
         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()
 
         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,

File rhodecode/lib/celerylib/tasks.py

View file
     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,

File rhodecode/lib/compat.py

View file
                     self.__cond.wait(timeout)
             finally:
                 self.__cond.release()
-
-
-

File rhodecode/lib/db_manage.py

View file
                 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)
 
                 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:
             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):
 
 
         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)\

File rhodecode/lib/dbmigrate/schema/db_1_3_0.py

View file
     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)
+    other_ref = Column('other_ref', Unicode(256), nullable=False)

File rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py

View file
     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()

File rhodecode/lib/dbmigrate/versions/007_version_1_4_0.py

View file
+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

File rhodecode/lib/diffs.py

View file
                 other_repo.ui.setconfig('hooks', k, None)
 
             unbundle = other_repo.getbundle('incoming', common=common,
-                                            heads=rheads)
+                                            heads=None)
 
             buf = BytesIO()
             while True:

File rhodecode/lib/ext_json.py

View file
                 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)

File rhodecode/lib/hooks.py

View file
 from rhodecode.lib.utils import action_logger
 from rhodecode.lib.vcs.backends.base import EmptyChangeset
 from rhodecode.lib.compat import json
+from rhodecode.lib.exceptions import HTTPLockedRC
+from rhodecode.lib.utils2 import safe_str
 from rhodecode.model.db import Repository, User
-from rhodecode.lib.utils2 import safe_str
-from rhodecode.lib.exceptions import HTTPLockedRC
-
 
 def _get_scm_size(alias, root_path):
 
     # 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'])

File rhodecode/lib/utils.py

View file
 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:
         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)
         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:
                     log.error(traceback.format_exc())
                     sa.rollback()
 
-    # clear cache keys
-    log.debug("Clearing cache keys now...")
-    CacheInvalidation.clear_cache()
-    sa.commit()
     return added, removed
 
 

File rhodecode/lib/vcs/backends/git/repository.py

View file
             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:

File rhodecode/lib/vcs/utils/hgcompat.py

View file
 from mercurial.encoding import tolocal
 from mercurial import discovery
 from mercurial import localrepo
-from mercurial import scmutil
+from mercurial import scmutil
+from mercurial.discovery import findcommonoutgoing

File rhodecode/model/changeset_status.py

View file
 
     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:
         """

File rhodecode/model/comment.py

View file
             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
             # 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

File rhodecode/model/db.py

View file
 
         Session().add(new_ui)
 
+    def __repr__(self):
+        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
+                                   self.ui_value)
+
 
 class User(Base, BaseModel):
     __tablename__ = 'users'
          '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)
     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)
         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
         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()
         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()

File rhodecode/model/forms.py

View file
                                         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')]
 
         pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
         pullrequest_desc = v.UnicodeString(strip=True, required=False)
 
-    return _PullRequestForm
+    return _PullRequestForm

File rhodecode/model/notification.py

View file
     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):
          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):

File rhodecode/model/permission.py

View file
                                 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))

File rhodecode/model/pull_request.py

View file
 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__)
 
         #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):
         #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))
         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(
                   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
 

File rhodecode/model/repo.py

View file
         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):
         """
         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):
         """
         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):
         """
         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):
         """

File rhodecode/model/repos_group.py

View file
 
 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__)
 
                             '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)
         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:
             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
 
 
             # 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
             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
         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):
         """
         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):
         """
         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):
         """
         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))

File rhodecode/model/user.py

View file
             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
             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

File rhodecode/model/validators.py

View file
             # 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))
 

File rhodecode/templates/admin/repos/repo_edit.html

View file
                     ${h.checkbox('enable_locking',value="True")}
                     <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
                 </div>
-            </div>            
+            </div>
             <div class="field">
                 <div class="label">
                     <label for="user">${_('Owner')}:</label>
         <div class="form">
            <div class="fields">
                ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
+              <div class="field" style="border:none;color:#888">
+              <ul>
+                  <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')}
+                  </li>
+              </ul>
+              </div>
+              <div class="field" style="border:none;">
+                ${_('List of cached values')}
+                  <ul>
+                  %for cache in c.repo_info.cache_keys:
+                      <li>INSTANCE ID:${cache.prefix or '-'} ${cache.cache_args} CACHED: ${h.bool2icon(cache.cache_active)}</li>
+                  %endfor
+                  </ul>
+              </div>
            </div>
         </div>
         ${h.end_form()}
         <h3>${_('Public journal')}</h3>
         ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
         <div class="form">
-                ${h.hidden('auth_token',str(h.get_token()))}
-                <div class="field">
-                %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
-                </div>
-               <div class="field" style="border:none;color:#888">
-               <ul>
-                    <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
-                    </li>
-               </ul>
-               </div>
+          ${h.hidden('auth_token',str(h.get_token()))}
+          <div class="field">
+          %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
+          </div>
+         <div class="field" style="border:none;color:#888">
+         <ul>
+              <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
+              </li>
+         </ul>
+         </div>
         </div>
         ${h.end_form()}
 
                 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
                 </li>
            </ul>
-           </div>           
+           </div>
         </div>
         ${h.end_form()}
 
                     <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
                </ul>
                </div>
-        </div>        
+        </div>
         ${h.end_form()}
-        
+
         <h3>${_('Delete')}</h3>
         ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
         <div class="form">
            </ul>
            </div>
         </div>
-        ${h.end_form()}        
+        ${h.end_form()}
 </div>
 
 </%def>

File rhodecode/templates/admin/repos/repo_edit_perms.html

View file
             </div> \
         </td> \
         <td></td>'""")
-    %>    
-    ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'    
+    %>
+    ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
     <tr class="new_members last_new_member" id="add_perm_input"></tr>
     <tr>
         <td colspan="6">

File rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html

View file
             </div> \
         </td> \
         <td></td>'""")
-    %>    
-    ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'    
+    %>
+    ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
     <tr class="new_members last_new_member" id="add_perm_input"></tr>
     <tr>
         <td colspan="6">
             </span>
         </td>
     </tr>
+    <tr>
+        <td colspan="6">
+           ${h.checkbox('recursive',value="True", label=_('apply to parents'))}
+           <span class="help-block">${_('Set or revoke permission to all children of that group, including repositories and other groups')}</span>
+        </td>
+    </tr>
 </table>
 <script type="text/javascript">
 function ajaxActionUser(user_id, field_id) {
             alert("${_('Failed to remove user')}");
         },
     };
-    var postData = '_method=delete&user_id=' + user_id;
+    var recursive = YUD.get('recursive').checked;
+    var postData = '_method=delete&recursive={0}&user_id={1}'.format(recursive,user_id);
     var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
 };
 
             alert("${_('Failed to remove users group')}");
         },
     };
-    var postData = '_method=delete&users_group_id='+users_group_id;
+    var recursive = YUD.get('recursive').checked;
+    var postData = '_method=delete&recursive={0}&users_group_id={1}'.format(recursive,users_group_id);
     var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
 };
 

File rhodecode/templates/admin/repos_groups/repos_groups.html

View file
 </%def>
 
 <%def name="breadcrumbs()">
-    <span class="groups_breadcrumbs"> ${_('Groups')}
+    <span class="groups_breadcrumbs">
+    ${h.link_to(_(u'Home'),h.url('/'))}
     %if c.group.parent_group:
         &raquo; ${h.link_to(c.group.parent_group.name,h.url('repos_group_home',group_name=c.group.parent_group.group_name))}
     %endif

File rhodecode/templates/admin/repos_groups/repos_groups_edit.html

View file
                     ${h.checkbox('enable_locking',value="True")}
                     <span class="help-block">${_('Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside')}</span>
                 </div>
-            </div>    
+            </div>
             <div class="buttons">
               ${h.submit('save',_('Save'),class_="ui-btn large")}
               ${h.reset('reset',_('Reset'),class_="ui-btn large")}

File rhodecode/templates/admin/settings/settings.html

View file
                             <li>[stale] <span class="metatag" tag="stale">stale</span></li>
                             <li>[dead] <span class="metatag" tag="dead">dead</span></li>
                             <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
-                            <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>                            
+                            <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
                             <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
                             <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
                             <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
                 </div>
                 <div class="checkboxes">
 					<div class="checkbox">
-						${h.checkbox('web_push_ssl','true')}
+						${h.checkbox('web_push_ssl', 'True')}
 						<label for="web_push_ssl">${_('require ssl for vcs operations')}</label>
 					</div>
                     <span class="help-block">${_('RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable')}</span>
                     ##    ${h.checkbox('extensions_hggit','True')}
                     ##    <label for="extensions_hggit">${_('hg-git extensions')}</label>
                     ##</div>
-                    ##<span class="help-block">${_('Requires hg-git library installed. Allows clonning from git remote locations')}</span>                    
+                    ##<span class="help-block">${_('Requires hg-git library installed. Allows clonning from git remote locations')}</span>
                 </div>
-             </div>             
+             </div>
             <div class="field">
                 <div class="label">
                     <label for="paths_root_path">${_('Repositories location')}:</label>

File rhodecode/templates/admin/users/user_edit.html

View file
                 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
                                              'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
              </div>
-             <div id="inherit_overlay" style="${'opacity:0.3' if c.user.inherit_default_permissions else ''}" >        
+             <div id="inherit_overlay" style="${'opacity:0.3' if c.user.inherit_default_permissions else ''}" >
              <div class="field">
                 <div class="label label-checkbox">
                     <label for="create_repo_perm">${_('Create repositories')}:</label>
                     ${h.checkbox('fork_repo_perm',value=True)}
                 </div>
              </div>
-             </div>             
+             </div>
             <div class="buttons">
               ${h.submit('save',_('Save'),class_="ui-btn large")}
               ${h.reset('reset',_('Reset'),class_="ui-btn large")}

File rhodecode/templates/admin/users_groups/users_group_edit.html

View file
                 </div>
                 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
                                              'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
-             </div>        
+             </div>
              <div id="inherit_overlay" style="${'opacity:0.3' if c.users_group.inherit_default_permissions else ''}" >
              <div class="field">
                 <div class="label label-checkbox">
                     ${h.checkbox('fork_repo_perm',value=True)}
                 </div>
              </div>
-             </div>          
+             </div>
             <div class="buttons">
               ${h.submit('save',_('Save'),class_="ui-btn large")}
               ${h.reset('reset',_('Reset'),class_="ui-btn large")}

File rhodecode/templates/base/root.html

View file
                     f.setAttribute('class','follow');
                     f.setAttribute('title',_TM['Start following this repository']);
                     if(f_cnt){
-                        var cnt = Number(f_cnt.innerHTML)+1;
+                        var cnt = Number(f_cnt.innerHTML)-1;
                         f_cnt.innerHTML = cnt;
                     }
                 }

File rhodecode/templates/email_templates/pull_request.html

View file
+## -*- coding: utf-8 -*-
+<%inherit file="main.html"/>
+
+User <b>${pr_user_created}</b> opened pull request for repository
+${pr_repo_url} and wants you to review changes.
+
+<div>title: ${pr_title}</div>
+<div>description:</div>
+<p>
+${body}
+</p>
+
+<div>revisions for reviewing</div>
+<ul>
+%for r in pr_revisions:
+    <li>${r}</li>
+%endfor
+</ul>
+
+View this pull request here: ${pr_url}

File rhodecode/templates/email_templates/pull_request_comment.html

View file
+## -*- coding: utf-8 -*-
+<%inherit file="main.html"/>
+
+User <b>${pr_comment_user}</b> commented on pull request #${pr_id} for
+repository ${pr_target_repo}
+
+<p>
+${body}
+
+%if status_change:
+    <span>New status -> ${status_change}</span>
+%endif
+</p>
+
+View this comment here: ${pr_comment_url}

File rhodecode/templates/files/files.html

View file
 
 var ypjax_links = function(){
     YUE.on(YUQ('.ypjax-link'), 'click',function(e){
-    	
+
     	//don't do ypjax on middle click
-    	if(e.which == 2 || !History.enabled){ 
+    	if(e.which == 2 || !History.enabled){
     		return true;
     	}
-    	
+
         var el = e.currentTarget;
         var url = el.href;
 
         var _base_url = '${h.url("files_home",repo_name=c.repo_name,revision='',f_path='')}';
         _base_url = _base_url.replace('//','/')
-        
+
         //extract rev and the f_path from url.
         parts = url.split(_base_url)
         if(parts.length != 2){
         	return false;
         }
-        
+
         var parts2 = parts[1].split('/');
       	var rev = parts2.shift(); // pop the first element which is the revision
       	var f_path = parts2.join('/');
-        
+
         var title = "${_('%s files') % c.repo_name}" + " - " + f_path;
-        
+
         var _node_list_url = node_list_url.replace('__REV__',rev);
         var _url_base = url_base.replace('__REV__',rev).replace('__FPATH__', f_path);
 
         // Change our States and save some data for handling events
         var data = {url:url,title:title, url_base:_url_base,
                     node_list_url:_node_list_url};
-        History.pushState(data, title, url);        
-        
+        History.pushState(data, title, url);
+
         //now we're sure that we can do ypjax things
         YUE.preventDefault(e)
         return false;
     // Inform Google Analytics of the change
     if ( typeof window.pageTracker !== 'undefined' ) {
         window.pageTracker._trackPageview(State.url);
-    }	
+    }
 }
 
-YUE.onDOMReady(function(){ 
+YUE.onDOMReady(function(){
     ypjax_links();
     var container = 'files_data';
     //Bind to StateChange Event
             }
           });
         }
-    });    
- 
+    });
+
     // init the search filter
     var _State = {
        url: "${h.url.current()}",

File rhodecode/templates/files/files_ypjax.html

View file
             <%include file='files_browser.html'/>
         %else:
             <%include file='files_source.html'/>
-        %endif        
+        %endif
 %else:
     <h2>
         <a href="#" onClick="javascript:parent.history.back();" target="main">${_('Go back')}</a>

File rhodecode/templates/pullrequests/pullrequest_show.html

View file
         ${self.breadcrumbs()}
     </div>
         %if c.pull_request.is_closed():
-        <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))}</div>
+        <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))} ${_('with status %s') % h.changeset_status_lbl(c.current_changeset_status)}</div>
         %endif
     <h3>${_('Title')}: ${c.pull_request.title}</h3>
-        
+
     <div class="form">
       <div id="summary" class="fields">
          <div class="field">
           <div class="input">
             <div class="tooltip" title="${h.tooltip(','.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
           </div>
-         </div>                
+         </div>
       </div>
-    </div>    
+    </div>
     <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
     <div style="padding:4px 4px 10px 20px">
       <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>

File rhodecode/templates/summary/summary.html

View file
 <%def name="breadcrumbs_links()">
     ${h.link_to(_(u'Home'),h.url('/'))}
     &raquo;
-    ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
+    ${h.repo_link(c.dbrepo.groups_and_repo)}
     &raquo;
     ${_('summary')}
 </%def>
                 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.desc_stylize(c.dbrepo.description))}</div>
               %else:
                 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
-              %endif        
+              %endif
 			 </div>
 
 			 <div class="field">

File rhodecode/tests/functional/test_compare.py

View file
         response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
 
         ## files diff
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1,  tag2))
-        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1,  tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1, tag2))
+        response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1, tag2))
 
     def test_index_branch(self):
         self.log_user()
             ## files
             response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s#C--826e8142e6ba">file1</a>""" % (r2_name, rev1, rev2))
 
-
         finally:
             RepoModel().delete(r1_id)
             RepoModel().delete(r2_id)
+
+    def test_org_repo_new_commits_after_forking(self):
+        self.log_user()
+
+        repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
+                                        description='diff-test',
+                                        owner=TEST_USER_ADMIN_LOGIN)
+
+        Session().commit()
+        r1_id = repo1.repo_id
+        r1_name = repo1.repo_name
+
+        #commit something initially !
+        cs0 = ScmModel().create_node(
+            repo=repo1.scm_instance, repo_name=r1_name,
+            cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
+            author=TEST_USER_ADMIN_LOGIN,
+            message='commit1',
+            content='line1',
+            f_path='file1'
+        )
+        Session().commit()
+        self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
+        #fork the repo1
+        repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
+                                description='compare-test',
+                                clone_uri=repo1.repo_full_path,
+                                owner=TEST_USER_ADMIN_LOGIN, fork_of='one')
+        Session().commit()
+        self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
+        r2_id = repo2.repo_id
+        r2_name = repo2.repo_name
+
+        #make 3 new commits in fork
+        cs1 = ScmModel().create_node(
+            repo=repo2.scm_instance, repo_name=r2_name,
+            cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN,
+            author=TEST_USER_ADMIN_LOGIN,
+            message='commit1-fork',
+            content='file1-line1-from-fork',
+            f_path='file1-fork'
+        )
+        cs2 = ScmModel().create_node(
+            repo=repo2.scm_instance, repo_name=r2_name,
+            cs=cs1, user=TEST_USER_ADMIN_LOGIN,
+            author=TEST_USER_ADMIN_LOGIN,
+            message='commit2-fork',
+            content='file2-line1-from-fork',
+            f_path='file2-fork'
+        )
+        cs3 = ScmModel().create_node(
+            repo=repo2.scm_instance, repo_name=r2_name,
+            cs=cs2, user=TEST_USER_ADMIN_LOGIN,
+            author=TEST_USER_ADMIN_LOGIN,
+            message='commit3-fork',
+            content='file3-line1-from-fork',
+            f_path='file3-fork'
+        )
+
+        #compare !
+        rev1 = 'default'
+        rev2 = 'default'
+        response = self.app.get(url(controller='compare', action='index',
+                                    repo_name=r2_name,
+                                    org_ref_type="branch",
+                                    org_ref=rev1,
+                                    other_ref_type="branch",
+                                    other_ref=rev2,
+                                    repo=r1_name
+                                    ))
+
+        try:
+            response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
+            response.mustcontain("""file1-line1-from-fork""")
+            response.mustcontain("""file2-line1-from-fork""")