Changeset - 148360f533a4
.hgtags
Show inline comments
 
c097458480a5972dd75d5695b61e855fd0ab371e rhodecode-0.0.0.7.0
 
8bdec09436cb7e4a764bd2ba50b84060e30eb34f rhodecode-0.0.0.7.1
 
1a18994cdc3bdd156ee93c7c0fb8d94a88f1f640 rhodecode-0.0.0.7.2
 
a3a7c3e03b76ee264a828cb1087970bb98bbffcd rhodecode-0.0.0.7.3
 
58b46f9194c347641bfc9a26697ef413a4761971 rhodecode-0.0.0.7.4
 
710e7a75bb6b8346cee3bd0ddda67592e4790268 rhodecode-0.0.0.7.5
 
ca80f8c0056211dad33483a50b913593516d7a6c rhodecode-0.0.0.7.6
 
0cf49c29c846fefeb4e1a222e4b1850e9e3eaa62 rhodecode-0.0.0.7.7
 
702c7e565c56a49c89414e81f28571c8e5b67408 rhodecode-0.0.0.7.8
 
c12f4d19c95065f313eefcd45eac9ef507f5fa55 rhodecode-0.0.0.7.9
 
558eb7c5028f24a90b5466ed16be13b213ba1fc2 rhodecode-0.0.0.8.0
 
a9814a642e11092b243ca01721254a04633a0ffc rhodecode-0.0.0.8.1
 
ccbe729908844884aea89d00fb14a6cb92e10c06 rhodecode-0.0.0.8.2
 
ca41d544dbdfd2f81bd0304168492a26276aadb6 rhodecode-0.0.0.8.3
 
2fa16ec5822da0c6fade3dd1ed9b6c0655e5dbbf rhodecode-0.0.0.8.4
 
16ba57d8fe2317c49dbd422afd07ab497687aa02 rhodecode-0.0.0.8.5
 
53128b6b9a4ddb6ee9554cbb83a082a6d1316b42 rhodecode-0.0.1.0.0rc4
 
afd98d1f817e6a6b52172735c22160239e615a6b rhodecode-0.0.1.0.0
 
bee56f209c40a6880f2f633b02227b5ee1f8ff5a rhodecode-0.0.1.0.1
 
d85b0948e53925ebbbc49e9f7967013a04f866e9 rhodecode-0.0.1.0.2
 
d9c8dddb96af521e346f05b88d515c536eef3d17 rhodecode-0.0.1.1.0
 
344f748517814ed0408a49e392dc625f4cc37fdc rhodecode-0.0.1.1.1
 
6c01c12eafb8cc72d4c4cbd121400fad755b2862 rhodecode-0.0.1.1.2
 
4fa80e0484ef5c33feaa9c39fc66916f410ba353 rhodecode-0.0.1.1.3
 
cb77867d69d3c5931712aac486c980a42ee90745 rhodecode-0.0.1.1.5
 
cb77867d69d3c5931712aac486c980a42ee90745 rhodecode-0.0.1.1.5
 
008bdfdd95c8bd31ae6d89f76c75c1f49cbcd0bc rhodecode-0.0.1.1.5
 
c5af1d3c861fb36b156224e75c2f55a97f54657d rhodecode-0.0.1.1.6
 
7327a0d1584cf28d33e738048af1f6809d499451 rhodecode-0.0.1.1.7
 
bd102f45950f779995a1beae42b6eb099cdd27b3 rhodecode-0.0.1.1.7
 
c8974135732aa0ceb841cee6df66e29f089b4963 rhodecode-0.0.1.1.8
 
c252049af24cd98eef5f4143fa3abbff3c912e29 rhodecode-0.0.1.2.0
 
0b8fba8ab90b01f811a50e6e7384989cced21d38 rhodecode-0.0.1.2.1
 
22273bec00ba2fd860c60a9277d3d7229e288e18 rhodecode-0.0.1.2.2
 
1ff606a7858dbd8a5f70b3da5cc89524bd0d84f9 rhodecode-0.0.1.2.3
 
a7a282a902b207ce34e830d643c79b7ab52e3b35 rhodecode-0.0.1.2.4
 
b6b611e7722e754abebaae6e265cbb4c823d344d rhodecode-0.0.1.2.5
 
dbc82e3362a25d2aece42060089824c4342efd17 rhodecode-0.0.1.3.0
 
79a95f338fd0115b2cdb77118f39e17d22ff505c rhodecode-0.0.1.3.1
 
9ab21c5ddb84935bea5c743b4e147ed5a398b30c rhodecode-0.0.1.3.2
 
934906f028b582a254e0028ba25e5d20dd32b9cd rhodecode-0.0.1.3.3
 
af21362474e3ab5aa0e2fbb1c872356f2c16c4f3 rhodecode-0.0.1.3.4
 
0e2792e04bd316fe64335cbe6a476031ac60b29b rhodecode-0.0.1.3.5
 
edfff9f37916389144d3a3644d0a7d7adfd79b11 rhodecode-0.0.1.3.6
 
9ae95fdeca184f2404205645f06c6597b74ef2db rhodecode-0.0.1.4.0
 
909143a4dde53c46d4f24abb426ec870471c7de1 rhodecode-0.0.1.4.1
 
d998cc84cf726798486a438763053f0e1dc1b646 rhodecode-0.0.1.4.2
 
3f5d40b9dd99ccb009ea2211ee2d4b594c634946 rhodecode-0.0.1.4.3
 
3148c08cf86f1849917e2d50f7ab7766c1550b0a rhodecode-0.0.1.4.4
 
a5f0bc867edc88be23eb808693e5393a97d4c54a rhodecode-0.0.1.5.0
 
3259dc7caea48687eab018ee646ae6ad7e7ef377 rhodecode-0.0.1.5.1
 
efe23d6c178c11d575a0214181276a3452776e48 rhodecode-0.0.1.5.2
 
1a498b11f1540f5b94b6f6009298f5dc3eaad9e9 rhodecode-0.0.1.5.3
 
3447862ad8c9ceba85857774c526e39fde3a2281 rhodecode-0.0.1.5.4
 
c15d7b336af58df9f1bbc8f8957464e7ea618d4c rhodecode-0.0.1.6.0rc1
 
78b53ee0d247f90d51b028307ff5717851b6c265 rhodecode-0.0.1.6.0
 
351ad34d56321349ff5bd38f537bd768b8efef2e rhodecode-0.0.1.7.0
 
1f71ef689d2a3c9978cea6591a1f4e9107a5ca83 rhodecode-0.0.1.7.1
 
d17e88a1a88a29f6fac948c94498129e405a40d3 0.1
 
ad0ce803b40cb17fc3988373052943e041030b02 0.2
 
c6e32714336345403adf76abb6ebf9b8116fcdc7 0.2.1
 
14f488a5dc4ca6647bc6acf12534fd137e968aa8 0.2.2
docs/api/api.rst
Show inline comments
 
@@ -644,360 +644,361 @@ OUTPUT::
 
                "created_on" :       "<date_time_created>",
 
                "description" :      "<description>",
 
                "landing_rev":       "<landing_rev>",
 
                "last_changeset":    {
 
                                       "author":   "<full_author>",
 
                                       "date":     "<date_time_of_commit>",
 
                                       "message":  "<commit_message>",
 
                                       "raw_id":   "<raw_id>",
 
                                       "revision": "<numeric_revision>",
 
                                       "short_id": "<short_id>"
 
                                     }
 
                "owner":             "<repo_owner>",
 
                "fork_of":           "<name_of_fork_parent>",
 
                "members" :     [
 
                                  {
 
                                    "type":        "user",
 
                                    "user_id" :    "<user_id>",
 
                                    "api_key" :    "<api_key>",
 
                                    "username" :   "<username>",
 
                                    "firstname":   "<firstname>",
 
                                    "lastname" :   "<lastname>",
 
                                    "email" :      "<email>",
 
                                    "emails":      "<list_of_all_additional_emails>",
 
                                    "active" :     "<bool>",
 
                                    "admin" :      "<bool>",
 
                                    "ldap_dn" :    "<ldap_dn>",
 
                                    "last_login":  "<last_login>",
 
                                    "permission" : "repository.(read|write|admin)"
 
                                  },
 
 
                                  {
 
                                    "type":      "users_group",
 
                                    "id" :       "<usersgroupid>",
 
                                    "name" :     "<usersgroupname>",
 
                                    "active":    "<bool>",
 
                                    "permission" : "repository.(read|write|admin)"
 
                                  },
 
 
                                ]
 
                 "followers":   [
 
                                  {
 
                                    "user_id" :     "<user_id>",
 
                                    "username" :    "<username>",
 
                                    "api_key" :     "<api_key>",
 
                                    "firstname":    "<firstname>",
 
                                    "lastname" :    "<lastname>",
 
                                    "email" :       "<email>",
 
                                    "emails":       "<list_of_all_additional_emails>",
 
                                    "ip_addresses": "<list_of_ip_addresses_for_user>",
 
                                    "active" :      "<bool>",
 
                                    "admin" :       "<bool>",
 
                                    "ldap_dn" :     "<ldap_dn>",
 
                                    "last_login":   "<last_login>",
 
                                  },
 
 
                 ]
 
            }
 
    error:  null
 

	
 

	
 
get_repos
 
---------
 

	
 
List all existing repositories.
 
This command can only be executed using the api_key of a user with admin rights,
 
or that of a regular user with at least read access to the repository.
 

	
 

	
 
INPUT::
 

	
 
    id : <id_for_response>
 
    api_key : "<api_key>"
 
    method :  "get_repos"
 
    args:     { }
 

	
 
OUTPUT::
 

	
 
    id : <id_given_in_input>
 
    result: [
 
              {
 
                "repo_id" :          "<repo_id>",
 
                "repo_name" :        "<reponame>"
 
                "repo_type" :        "<repo_type>",
 
                "clone_uri" :        "<clone_uri>",
 
                "private": :         "<bool>",
 
                "created_on" :       "<datetimecreated>",
 
                "description" :      "<description>",
 
                "landing_rev":       "<landing_rev>",
 
                "owner":             "<repo_owner>",
 
                "fork_of":           "<name_of_fork_parent>",
 
                "enable_downloads":  "<bool>",
 
                "enable_locking":    "<bool>",
 
                "enable_statistics": "<bool>",
 
              },
 
 
            ]
 
    error:  null
 

	
 

	
 
get_repo_nodes
 
--------------
 

	
 
Return a list of files and directories for a given path at the given revision.
 
It is possible to specify ret_type to show only ``files`` or ``dirs``.
 
This command can only be executed using the api_key of a user with admin rights.
 

	
 

	
 
INPUT::
 

	
 
    id : <id_for_response>
 
    api_key : "<api_key>"
 
    method :  "get_repo_nodes"
 
    args:     {
 
                "repoid" : "<reponame or repo_id>"
 
                "revision"  : "<revision>",
 
                "root_path" : "<root_path>",
 
                "ret_type"  : "<ret_type> = Optional('all')"
 
              }
 

	
 
OUTPUT::
 

	
 
    id : <id_given_in_input>
 
    result: [
 
              {
 
                "name" :        "<name>"
 
                "type" :        "<type>",
 
              },
 
 
            ]
 
    error:  null
 

	
 

	
 
create_repo
 
-----------
 

	
 
Create a repository. If the repository name contains "/", all needed repository
 
groups will be created. For example "foo/bar/baz" will create repository groups
 
"foo", "bar" (with "foo" as parent), and create "baz" repository with
 
"bar" as group.
 
This command can only be executed using the api_key of a user with admin rights,
 
or that of a regular user with create repository permission.
 
Regular users cannot specify owner parameter.
 

	
 

	
 
INPUT::
 

	
 
    id : <id_for_response>
 
    api_key : "<api_key>"
 
    method :  "create_repo"
 
    args:     {
 
                "repo_name" :        "<reponame>",
 
                "owner" :            "<onwer_name_or_id = Optional(=apiuser)>",
 
                "repo_type" :        "<repo_type> = Optional('hg')",
 
                "description" :      "<description> = Optional('')",
 
                "private" :          "<bool> = Optional(False)",
 
                "clone_uri" :        "<clone_uri> = Optional(None)",
 
                "landing_rev" :      "<landing_rev> = Optional('tip')",
 
                "enable_downloads":  "<bool> = Optional(False)",
 
                "enable_locking":    "<bool> = Optional(False)",
 
                "enable_statistics": "<bool> = Optional(False)",
 
              }
 

	
 
OUTPUT::
 

	
 
    id : <id_given_in_input>
 
    result: {
 
              "msg": "Created new repository `<reponame>`",
 
              "repo": {
 
                "repo_id" :          "<repo_id>",
 
                "repo_name" :        "<reponame>"
 
                "repo_type" :        "<repo_type>",
 
                "clone_uri" :        "<clone_uri>",
 
                "private": :         "<bool>",
 
                "created_on" :       "<datetimecreated>",
 
                "description" :      "<description>",
 
                "landing_rev":       "<landing_rev>",
 
                "owner":             "<username or user_id>",
 
                "fork_of":           "<name_of_fork_parent>",
 
                "enable_downloads":  "<bool>",
 
                "enable_locking":    "<bool>",
 
                "enable_statistics": "<bool>",
 
              },
 
            }
 
    error:  null
 

	
 

	
 
fork_repo
 
---------
 

	
 
Create a fork of the given repo. If using Celery, this will
 
return success message immediately and a fork will be created
 
asynchronously.
 
This command can only be executed using the api_key of a user with admin rights,
 
or that of a regular user with fork permission and at least read access to the repository.
 
This command can only be executed using the api_key of a user with admin
 
rights, or with the global fork permission, by a regular user with create
 
repository permission and at least read access to the repository.
 
Regular users cannot specify owner parameter.
 

	
 

	
 
INPUT::
 

	
 
    id : <id_for_response>
 
    api_key : "<api_key>"
 
    method :  "fork_repo"
 
    args:     {
 
                "repoid" :          "<reponame or repo_id>",
 
                "fork_name":        "<forkname>",
 
                "owner":            "<username or user_id = Optional(=apiuser)>",
 
                "description":      "<description>",
 
                "copy_permissions": "<bool>",
 
                "private":          "<bool>",
 
                "landing_rev":      "<landing_rev>"
 

	
 
              }
 

	
 
OUTPUT::
 

	
 
    id : <id_given_in_input>
 
    result: {
 
              "msg": "Created fork of `<reponame>` as `<forkname>`",
 
              "success": true
 
            }
 
    error:  null
 

	
 

	
 
delete_repo
 
-----------
 

	
 
Delete a repository.
 
This command can only be executed using the api_key of a user with admin rights,
 
or that of a regular user with admin access to the repository.
 
When ``forks`` param is set it is possible to detach or delete forks of the deleted repository.
 

	
 

	
 
INPUT::
 

	
 
    id : <id_for_response>
 
    api_key : "<api_key>"
 
    method :  "delete_repo"
 
    args:     {
 
                "repoid" : "<reponame or repo_id>",
 
                "forks"  : "`delete` or `detach` = Optional(None)"
 
              }
 

	
 
OUTPUT::
 

	
 
    id : <id_given_in_input>
 
    result: {
 
              "msg": "Deleted repository `<reponame>`",
 
              "success": true
 
            }
 
    error:  null
 

	
 

	
 
grant_user_permission
 
---------------------
 

	
 
Grant permission for a user on the given repository, or update the existing one if found.
 
This command can only be executed using the api_key of a user with admin rights.
 

	
 

	
 
INPUT::
 

	
 
    id : <id_for_response>
 
    api_key : "<api_key>"
 
    method :  "grant_user_permission"
 
    args:     {
 
                "repoid" : "<reponame or repo_id>"
 
                "userid" : "<username or user_id>"
 
                "perm" :       "(repository.(none|read|write|admin))",
 
              }
 

	
 
OUTPUT::
 

	
 
    id : <id_given_in_input>
 
    result: {
 
              "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
 
              "success": true
 
            }
 
    error:  null
 

	
 

	
 
revoke_user_permission
 
----------------------
 

	
 
Revoke permission for a user on the given repository.
 
This command can only be executed using the api_key of a user with admin rights.
 

	
 

	
 
INPUT::
 

	
 
    id : <id_for_response>
 
    api_key : "<api_key>"
 
    method  : "revoke_user_permission"
 
    args:     {
 
                "repoid" : "<reponame or repo_id>"
 
                "userid" : "<username or user_id>"
 
              }
 

	
 
OUTPUT::
 

	
 
    id : <id_given_in_input>
 
    result: {
 
              "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
 
              "success": true
 
            }
 
    error:  null
 

	
 

	
 
grant_user_group_permission
 
---------------------------
 

	
 
Grant permission for a user group on the given repository, or update the
 
existing one if found.
 
This command can only be executed using the api_key of a user with admin rights.
 

	
 

	
 
INPUT::
 

	
 
    id : <id_for_response>
 
    api_key : "<api_key>"
 
    method :  "grant_user_group_permission"
 
    args:     {
 
                "repoid" : "<reponame or repo_id>"
 
                "usersgroupid" : "<user group id or name>"
 
                "perm" : "(repository.(none|read|write|admin))",
 
              }
 

	
 
OUTPUT::
 

	
 
    id : <id_given_in_input>
 
    result: {
 
              "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
 
              "success": true
 
            }
 
    error:  null
 

	
 

	
 
revoke_user_group_permission
 
----------------------------
 

	
 
Revoke permission for a user group on the given repository.
 
This command can only be executed using the api_key of a user with admin rights.
 

	
 
INPUT::
 

	
 
    id : <id_for_response>
 
    api_key : "<api_key>"
 
    method  : "revoke_user_group_permission"
 
    args:     {
 
                "repoid" : "<reponame or repo_id>"
 
                "usersgroupid" : "<user group id or name>"
 
              }
 

	
 
OUTPUT::
 

	
 
    id : <id_given_in_input>
 
    result: {
 
              "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
 
              "success": true
 
            }
 
    error:  null
kallithea/__init__.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.__init__
 
~~~~~~~~~~~~~~~~~~
 

	
 
Kallithea, a web based repository management based on pylons
 
versioning implementation: http://www.python.org/dev/peps/pep-0386/
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Apr 9, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, (C) 2014 Bradley M. Kuhn, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import sys
 
import platform
 

	
 
VERSION = (0, 2, 1)
 
VERSION = (0, 2, 2)
 
BACKENDS = {
 
    'hg': 'Mercurial repository',
 
    'git': 'Git repository',
 
}
 

	
 
CELERY_ON = False
 
CELERY_EAGER = False
 

	
 
# link to config for pylons
 
CONFIG = {}
 

	
 
# Linked module for extensions
 
EXTENSIONS = {}
 

	
 
# BRAND controls internal references in database and config to the products
 
# own name.
 
#
 
# NOTE: If you want compatibility with a database that was originally created
 
#  for use with the RhodeCode software product, change BRAND to "rhodecode",
 
#  either by editing here or by creating a new file:
 
#  echo "BRAND = 'rhodecode'" > kallithea/brand.py
 

	
 
BRAND = "kallithea"
 
try:
 
    from kallithea.brand import BRAND
 
except ImportError:
 
    pass
 

	
 
# Prefix for the ui and settings table names
 
DB_PREFIX = (BRAND + "_") if BRAND != "kallithea" else ""
 

	
 
# Users.extern_type and .extern_name value for local users
 
EXTERN_TYPE_INTERNAL = BRAND if BRAND != 'kallithea' else 'internal'
 

	
 
# db_migrate_version.repository_id value, same as kallithea/lib/dbmigrate/migrate.cfg
 
DB_MIGRATIONS = BRAND + "_db_migrations"
 

	
 
try:
 
    from kallithea.lib import get_current_revision
 
    _rev = get_current_revision(quiet=True)
 
    if _rev and len(VERSION) > 3:
 
        VERSION += ('%s' % _rev[0],)
 
except ImportError:
 
    pass
 

	
 
__version__ = ('.'.join((str(each) for each in VERSION[:3])))
 
__dbversion__ = 31  # defines current db version for migrations
 
__platform__ = platform.system()
 
__license__ = 'GPLv3'
 
__py_version__ = sys.version_info
 
__author__ = "Various Authors"
 
__url__ = 'https://kallithea-scm.org/'
 

	
 
is_windows = __platform__ in ['Windows']
 
is_unix = not is_windows
 

	
 
if len(VERSION) > 3:
 
    __version__ += '.'+VERSION[3]
 

	
 
    if len(VERSION) > 4:
 
        __version__ += VERSION[4]
 
    else:
 
        __version__ += '0'
kallithea/controllers/admin/my_account.py
Show inline comments
 
@@ -71,202 +71,202 @@ class MyAccountController(BaseController
 
                      " crucial for entire application"), category='warning')
 
            return redirect(url('users'))
 
        c.EXTERN_TYPE_INTERNAL = EXTERN_TYPE_INTERNAL
 

	
 
    def _load_my_repos_data(self, watched=False):
 
        if watched:
 
            admin = False
 
            repos_list = [x.follows_repository for x in
 
                          Session().query(UserFollowing).filter(
 
                              UserFollowing.user_id ==
 
                              self.authuser.user_id).all()]
 
        else:
 
            admin = True
 
            repos_list = Session().query(Repository)\
 
                         .filter(Repository.user_id ==
 
                                 self.authuser.user_id)\
 
                         .order_by(func.lower(Repository.repo_name)).all()
 

	
 
        repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
 
                                                   admin=admin)
 
        #json used to render the grid
 
        return json.dumps(repos_data)
 

	
 
    def my_account(self):
 
        """
 
        GET /_admin/my_account Displays info about my account
 
        """
 
        # url('my_account')
 
        c.active = 'profile'
 
        self.__load_data()
 
        c.perm_user = AuthUser(user_id=self.authuser.user_id)
 
        c.ip_addr = self.ip_addr
 
        c.extern_type = c.user.extern_type
 
        c.extern_name = c.user.extern_name
 

	
 
        defaults = c.user.get_dict()
 
        update = False
 
        if request.POST:
 
            _form = UserForm(edit=True,
 
                             old_data={'user_id': self.authuser.user_id,
 
                                       'email': self.authuser.email})()
 
            form_result = {}
 
            try:
 
                post_data = dict(request.POST)
 
                post_data['new_password'] = ''
 
                post_data['password_confirmation'] = ''
 
                form_result = _form.to_python(post_data)
 
                # skip updating those attrs for my account
 
                skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
 
                              'new_password', 'password_confirmation']
 
                #TODO: plugin should define if username can be updated
 
                if c.extern_type != EXTERN_TYPE_INTERNAL:
 
                    # forbid updating username for external accounts
 
                    skip_attrs.append('username')
 

	
 
                UserModel().update(self.authuser.user_id, form_result,
 
                                   skip_attrs=skip_attrs)
 
                h.flash(_('Your account was updated successfully'),
 
                        category='success')
 
                Session().commit()
 
                update = True
 

	
 
            except formencode.Invalid, errors:
 
                return htmlfill.render(
 
                    render('admin/my_account/my_account.html'),
 
                    defaults=errors.value,
 
                    errors=errors.error_dict or {},
 
                    prefix_error=False,
 
                    encoding="UTF-8",
 
                    force_defaults=False)
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                h.flash(_('Error occurred during update of user %s') \
 
                        % form_result.get('username'), category='error')
 
        if update:
 
            return redirect('my_account')
 
        return htmlfill.render(
 
            render('admin/my_account/my_account.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    def my_account_password(self):
 
        c.active = 'password'
 
        self.__load_data()
 
        if request.POST:
 
            _form = PasswordChangeForm(self.authuser.username)()
 
            try:
 
                form_result = _form.to_python(request.POST)
 
                UserModel().update(self.authuser.user_id, form_result)
 
                Session().commit()
 
                h.flash(_("Successfully updated password"), category='success')
 
            except formencode.Invalid as errors:
 
                return htmlfill.render(
 
                    render('admin/my_account/my_account.html'),
 
                    defaults=errors.value,
 
                    errors=errors.error_dict or {},
 
                    prefix_error=False,
 
                    encoding="UTF-8",
 
                    force_defaults=False)
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                h.flash(_('Error occurred during update of user password'),
 
                        category='error')
 
        return render('admin/my_account/my_account.html')
 

	
 
    def my_account_repos(self):
 
        c.active = 'repos'
 
        self.__load_data()
 

	
 
        #json used to render the grid
 
        c.data = self._load_my_repos_data()
 
        return render('admin/my_account/my_account.html')
 

	
 
    def my_account_watched(self):
 
        c.active = 'watched'
 
        self.__load_data()
 

	
 
        #json used to render the grid
 
        c.data = self._load_my_repos_data(watched=True)
 
        return render('admin/my_account/my_account.html')
 

	
 
    def my_account_perms(self):
 
        c.active = 'perms'
 
        self.__load_data()
 
        c.perm_user = AuthUser(user_id=self.authuser.user_id)
 
        c.ip_addr = self.ip_addr
 

	
 
        return render('admin/my_account/my_account.html')
 

	
 
    def my_account_emails(self):
 
        c.active = 'emails'
 
        self.__load_data()
 

	
 
        c.user_email_map = UserEmailMap.query()\
 
            .filter(UserEmailMap.user == c.user).all()
 
        return render('admin/my_account/my_account.html')
 

	
 
    def my_account_emails_add(self):
 
        email = request.POST.get('new_email')
 

	
 
        try:
 
            UserModel().add_extra_email(self.authuser.user_id, email)
 
            Session().commit()
 
            h.flash(_("Added email %s to user") % email, category='success')
 
        except formencode.Invalid, error:
 
            msg = error.error_dict['email']
 
            h.flash(msg, category='error')
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('An error occurred during email saving'),
 
                    category='error')
 
        return redirect(url('my_account_emails'))
 

	
 
    def my_account_emails_delete(self):
 
        email_id = request.POST.get('del_email_id')
 
        user_model = UserModel()
 
        user_model.delete_extra_email(self.authuser.user_id, email_id)
 
        Session().commit()
 
        h.flash(_("Removed email from user"), category='success')
 
        return redirect(url('my_account_emails'))
 

	
 
    def my_account_api_keys(self):
 
        c.active = 'api_keys'
 
        self.__load_data()
 
        show_expired = True
 
        c.lifetime_values = [
 
            (str(-1), _('Forever')),
 
            (str(5), _('5 minutes')),
 
            (str(60), _('1 hour')),
 
            (str(60 * 24), _('1 day')),
 
            (str(60 * 24 * 30), _('1 month')),
 
        ]
 
        c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
 
        c.user_api_keys = ApiKeyModel().get_api_keys(self.authuser.user_id,
 
                                                     show_expired=show_expired)
 
        return render('admin/my_account/my_account.html')
 

	
 
    def my_account_api_keys_add(self):
 
        lifetime = safe_int(request.POST.get('lifetime'), -1)
 
        description = request.POST.get('description')
 
        ApiKeyModel().create(self.authuser.user_id, description, lifetime)
 
        Session().commit()
 
        h.flash(_("API key successfully created"), category='success')
 
        return redirect(url('my_account_api_keys'))
 

	
 
    def my_account_api_keys_delete(self):
 
        api_key = request.POST.get('del_api_key')
 
        user_id = self.authuser.user_id
 
        if request.POST.get('del_api_key_builtin'):
 
            user = User.get(user_id)
 
            if user:
 
                user.api_key = generate_api_key(user.username)
 
                user.api_key = generate_api_key()
 
                Session().add(user)
 
                Session().commit()
 
                h.flash(_("API key successfully reset"), category='success')
 
        elif api_key:
 
            ApiKeyModel().delete(api_key, self.authuser.user_id)
 
            Session().commit()
 
            h.flash(_("API key successfully deleted"), category='success')
 

	
 
        return redirect(url('my_account_api_keys'))
kallithea/controllers/admin/users.py
Show inline comments
 
@@ -128,362 +128,362 @@ class UsersController(BaseController):
 
            form_result = user_form.to_python(dict(request.POST))
 
            user = user_model.create(form_result)
 
            usr = form_result['username']
 
            action_logger(self.authuser, 'admin_created_user:%s' % usr,
 
                          None, self.ip_addr, self.sa)
 
            h.flash(h.literal(_('Created user %s') % h.link_to(h.escape(usr), url('edit_user', id=user.user_id))),
 
                    category='success')
 
            Session().commit()
 
        except formencode.Invalid, errors:
 
            return htmlfill.render(
 
                render('admin/users/user_add.html'),
 
                defaults=errors.value,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                encoding="UTF-8",
 
                force_defaults=False)
 
        except UserCreationError, e:
 
            h.flash(e, 'error')
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('Error occurred during creation of user %s') \
 
                    % request.POST.get('username'), category='error')
 
        return redirect(url('users'))
 

	
 
    def new(self, format='html'):
 
        """GET /users/new: Form to create a new item"""
 
        # url('new_user')
 
        c.default_extern_type = auth_modules.auth_internal.KallitheaAuthPlugin.name
 
        return render('admin/users/user_add.html')
 

	
 
    def update(self, id):
 
        """PUT /users/id: Update an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="PUT" />
 
        # Or using helpers:
 
        #    h.form(url('update_user', id=ID),
 
        #           method='put')
 
        # url('user', id=ID)
 
        c.active = 'profile'
 
        user_model = UserModel()
 
        c.user = user_model.get(id)
 
        c.extern_type = c.user.extern_type
 
        c.extern_name = c.user.extern_name
 
        c.perm_user = AuthUser(user_id=id)
 
        c.ip_addr = self.ip_addr
 
        _form = UserForm(edit=True, old_data={'user_id': id,
 
                                              'email': c.user.email})()
 
        form_result = {}
 
        try:
 
            form_result = _form.to_python(dict(request.POST))
 
            skip_attrs = ['extern_type', 'extern_name']
 
            #TODO: plugin should define if username can be updated
 
            if c.extern_type != kallithea.EXTERN_TYPE_INTERNAL:
 
                # forbid updating username for external accounts
 
                skip_attrs.append('username')
 

	
 
            user_model.update(id, form_result, skip_attrs=skip_attrs)
 
            usr = form_result['username']
 
            action_logger(self.authuser, 'admin_updated_user:%s' % usr,
 
                          None, self.ip_addr, self.sa)
 
            h.flash(_('User updated successfully'), category='success')
 
            Session().commit()
 
        except formencode.Invalid, errors:
 
            defaults = errors.value
 
            e = errors.error_dict or {}
 
            defaults.update({
 
                'create_repo_perm': user_model.has_perm(id,
 
                                                        'hg.create.repository'),
 
                'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
 
                '_method': 'put'
 
            })
 
            return htmlfill.render(
 
                render('admin/users/user_edit.html'),
 
                defaults=defaults,
 
                errors=e,
 
                prefix_error=False,
 
                encoding="UTF-8",
 
                force_defaults=False)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('Error occurred during update of user %s') \
 
                    % form_result.get('username'), category='error')
 
        return redirect(url('edit_user', id=id))
 

	
 
    def delete(self, id):
 
        """DELETE /users/id: Delete an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="DELETE" />
 
        # Or using helpers:
 
        #    h.form(url('delete_user', id=ID),
 
        #           method='delete')
 
        # url('user', id=ID)
 
        usr = User.get_or_404(id)
 
        try:
 
            UserModel().delete(usr)
 
            Session().commit()
 
            h.flash(_('Successfully deleted user'), category='success')
 
        except (UserOwnsReposException, DefaultUserException), e:
 
            h.flash(e, category='warning')
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('An error occurred during deletion of user'),
 
                    category='error')
 
        return redirect(url('users'))
 

	
 
    def show(self, id, format='html'):
 
        """GET /users/id: Show a specific item"""
 
        # url('user', id=ID)
 
        User.get_or_404(-1)
 

	
 
    def _get_user_or_raise_if_default(self, id):
 
        try:
 
            return User.get_or_404(id, allow_default=False)
 
        except DefaultUserException:
 
            h.flash(_("The default user cannot be edited"), category='warning')
 
            raise HTTPNotFound
 

	
 
    def edit(self, id, format='html'):
 
        """GET /users/id/edit: Form to edit an existing item"""
 
        # url('edit_user', id=ID)
 
        c.user = self._get_user_or_raise_if_default(id)
 
        c.active = 'profile'
 
        c.extern_type = c.user.extern_type
 
        c.extern_name = c.user.extern_name
 
        c.perm_user = AuthUser(user_id=id)
 
        c.ip_addr = self.ip_addr
 

	
 
        defaults = c.user.get_dict()
 
        return htmlfill.render(
 
            render('admin/users/user_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    def edit_advanced(self, id):
 
        c.user = self._get_user_or_raise_if_default(id)
 
        c.active = 'advanced'
 
        c.perm_user = AuthUser(user_id=id)
 
        c.ip_addr = self.ip_addr
 

	
 
        umodel = UserModel()
 
        defaults = c.user.get_dict()
 
        defaults.update({
 
            'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
 
            'create_user_group_perm': umodel.has_perm(c.user,
 
                                                      'hg.usergroup.create.true'),
 
            'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
 
        })
 
        return htmlfill.render(
 
            render('admin/users/user_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    def edit_api_keys(self, id):
 
        c.user = self._get_user_or_raise_if_default(id)
 
        c.active = 'api_keys'
 
        show_expired = True
 
        c.lifetime_values = [
 
            (str(-1), _('Forever')),
 
            (str(5), _('5 minutes')),
 
            (str(60), _('1 hour')),
 
            (str(60 * 24), _('1 day')),
 
            (str(60 * 24 * 30), _('1 month')),
 
        ]
 
        c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
 
        c.user_api_keys = ApiKeyModel().get_api_keys(c.user.user_id,
 
                                                     show_expired=show_expired)
 
        defaults = c.user.get_dict()
 
        return htmlfill.render(
 
            render('admin/users/user_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    def add_api_key(self, id):
 
        c.user = self._get_user_or_raise_if_default(id)
 

	
 
        lifetime = safe_int(request.POST.get('lifetime'), -1)
 
        description = request.POST.get('description')
 
        ApiKeyModel().create(c.user.user_id, description, lifetime)
 
        Session().commit()
 
        h.flash(_("API key successfully created"), category='success')
 
        return redirect(url('edit_user_api_keys', id=c.user.user_id))
 

	
 
    def delete_api_key(self, id):
 
        c.user = self._get_user_or_raise_if_default(id)
 

	
 
        api_key = request.POST.get('del_api_key')
 
        if request.POST.get('del_api_key_builtin'):
 
            user = User.get(c.user.user_id)
 
            if user:
 
                user.api_key = generate_api_key(user.username)
 
                user.api_key = generate_api_key()
 
                Session().add(user)
 
                Session().commit()
 
                h.flash(_("API key successfully reset"), category='success')
 
        elif api_key:
 
            ApiKeyModel().delete(api_key, c.user.user_id)
 
            Session().commit()
 
            h.flash(_("API key successfully deleted"), category='success')
 

	
 
        return redirect(url('edit_user_api_keys', id=c.user.user_id))
 

	
 
    def update_account(self, id):
 
        pass
 

	
 
    def edit_perms(self, id):
 
        c.user = self._get_user_or_raise_if_default(id)
 
        c.active = 'perms'
 
        c.perm_user = AuthUser(user_id=id)
 
        c.ip_addr = self.ip_addr
 

	
 
        umodel = UserModel()
 
        defaults = c.user.get_dict()
 
        defaults.update({
 
            'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
 
            'create_user_group_perm': umodel.has_perm(c.user,
 
                                                      'hg.usergroup.create.true'),
 
            'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
 
        })
 
        return htmlfill.render(
 
            render('admin/users/user_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    def update_perms(self, id):
 
        """PUT /users_perm/id: Update an existing item"""
 
        # url('user_perm', id=ID, method='put')
 
        user = self._get_user_or_raise_if_default(id)
 

	
 
        try:
 
            form = CustomDefaultPermissionsForm()()
 
            form_result = form.to_python(request.POST)
 

	
 
            inherit_perms = form_result['inherit_default_permissions']
 
            user.inherit_default_permissions = inherit_perms
 
            Session().add(user)
 
            user_model = UserModel()
 

	
 
            defs = UserToPerm.query()\
 
                .filter(UserToPerm.user == user)\
 
                .all()
 
            for ug in defs:
 
                Session().delete(ug)
 

	
 
            if form_result['create_repo_perm']:
 
                user_model.grant_perm(id, 'hg.create.repository')
 
            else:
 
                user_model.grant_perm(id, 'hg.create.none')
 
            if form_result['create_user_group_perm']:
 
                user_model.grant_perm(id, 'hg.usergroup.create.true')
 
            else:
 
                user_model.grant_perm(id, 'hg.usergroup.create.false')
 
            if form_result['fork_repo_perm']:
 
                user_model.grant_perm(id, 'hg.fork.repository')
 
            else:
 
                user_model.grant_perm(id, 'hg.fork.none')
 
            h.flash(_("Updated permissions"), category='success')
 
            Session().commit()
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('An error occurred during permissions saving'),
 
                    category='error')
 
        return redirect(url('edit_user_perms', id=id))
 

	
 
    def edit_emails(self, id):
 
        c.user = self._get_user_or_raise_if_default(id)
 
        c.active = 'emails'
 
        c.user_email_map = UserEmailMap.query()\
 
            .filter(UserEmailMap.user == c.user).all()
 

	
 
        defaults = c.user.get_dict()
 
        return htmlfill.render(
 
            render('admin/users/user_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    def add_email(self, id):
 
        """POST /user_emails:Add an existing item"""
 
        # url('user_emails', id=ID, method='put')
 
        user = self._get_user_or_raise_if_default(id)
 
        email = request.POST.get('new_email')
 
        user_model = UserModel()
 

	
 
        try:
 
            user_model.add_extra_email(id, email)
 
            Session().commit()
 
            h.flash(_("Added email %s to user") % email, category='success')
 
        except formencode.Invalid, error:
 
            msg = error.error_dict['email']
 
            h.flash(msg, category='error')
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('An error occurred during email saving'),
 
                    category='error')
 
        return redirect(url('edit_user_emails', id=id))
 

	
 
    def delete_email(self, id):
 
        """DELETE /user_emails_delete/id: Delete an existing item"""
 
        # url('user_emails_delete', id=ID, method='delete')
 
        user = self._get_user_or_raise_if_default(id)
 
        email_id = request.POST.get('del_email_id')
 
        user_model = UserModel()
 
        user_model.delete_extra_email(id, email_id)
 
        Session().commit()
 
        h.flash(_("Removed email from user"), category='success')
 
        return redirect(url('edit_user_emails', id=id))
 

	
 
    def edit_ips(self, id):
 
        c.user = self._get_user_or_raise_if_default(id)
 
        c.active = 'ips'
 
        c.user_ip_map = UserIpMap.query()\
 
            .filter(UserIpMap.user == c.user).all()
 

	
 
        c.inherit_default_ips = c.user.inherit_default_permissions
 
        c.default_user_ip_map = UserIpMap.query()\
 
            .filter(UserIpMap.user == User.get_default_user()).all()
 

	
 
        defaults = c.user.get_dict()
 
        return htmlfill.render(
 
            render('admin/users/user_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False)
 

	
 
    def add_ip(self, id):
 
        """POST /user_ips:Add an existing item"""
 
        # url('user_ips', id=ID, method='put')
 

	
 
        ip = request.POST.get('new_ip')
 
        user_model = UserModel()
 

	
 
        try:
 
            user_model.add_extra_ip(id, ip)
 
            Session().commit()
 
            h.flash(_("Added IP address %s to user whitelist") % ip, category='success')
 
        except formencode.Invalid, error:
 
            msg = error.error_dict['ip']
 
            h.flash(msg, category='error')
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('An error occurred during ip saving'),
 
                    category='error')
 

	
 
        if 'default_user' in request.POST:
 
            return redirect(url('admin_permissions_ips'))
 
        return redirect(url('edit_user_ips', id=id))
 

	
 
    def delete_ip(self, id):
 
        """DELETE /user_ips_delete/id: Delete an existing item"""
 
        # url('user_ips_delete', id=ID, method='delete')
 
        ip_id = request.POST.get('del_ip_id')
 
        user_model = UserModel()
 
        user_model.delete_extra_ip(id, ip_id)
 
        Session().commit()
 
        h.flash(_("Removed IP address from user whitelist"), category='success')
 

	
 
        if 'default_user' in request.POST:
 
            return redirect(url('admin_permissions_ips'))
 
        return redirect(url('edit_user_ips', id=id))
kallithea/controllers/api/api.py
Show inline comments
 
@@ -1367,481 +1367,495 @@ class ApiController(JSONRPCController):
 

	
 
        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
 
            # check if we have admin permission for this repo !
 
            perms = ('repository.admin', 'repository.write', 'repository.read')
 
            if not HasRepoPermissionAnyApi(*perms)(user=apiuser, repo_name=repo.repo_name):
 
                raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 

	
 
        ret_type = Optional.extract(ret_type)
 
        _map = {}
 
        try:
 
            _d, _f = ScmModel().get_nodes(repo, revision, root_path,
 
                                          flat=False)
 
            _map = {
 
                'all': _d + _f,
 
                'files': _f,
 
                'dirs': _d,
 
            }
 
            return _map[ret_type]
 
        except KeyError:
 
            raise JSONRPCError('ret_type must be one of %s'
 
                               % (','.join(_map.keys())))
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError(
 
                'failed to get repo: `%s` nodes' % repo.repo_name
 
            )
 

	
 
    @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
 
    def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')),
 
                    repo_type=Optional('hg'), description=Optional(''),
 
                    private=Optional(False), clone_uri=Optional(None),
 
                    landing_rev=Optional('rev:tip'),
 
                    enable_statistics=Optional(False),
 
                    enable_locking=Optional(False),
 
                    enable_downloads=Optional(False),
 
                    copy_permissions=Optional(False)):
 
        """
 
        Creates a repository. If repository name contains "/", all needed repository
 
        groups will be created. For example "foo/bar/baz" will create groups
 
        "foo", "bar" (with "foo" as parent), and create "baz" repository with
 
        "bar" as group. This command can be executed only using api_key
 
        belonging to user with admin rights or regular user that have create
 
        repository permission. Regular users cannot specify owner parameter
 

	
 
        :param apiuser: filled automatically from apikey
 
        :type apiuser: AuthUser
 
        :param repo_name: repository name
 
        :type repo_name: str
 
        :param owner: user_id or username
 
        :type owner: Optional(str)
 
        :param repo_type: 'hg' or 'git'
 
        :type repo_type: Optional(str)
 
        :param description: repository description
 
        :type description: Optional(str)
 
        :param private:
 
        :type private: bool
 
        :param clone_uri:
 
        :type clone_uri: str
 
        :param landing_rev: <rev_type>:<rev>
 
        :type landing_rev: str
 
        :param enable_locking:
 
        :type enable_locking: bool
 
        :param enable_downloads:
 
        :type enable_downloads: bool
 
        :param enable_statistics:
 
        :type enable_statistics: bool
 
        :param copy_permissions: Copy permission from group that repository is
 
            being created.
 
        :type copy_permissions: bool
 

	
 
        OUTPUT::
 

	
 
            id : <id_given_in_input>
 
            result: {
 
                      "msg": "Created new repository `<reponame>`",
 
                      "success": true,
 
                      "task": "<celery task id or None if done sync>"
 
                    }
 
            error:  null
 

	
 
        ERROR OUTPUT::
 

	
 
          id : <id_given_in_input>
 
          result : null
 
          error :  {
 
             'failed to create repository `<repo_name>`
 
          }
 

	
 
        """
 
        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
 
            if not isinstance(owner, Optional):
 
                #forbid setting owner for non-admins
 
                raise JSONRPCError(
 
                    'Only Kallithea admin can specify `owner` param'
 
                )
 
        if isinstance(owner, Optional):
 
            owner = apiuser.user_id
 

	
 
        owner = get_user_or_error(owner)
 

	
 
        if RepoModel().get_by_repo_name(repo_name):
 
            raise JSONRPCError("repo `%s` already exist" % repo_name)
 

	
 
        defs = Setting.get_default_repo_settings(strip_prefix=True)
 
        if isinstance(private, Optional):
 
            private = defs.get('repo_private') or Optional.extract(private)
 
        if isinstance(repo_type, Optional):
 
            repo_type = defs.get('repo_type')
 
        if isinstance(enable_statistics, Optional):
 
            enable_statistics = defs.get('repo_enable_statistics')
 
        if isinstance(enable_locking, Optional):
 
            enable_locking = defs.get('repo_enable_locking')
 
        if isinstance(enable_downloads, Optional):
 
            enable_downloads = defs.get('repo_enable_downloads')
 

	
 
        clone_uri = Optional.extract(clone_uri)
 
        description = Optional.extract(description)
 
        landing_rev = Optional.extract(landing_rev)
 
        copy_permissions = Optional.extract(copy_permissions)
 

	
 
        try:
 
            repo_name_cleaned = repo_name.split('/')[-1]
 
            # create structure of groups and return the last group
 
            repo_group = map_groups(repo_name)
 
            data = dict(
 
                repo_name=repo_name_cleaned,
 
                repo_name_full=repo_name,
 
                repo_type=repo_type,
 
                repo_description=description,
 
                owner=owner,
 
                repo_private=private,
 
                clone_uri=clone_uri,
 
                repo_group=repo_group,
 
                repo_landing_rev=landing_rev,
 
                enable_statistics=enable_statistics,
 
                enable_locking=enable_locking,
 
                enable_downloads=enable_downloads,
 
                repo_copy_permissions=copy_permissions,
 
            )
 

	
 
            task = RepoModel().create(form_data=data, cur_user=owner)
 
            from celery.result import BaseAsyncResult
 
            task_id = None
 
            if isinstance(task, BaseAsyncResult):
 
                task_id = task.task_id
 
            # no commit, it's done in RepoModel, or async via celery
 
            return dict(
 
                msg="Created new repository `%s`" % (repo_name,),
 
                success=True,  # cannot return the repo data here since fork
 
                               # can be done async
 
                task=task_id
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError(
 
                'failed to create repository `%s`' % (repo_name,))
 

	
 
    # permission check inside
 
    def update_repo(self, apiuser, repoid, name=Optional(None),
 
                    owner=Optional(OAttr('apiuser')),
 
                    group=Optional(None),
 
                    description=Optional(''), private=Optional(False),
 
                    clone_uri=Optional(None), landing_rev=Optional('rev:tip'),
 
                    enable_statistics=Optional(False),
 
                    enable_locking=Optional(False),
 
                    enable_downloads=Optional(False)):
 

	
 
        """
 
        Updates repo
 

	
 
        :param apiuser: filled automatically from apikey
 
        :type apiuser: AuthUser
 
        :param repoid: repository name or repository id
 
        :type repoid: str or int
 
        :param name:
 
        :param owner:
 
        :param group:
 
        :param description:
 
        :param private:
 
        :param clone_uri:
 
        :param landing_rev:
 
        :param enable_statistics:
 
        :param enable_locking:
 
        :param enable_downloads:
 
        """
 
        repo = get_repo_or_error(repoid)
 
        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
 
            # check if we have admin permission for this repo !
 
            if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
 
                                                               repo_name=repo.repo_name):
 
                raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 

	
 
            if (name != repo.repo_name and
 
                not HasPermissionAnyApi('hg.create.repository')(user=apiuser)
 
                ):
 
                raise JSONRPCError('no permission to create (or move) repositories')
 

	
 
            if not isinstance(owner, Optional):
 
                #forbid setting owner for non-admins
 
                raise JSONRPCError(
 
                    'Only Kallithea admin can specify `owner` param'
 
                )
 

	
 
        updates = {
 
            # update function requires this.
 
            'repo_name': repo.repo_name
 
        }
 
        repo_group = group
 
        if not isinstance(repo_group, Optional):
 
            repo_group = get_repo_group_or_error(repo_group)
 
            repo_group = repo_group.group_id
 
        try:
 
            store_update(updates, name, 'repo_name')
 
            store_update(updates, repo_group, 'repo_group')
 
            store_update(updates, owner, 'user')
 
            store_update(updates, description, 'repo_description')
 
            store_update(updates, private, 'repo_private')
 
            store_update(updates, clone_uri, 'clone_uri')
 
            store_update(updates, landing_rev, 'repo_landing_rev')
 
            store_update(updates, enable_statistics, 'repo_enable_statistics')
 
            store_update(updates, enable_locking, 'repo_enable_locking')
 
            store_update(updates, enable_downloads, 'repo_enable_downloads')
 

	
 
            RepoModel().update(repo, **updates)
 
            Session().commit()
 
            return dict(
 
                msg='updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
 
                repository=repo.get_api_data()
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError('failed to update repo `%s`' % repoid)
 

	
 
    @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
 
    def fork_repo(self, apiuser, repoid, fork_name,
 
                  owner=Optional(OAttr('apiuser')),
 
                  description=Optional(''), copy_permissions=Optional(False),
 
                  private=Optional(False), landing_rev=Optional('rev:tip')):
 
        """
 
        Creates a fork of given repo. In case of using celery this will
 
        immediately return success message, while fork is going to be created
 
        asynchronous. This command can be executed only using api_key belonging to
 
        user with admin rights or regular user that have fork permission, and at least
 
        read access to forking repository. Regular users cannot specify owner parameter.
 

	
 
        :param apiuser: filled automatically from apikey
 
        :type apiuser: AuthUser
 
        :param repoid: repository name or repository id
 
        :type repoid: str or int
 
        :param fork_name:
 
        :param owner:
 
        :param description:
 
        :param copy_permissions:
 
        :param private:
 
        :param landing_rev:
 

	
 
        INPUT::
 

	
 
            id : <id_for_response>
 
            api_key : "<api_key>"
 
            args:     {
 
                        "repoid" :          "<reponame or repo_id>",
 
                        "fork_name":        "<forkname>",
 
                        "owner":            "<username or user_id = Optional(=apiuser)>",
 
                        "description":      "<description>",
 
                        "copy_permissions": "<bool>",
 
                        "private":          "<bool>",
 
                        "landing_rev":      "<landing_rev>"
 
                      }
 

	
 
        OUTPUT::
 

	
 
            id : <id_given_in_input>
 
            result: {
 
                      "msg": "Created fork of `<reponame>` as `<forkname>`",
 
                      "success": true,
 
                      "task": "<celery task id or None if done sync>"
 
                    }
 
            error:  null
 

	
 
        """
 
        repo = get_repo_or_error(repoid)
 
        repo_name = repo.repo_name
 

	
 
        _repo = RepoModel().get_by_repo_name(fork_name)
 
        if _repo:
 
            type_ = 'fork' if _repo.fork else 'repo'
 
            raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
 

	
 
        if HasPermissionAnyApi('hg.admin')(user=apiuser):
 
            pass
 
        elif HasRepoPermissionAnyApi('repository.admin',
 
                                     'repository.write',
 
                                     'repository.read')(user=apiuser,
 
                                                        repo_name=repo.repo_name):
 
            if not isinstance(owner, Optional):
 
                #forbid setting owner for non-admins
 
                raise JSONRPCError(
 
                    'Only Kallithea admin can specify `owner` param'
 
                )
 

	
 
            if not HasPermissionAnyApi('hg.create.repository')(user=apiuser):
 
                raise JSONRPCError('no permission to create repositories')
 
        else:
 
            raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 

	
 
        if isinstance(owner, Optional):
 
            owner = apiuser.user_id
 

	
 
        owner = get_user_or_error(owner)
 

	
 
        try:
 
            # create structure of groups and return the last group
 
            group = map_groups(fork_name)
 

	
 
            form_data = dict(
 
                repo_name=fork_name,
 
                repo_name_full=fork_name,
 
                repo_group=group,
 
                repo_type=repo.repo_type,
 
                description=Optional.extract(description),
 
                private=Optional.extract(private),
 
                copy_permissions=Optional.extract(copy_permissions),
 
                landing_rev=Optional.extract(landing_rev),
 
                update_after_clone=False,
 
                fork_parent_id=repo.repo_id,
 
            )
 
            task = RepoModel().create_fork(form_data, cur_user=owner)
 
            # no commit, it's done in RepoModel, or async via celery
 
            from celery.result import BaseAsyncResult
 
            task_id = None
 
            if isinstance(task, BaseAsyncResult):
 
                task_id = task.task_id
 
            return dict(
 
                msg='Created fork of `%s` as `%s`' % (repo.repo_name,
 
                                                      fork_name),
 
                success=True,  # cannot return the repo data here since fork
 
                               # can be done async
 
                task=task_id
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError(
 
                'failed to fork repository `%s` as `%s`' % (repo_name,
 
                                                            fork_name)
 
            )
 

	
 
    # permission check inside
 
    def delete_repo(self, apiuser, repoid, forks=Optional('')):
 
        """
 
        Deletes a repository. This command can be executed only using api_key belonging
 
        to user with admin rights or regular user that have admin access to repository.
 
        When `forks` param is set it's possible to detach or delete forks of deleting
 
        repository
 

	
 
        :param apiuser: filled automatically from apikey
 
        :type apiuser: AuthUser
 
        :param repoid: repository name or repository id
 
        :type repoid: str or int
 
        :param forks: `detach` or `delete`, what do do with attached forks for repo
 
        :type forks: Optional(str)
 

	
 
        OUTPUT::
 

	
 
            id : <id_given_in_input>
 
            result: {
 
                      "msg": "Deleted repository `<reponame>`",
 
                      "success": true
 
                    }
 
            error:  null
 

	
 
        """
 
        repo = get_repo_or_error(repoid)
 

	
 
        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
 
            # check if we have admin permission for this repo !
 
            if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
 
                                                           repo_name=repo.repo_name):
 
                raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 

	
 
        try:
 
            handle_forks = Optional.extract(forks)
 
            _forks_msg = ''
 
            _forks = [f for f in repo.forks]
 
            if handle_forks == 'detach':
 
                _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
 
            elif handle_forks == 'delete':
 
                _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
 
            elif _forks:
 
                raise JSONRPCError(
 
                    'Cannot delete `%s` it still contains attached forks' %
 
                    (repo.repo_name,)
 
                )
 

	
 
            RepoModel().delete(repo, forks=forks)
 
            Session().commit()
 
            return dict(
 
                msg='Deleted repository `%s`%s' % (repo.repo_name, _forks_msg),
 
                success=True
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError(
 
                'failed to delete repository `%s`' % (repo.repo_name,)
 
            )
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def grant_user_permission(self, apiuser, repoid, userid, perm):
 
        """
 
        Grant permission for user on given repository, or update existing one
 
        if found. This command can be executed only using api_key belonging to user
 
        with admin rights.
 

	
 
        :param apiuser: filled automatically from apikey
 
        :type apiuser: AuthUser
 
        :param repoid: repository name or repository id
 
        :type repoid: str or int
 
        :param userid:
 
        :param perm: (repository.(none|read|write|admin))
 
        :type perm: str
 

	
 
        OUTPUT::
 

	
 
            id : <id_given_in_input>
 
            result: {
 
                      "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
 
                      "success": true
 
                    }
 
            error:  null
 
        """
 
        repo = get_repo_or_error(repoid)
 
        user = get_user_or_error(userid)
 
        perm = get_perm_or_error(perm)
 

	
 
        try:
 

	
 
            RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
 

	
 
            Session().commit()
 
            return dict(
 
                msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
 
                    perm.permission_name, user.username, repo.repo_name
 
                ),
 
                success=True
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError(
 
                'failed to edit permission for user: `%s` in repo: `%s`' % (
 
                    userid, repoid
 
                )
 
            )
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def revoke_user_permission(self, apiuser, repoid, userid):
 
        """
 
        Revoke permission for user on given repository. This command can be executed
 
        only using api_key belonging to user with admin rights.
 

	
 
        :param apiuser: filled automatically from apikey
 
        :type apiuser: AuthUser
 
        :param repoid: repository name or repository id
 
        :type repoid: str or int
 
        :param userid:
 

	
 
        OUTPUT::
 

	
 
            id : <id_given_in_input>
 
            result: {
 
                      "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
 
                      "success": true
 
                    }
 
            error:  null
 

	
 
        """
 

	
 
        repo = get_repo_or_error(repoid)
 
        user = get_user_or_error(userid)
 
        try:
 
            RepoModel().revoke_user_permission(repo=repo, user=user)
 
            Session().commit()
 
            return dict(
 
                msg='Revoked perm for user: `%s` in repo: `%s`' % (
 
                    user.username, repo.repo_name
 
                ),
 
                success=True
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError(
 
                'failed to edit permission for user: `%s` in repo: `%s`' % (
 
                    userid, repoid
 
                )
 
            )
 

	
kallithea/i18n/be/LC_MESSAGES/kallithea.po
Show inline comments
 
@@ -818,393 +818,393 @@ msgstr "Нічога"
 
msgid "Marked repo %s as fork of %s"
 
msgstr "Рэпазітар %s адзначаны як форк %s"
 

	
 
#: kallithea/controllers/admin/repos.py:559
 
msgid "An error occurred during this operation"
 
msgstr "Адбылася памылка пры выкананні аперацыі"
 

	
 
#: kallithea/controllers/admin/repos.py:575
 
msgid "Locked repository"
 
msgstr "Зачынены рэпазітар"
 

	
 
#: kallithea/controllers/admin/repos.py:578
 
msgid "Unlocked repository"
 
msgstr "Адкрыты рэпазітар"
 

	
 
#: kallithea/controllers/admin/repos.py:581
 
#: kallithea/controllers/admin/repos.py:608
 
msgid "An error occurred during unlocking"
 
msgstr "Адбылася памылка падчас разблакавання"
 

	
 
#: kallithea/controllers/admin/repos.py:599
 
msgid "Unlocked"
 
msgstr "Разблакавана"
 

	
 
#: kallithea/controllers/admin/repos.py:602
 
msgid "Locked"
 
msgstr "Заблакавана"
 

	
 
#: kallithea/controllers/admin/repos.py:604
 
#, python-format
 
msgid "Repository has been %s"
 
msgstr "Рэпазітар %s"
 

	
 
#: kallithea/controllers/admin/repos.py:622
 
msgid "Cache invalidation successful"
 
msgstr "Кэш скінуты"
 

	
 
#: kallithea/controllers/admin/repos.py:626
 
msgid "An error occurred during cache invalidation"
 
msgstr "Адбылася памылка пры ачыстцы кэша"
 

	
 
#: kallithea/controllers/admin/repos.py:641
 
msgid "Pulled from remote location"
 
msgstr "Занесены змены з выдаленага рэпазітара"
 

	
 
#: kallithea/controllers/admin/repos.py:644
 
msgid "An error occurred during pull from remote location"
 
msgstr "Адбылася памылка пры занясенні змен з выдаленага рэпазітара"
 

	
 
#: kallithea/controllers/admin/repos.py:677
 
msgid "An error occurred during deletion of repository stats"
 
msgstr "Адбылася памылка пры выдаленні статыстыкі рэпазітара"
 

	
 
#: kallithea/controllers/admin/settings.py:170
 
msgid "Updated VCS settings"
 
msgstr "Абноўлены налады VCS"
 

	
 
#: kallithea/controllers/admin/settings.py:174
 
msgid ""
 
"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 
"missing"
 
msgstr ""
 
"Немагчыма ўключыць падтрымку hgsubversion. Бібліятэка «hgsubversion» "
 
"адсутнічае"
 

	
 
#: kallithea/controllers/admin/settings.py:180
 
#: kallithea/controllers/admin/settings.py:274
 
msgid "Error occurred during updating application settings"
 
msgstr "Адбылася памылка пры абнаўленні налад прыкладання"
 

	
 
#: kallithea/controllers/admin/settings.py:213
 
#, python-format
 
msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 
msgstr "Рэпазітары паспяхова перасканіраваны, дададзена: %s, выдалена: %s."
 

	
 
#: kallithea/controllers/admin/settings.py:270
 
msgid "Updated application settings"
 
msgstr "Абноўленыя параметры налады прыкладання"
 

	
 
#: kallithea/controllers/admin/settings.py:327
 
msgid "Updated visualisation settings"
 
msgstr "Налады візуалізацыі абноўлены"
 

	
 
#: kallithea/controllers/admin/settings.py:332
 
msgid "Error occurred during updating visualisation settings"
 
msgstr "Адбылася памылка пры абнаўленні налад візуалізацыі"
 

	
 
#: kallithea/controllers/admin/settings.py:358
 
msgid "Please enter email address"
 
msgstr "Калі ласка, увядзіце адрас электроннай пошты"
 

	
 
#: kallithea/controllers/admin/settings.py:373
 
msgid "Send email task created"
 
msgstr "Задача адпраўкі Email створана"
 

	
 
#: kallithea/controllers/admin/settings.py:404
 
msgid "Added new hook"
 
msgstr "Дададзена новая пастка"
 

	
 
#: kallithea/controllers/admin/settings.py:418
 
msgid "Updated hooks"
 
msgstr "Абноўленыя пасткі"
 

	
 
#: kallithea/controllers/admin/settings.py:422
 
msgid "Error occurred during hook creation"
 
msgstr "адбылася памылка пры стварэнні хука"
 

	
 
#: kallithea/controllers/admin/settings.py:448
 
msgid "Whoosh reindex task scheduled"
 
msgstr "Запланавана пераіндэксаванне базы Whoosh"
 

	
 
#: kallithea/controllers/admin/user_groups.py:150
 
#, python-format
 
msgid "Created user group %s"
 
msgstr "Створана група карыстачоў %s"
 

	
 
#: kallithea/controllers/admin/user_groups.py:163
 
#, python-format
 
msgid "Error occurred during creation of user group %s"
 
msgstr "Адбылася памылка пры стварэнні групы карыстачоў %s"
 

	
 
#: kallithea/controllers/admin/user_groups.py:201
 
#, python-format
 
msgid "Updated user group %s"
 
msgstr "Група карыстачоў %s абноўлена"
 

	
 
#: kallithea/controllers/admin/user_groups.py:224
 
#, python-format
 
msgid "Error occurred during update of user group %s"
 
msgstr "Адбылася памылка пры абнаўленні групы карыстачоў %s"
 

	
 
#: kallithea/controllers/admin/user_groups.py:242
 
msgid "Successfully deleted user group"
 
msgstr "Група карыстачоў паспяхова выдалена"
 

	
 
#: kallithea/controllers/admin/user_groups.py:247
 
msgid "An error occurred during deletion of user group"
 
msgstr "Адбылася памылка пры выдаленні групы карыстачоў"
 

	
 
#: kallithea/controllers/admin/user_groups.py:314
 
msgid "Target group cannot be the same"
 
msgstr "Мэтавая група не можа быць такі ж"
 

	
 
#: kallithea/controllers/admin/user_groups.py:320
 
msgid "User Group permissions updated"
 
msgstr "Прывілеі групы карыстачоў абноўлены"
 

	
 
#: kallithea/controllers/admin/user_groups.py:440
 
#: kallithea/controllers/admin/users.py:396
 
msgid "Updated permissions"
 
msgstr "Абноўлены прывілеі"
 

	
 
#: kallithea/controllers/admin/user_groups.py:444
 
#: kallithea/controllers/admin/users.py:400
 
msgid "An error occurred during permissions saving"
 
msgstr "Адбылася памылка пры захаванні прывілеяў"
 

	
 
#: kallithea/controllers/admin/users.py:132
 
#, python-format
 
msgid "Created user %s"
 
msgstr "Карыстач %s створаны"
 

	
 
#: kallithea/controllers/admin/users.py:147
 
#, python-format
 
msgid "Error occurred during creation of user %s"
 
msgstr "Адбылася памылка пры стварэнні карыстача %s"
 

	
 
#: kallithea/controllers/admin/users.py:186
 
msgid "User updated successfully"
 
msgstr "Карыстач паспяхова абноўлены"
 

	
 
#: kallithea/controllers/admin/users.py:222
 
msgid "Successfully deleted user"
 
msgstr "Карыстач паспяхова выдалены"
 

	
 
#: kallithea/controllers/admin/users.py:227
 
msgid "An error occurred during deletion of user"
 
msgstr "Адбылася памылка пры выдаленні карыстача"
 

	
 
#: kallithea/controllers/admin/users.py:241
 
#: kallithea/controllers/admin/users.py:259
 
#: kallithea/controllers/admin/users.py:282
 
#: kallithea/controllers/admin/users.py:307
 
#: kallithea/controllers/admin/users.py:320
 
#: kallithea/controllers/admin/users.py:344
 
#: kallithea/controllers/admin/users.py:407
 
#: kallithea/controllers/admin/users.py:454
 
msgid "You can't edit this user"
 
msgstr "Вы не можаце рэдагаваць дадзенага карыстача"
 

	
 
#: kallithea/controllers/admin/users.py:482
 
#, python-format
 
msgid "Added IP address %s to user whitelist"
 
msgid "Added ip %s to user whitelist"
 
msgstr "Дададзены IP %s у белы спіс карыстача"
 

	
 
#: kallithea/controllers/admin/users.py:488
 
msgid "An error occurred during ip saving"
 
msgstr "Адбылася памылка пры захаванні IP"
 

	
 
#: kallithea/controllers/admin/users.py:502
 
msgid "Removed IP address from user whitelist"
 
msgid "Removed ip address from user whitelist"
 
msgstr "Выдалены IP %s з белага спісу карыстача"
 

	
 
#: kallithea/lib/auth.py:745
 
#, python-format
 
msgid "IP %s not allowed"
 
msgstr "IP %s заблакаваны"
 

	
 
#: kallithea/lib/auth.py:806
 
msgid "You need to be a registered user to perform this action"
 
msgstr "Вы павінны быць зарэгістраваным карыстачом, каб выканаць гэта дзеянне"
 

	
 
#: kallithea/lib/auth.py:843
 
msgid "You need to be signed in to view this page"
 
msgstr "Старонка даступная толькі аўтарызаваным карыстачам"
 

	
 
#: kallithea/lib/base.py:427
 
msgid "Repository not found in the filesystem"
 
msgstr "Рэпазітар не знойдзены на файлавай сістэме"
 

	
 
#: kallithea/lib/base.py:453 kallithea/lib/helpers.py:643
 
msgid "Changeset not found"
 
msgstr "Набор змен не знойдзены"
 

	
 
#: kallithea/lib/diffs.py:66
 
msgid "Binary file"
 
msgstr "Двайковы файл"
 

	
 
#: kallithea/lib/diffs.py:82
 
msgid ""
 
"Changeset was too big and was cut off, use diff menu to display this diff"
 
msgstr ""
 
"Набор змены апынуўся занадта вялікімі і быў падрэзаны, выкарыстоўвайце меню "
 
"параўнання для паказу выніку параўнання"
 

	
 
#: kallithea/lib/diffs.py:92
 
msgid "No changes detected"
 
msgstr "Змен не выяўлена"
 

	
 
#: kallithea/lib/helpers.py:627
 
#, python-format
 
msgid "Deleted branch: %s"
 
msgstr "Выдалена галінка: %s"
 

	
 
#: kallithea/lib/helpers.py:630
 
#, python-format
 
msgid "Created tag: %s"
 
msgstr "Створаны тэг: %s"
 

	
 
#: kallithea/lib/helpers.py:693
 
#, python-format
 
msgid "Show all combined changesets %s->%s"
 
msgstr "Паказаць адрозненні разам %s->%s"
 

	
 
#: kallithea/lib/helpers.py:699
 
msgid "compare view"
 
msgstr "параўнанне"
 

	
 
#: kallithea/lib/helpers.py:718
 
msgid "and"
 
msgstr "і"
 

	
 
#: kallithea/lib/helpers.py:719
 
#, python-format
 
msgid "%s more"
 
msgstr "на %s больш"
 

	
 
#: kallithea/lib/helpers.py:720
 
#: kallithea/templates/changelog/changelog.html:44
 
msgid "revisions"
 
msgstr "версіі"
 

	
 
#: kallithea/lib/helpers.py:744
 
#, python-format
 
msgid "fork name %s"
 
msgstr "імя форка %s"
 

	
 
#: kallithea/lib/helpers.py:761
 
#, python-format
 
msgid "Pull request #%s"
 
msgstr "Pull-запыт #%s"
 

	
 
#: kallithea/lib/helpers.py:771
 
msgid "[deleted] repository"
 
msgstr "[выдалены] рэпазітар"
 

	
 
#: kallithea/lib/helpers.py:773 kallithea/lib/helpers.py:785
 
msgid "[created] repository"
 
msgstr "[створаны] рэпазітар"
 

	
 
#: kallithea/lib/helpers.py:775
 
msgid "[created] repository as fork"
 
msgstr "[створаны] рэпазітар як форк"
 

	
 
#: kallithea/lib/helpers.py:777 kallithea/lib/helpers.py:787
 
msgid "[forked] repository"
 
msgstr "[форкнуты] рэпазітар"
 

	
 
#: kallithea/lib/helpers.py:779 kallithea/lib/helpers.py:789
 
msgid "[updated] repository"
 
msgstr "[абноўлены] рэпазітар"
 

	
 
#: kallithea/lib/helpers.py:781
 
msgid "[downloaded] archive from repository"
 
msgstr "[загружаны] архіў з рэпазітара"
 

	
 
#: kallithea/lib/helpers.py:783
 
msgid "[delete] repository"
 
msgstr "[выдалены] рэпазітар"
 

	
 
#: kallithea/lib/helpers.py:791
 
msgid "[created] user"
 
msgstr "[створаны] карыстач"
 

	
 
#: kallithea/lib/helpers.py:793
 
msgid "[updated] user"
 
msgstr "[абноўлены] карыстач"
 

	
 
#: kallithea/lib/helpers.py:795
 
msgid "[created] user group"
 
msgstr "[створана] група карыстачоў"
 

	
 
#: kallithea/lib/helpers.py:797
 
msgid "[updated] user group"
 
msgstr "[абноўлена] група карыстачоў"
 

	
 
#: kallithea/lib/helpers.py:799
 
msgid "[commented] on revision in repository"
 
msgstr "[каментар] да рэвізіі ў рэпазітары"
 

	
 
#: kallithea/lib/helpers.py:801
 
msgid "[commented] on pull request for"
 
msgstr "[пракаменціравана] у запыце на занясенне змен для"
 

	
 
#: kallithea/lib/helpers.py:803
 
msgid "[closed] pull request for"
 
msgstr "[зачынены] Pull-запыт для"
 

	
 
#: kallithea/lib/helpers.py:805
 
msgid "[pushed] into"
 
msgstr "[адпраўлена] у"
 

	
 
#: kallithea/lib/helpers.py:807
 
msgid "[committed via Kallithea] into repository"
 
msgstr "[занесены змены з дапамогай Kallithea] у рэпазітары"
 

	
 
#: kallithea/lib/helpers.py:809
 
msgid "[pulled from remote] into repository"
 
msgstr "[занесены змены з выдаленага рэпазітара] у рэпазітар"
 

	
 
#: kallithea/lib/helpers.py:811
 
msgid "[pulled] from"
 
msgstr "[занесены змены] з"
 

	
 
#: kallithea/lib/helpers.py:813
 
msgid "[started following] repository"
 
msgstr "[дададзены ў назіранні] рэпазітар"
 

	
 
#: kallithea/lib/helpers.py:815
 
msgid "[stopped following] repository"
 
msgstr "[выдалены з назірання] рэпазітар"
 

	
 
#: kallithea/lib/helpers.py:1144
 
#, python-format
 
msgid " and %s more"
 
msgstr " і на %s больш"
 

	
 
#: kallithea/lib/helpers.py:1148
 
msgid "No Files"
 
msgstr "Файлаў няма"
 

	
 
#: kallithea/lib/helpers.py:1214
 
msgid "new file"
 
msgstr "новы файл"
 

	
 
#: kallithea/lib/helpers.py:1217
 
msgid "mod"
 
msgstr "зменены"
 

	
 
#: kallithea/lib/helpers.py:1220
 
msgid "del"
 
msgstr "выдалены"
 

	
 
#: kallithea/lib/helpers.py:1223
 
msgid "rename"
 
msgstr "пераназваны"
 

	
 
#: kallithea/lib/helpers.py:1228
 
msgid "chmod"
 
msgstr "chmod"
 

	
 
#: kallithea/lib/helpers.py:1460
 
#, python-format
 
@@ -1852,385 +1852,385 @@ msgid ""
 
"owners or remove those repository groups: %s"
 
msgstr ""
 
"Карыстач \"%s\" усё яшчэ з'яўляецца ўладальнікам %s груп рэпазітароў і таму "
 
"не можа быць выдалены. Зменіце ўладальніка ці выдаліце дадзеныя групы: %s"
 

	
 
#: kallithea/model/user.py:275
 
#, python-format
 
msgid ""
 
"User \"%s\" still owns %s user groups and cannot be removed. Switch owners "
 
"or remove those user groups: %s"
 
msgstr ""
 
"Карыстач \"%s\" усё яшчэ з'яўляецца ўладальнікам %s груп карыстачоў і таму "
 
"не можа быць выдалены. Зменіце ўладальніка ці выдаліце дадзеныя групы: %s"
 

	
 
#: kallithea/model/user.py:305
 
msgid "Password reset link"
 
msgstr "Спасылка скіду пароля"
 

	
 
#: kallithea/model/user.py:328
 
msgid "Your new password"
 
msgstr "Ваш новы пароль"
 

	
 
#: kallithea/model/user.py:329
 
#, python-format
 
msgid "Your new Kallithea password:%s"
 
msgstr "Ваш новы пароль ад Kallithea: %s"
 

	
 
#: kallithea/model/validators.py:83 kallithea/model/validators.py:84
 
msgid "Value cannot be an empty list"
 
msgstr "Значэнне не можа быць пустым спісам"
 

	
 
#: kallithea/model/validators.py:101
 
#, python-format
 
msgid "Username \"%(username)s\" already exists"
 
msgstr "Карыстач з імем \"%(username)s\" ужо існуе"
 

	
 
#: kallithea/model/validators.py:103
 
#, python-format
 
msgid "Username \"%(username)s\" is forbidden"
 
msgstr "Імя \"%(username)s\" адхілена"
 

	
 
#: kallithea/model/validators.py:105
 
msgid ""
 
"Username may only contain alphanumeric characters underscores, periods or "
 
"dashes and must begin with alphanumeric character or underscore"
 
msgstr ""
 
"Імя карыстача можа ўтрымоўваць толькі літары, лічбы, знакі падкрэслення, "
 
"кропкі і працяжнік; а гэтак жа павінна пачынацца з літары, лічбы або са "
 
"знака падкрэслення"
 

	
 
#: kallithea/model/validators.py:132
 
msgid "The input is not valid"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:139
 
#, python-format
 
msgid "Username %(username)s is not valid"
 
msgstr "Імя \"%(username)s\" недапушчальна"
 

	
 
#: kallithea/model/validators.py:158
 
msgid "Invalid user group name"
 
msgstr "Няслушнае імя групы карыстачоў"
 

	
 
#: kallithea/model/validators.py:159
 
#, python-format
 
msgid "User group \"%(usergroup)s\" already exists"
 
msgstr "Група карыстачоў \"%(usergroup)s\" ужо існуе"
 

	
 
#: kallithea/model/validators.py:161
 
msgid ""
 
"user group name may only contain alphanumeric characters underscores, "
 
"periods or dashes and must begin with alphanumeric character"
 
msgstr ""
 
"імя групы карыстачоў можа ўтрымоўваць толькі літары, лічбы, знакі "
 
"падкрэслення, кропкі і працяжнік; а гэтак жа павінна пачынацца з літары ці "
 
"лічбы"
 

	
 
#: kallithea/model/validators.py:199
 
msgid "Cannot assign this group as parent"
 
msgstr "Немагчыма выкарыстоўваць гэту групу як бацькоўскую"
 

	
 
#: kallithea/model/validators.py:200
 
#, python-format
 
msgid "Group \"%(group_name)s\" already exists"
 
msgstr "Група \"%(group_name)s\" ужо існуе"
 

	
 
#: kallithea/model/validators.py:202
 
#, python-format
 
msgid "Repository with name \"%(group_name)s\" already exists"
 
msgstr "Рэпазітар з  імем \"%(group_name)s\" ужо існуе"
 

	
 
#: kallithea/model/validators.py:260
 
msgid "Invalid characters (non-ascii) in password"
 
msgstr "Недапушчальныя знакі (не ascii) у паролі"
 

	
 
#: kallithea/model/validators.py:275
 
msgid "Invalid old password"
 
msgstr "Няслушна зададзены стары пароль"
 

	
 
#: kallithea/model/validators.py:291
 
msgid "Passwords do not match"
 
msgstr "Паролі не супадаюць"
 

	
 
#: kallithea/model/validators.py:308
 
msgid "invalid password"
 
msgstr "няслушны пароль"
 

	
 
#: kallithea/model/validators.py:309
 
msgid "invalid user name"
 
msgstr "няслушнае імя карыстача"
 

	
 
#: kallithea/model/validators.py:310
 
msgid "Your account is disabled"
 
msgstr "Ваш акаўнт выключаны"
 

	
 
#: kallithea/model/validators.py:354
 
#, python-format
 
msgid "Repository name %(repo)s is disallowed"
 
msgstr "Імя рэпазітара %(repo)s забаронена"
 

	
 
#: kallithea/model/validators.py:356
 
#, python-format
 
msgid "Repository named %(repo)s already exists"
 
msgstr "Рэпазітар %(repo)s ужо існуе"
 

	
 
#: kallithea/model/validators.py:357
 
#, python-format
 
msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 
msgstr "Рэпазітар \"%(repo)s\" ужо існуе ў групе \"%(group)s\""
 

	
 
#: kallithea/model/validators.py:359
 
#, python-format
 
msgid "Repository group with name \"%(repo)s\" already exists"
 
msgstr "Група рэпазітароў \"%(repo)s\" ужо існуе"
 

	
 
#: kallithea/model/validators.py:474
 
msgid "invalid clone URL"
 
msgstr "няслушны URL для кланавання"
 

	
 
#: kallithea/model/validators.py:475
 
msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
 
msgstr ""
 
"Няслушны URL кланаванні, падайце карэктны URL для кланавання ў фармаце "
 
"http(s)/svn+http(s)/ssh"
 

	
 
#: kallithea/model/validators.py:500
 
msgid "Fork has to be the same type as parent"
 
msgstr "Тып форка будзе супадаць з бацькоўскім"
 

	
 
#: kallithea/model/validators.py:515
 
msgid "You don't have permissions to create repository in this group"
 
msgstr "У вас недастаткова мае рацыю для стварэння рэпазітароў у гэтай групе"
 

	
 
#: kallithea/model/validators.py:517
 
msgid "no permission to create repository in root location"
 
msgstr "недастаткова мае рацыю для стварэння рэпазітара ў каранёвым каталогу"
 

	
 
#: kallithea/model/validators.py:566
 
msgid "You don't have permissions to create a group in this location"
 
msgstr "У Вас недастаткова прывілеяў для стварэння групы ў гэтым месцы"
 

	
 
#: kallithea/model/validators.py:607
 
msgid "This username or user group name is not valid"
 
msgstr "Дадзенае імя карыстача ці групы карыстачоў недапушчальна"
 

	
 
#: kallithea/model/validators.py:700
 
msgid "This is not a valid path"
 
msgstr "Гэты шлях хібны"
 

	
 
#: kallithea/model/validators.py:715
 
msgid "This e-mail address is already taken"
 
msgstr "Гэты E-mail ужо заняты"
 

	
 
#: kallithea/model/validators.py:735
 
#, python-format
 
msgid "e-mail \"%(email)s\" does not exist."
 
msgstr "\"%(email)s\" не існуе."
 

	
 
#: kallithea/model/validators.py:772
 
msgid ""
 
"The LDAP Login attribute of the CN must be specified - this is the name of "
 
"the attribute that is equivalent to \"username\""
 
msgstr ""
 
"Для ўваходу па LDAP павінна быць паказана значэнне атрыбута CN - гэта "
 
"эквівалент імя карыстача"
 

	
 
#: kallithea/model/validators.py:785
 
#, python-format
 
msgid "Revisions %(revs)s are already part of pull request or have set status"
 
msgstr "Рэвізіі %(revs)s ужо ўключаны ў pull-request ці маюць усталяваны статус"
 

	
 
#: kallithea/model/validators.py:817
 
msgid "Please enter a valid IPv4 or IPv6 address"
 
msgid "Please enter a valid IPv4 or IpV6 address"
 
msgstr "Калі ласка, увядзіце існы IPv4 ці IPv6 адрас"
 

	
 
#: kallithea/model/validators.py:818
 
#, python-format
 
msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
 
msgstr ""
 
"Значэнне маскі падсеткі павінна быць у межах ад 0 да 32 (%(bits)r - няслушна)"
 

	
 
#: kallithea/model/validators.py:851
 
msgid "Key name can only consist of letters, underscore, dash or numbers"
 
msgstr ""
 
"Ключавое імя можа толькі складацца з літар, знака падкрэслення, працяжнік ці "
 
"лікаў"
 

	
 
#: kallithea/model/validators.py:865
 
msgid "Filename cannot be inside a directory"
 
msgstr "Файла няма ў каталогу"
 

	
 
#: kallithea/model/validators.py:881
 
#, python-format
 
msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 
msgstr ""
 

	
 
#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
 
msgid "About"
 
msgstr "Пра праграму"
 

	
 
#: kallithea/templates/index.html:5
 
msgid "Dashboard"
 
msgstr "Панэль кіравання"
 

	
 
#: kallithea/templates/index_base.html:6
 
#: kallithea/templates/admin/my_account/my_account_repos.html:3
 
#: kallithea/templates/admin/my_account/my_account_watched.html:3
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 
#: kallithea/templates/admin/repos/repos.html:9
 
#: kallithea/templates/admin/user_groups/user_groups.html:9
 
#: kallithea/templates/admin/users/users.html:9
 
#: kallithea/templates/bookmarks/bookmarks.html:9
 
#: kallithea/templates/branches/branches.html:9
 
#: kallithea/templates/journal/journal.html:9
 
#: kallithea/templates/journal/journal.html:48
 
#: kallithea/templates/journal/journal.html:49
 
#: kallithea/templates/tags/tags.html:9
 
msgid "quick filter..."
 
msgstr "фільтр..."
 

	
 
#: kallithea/templates/index_base.html:6
 
msgid "repositories"
 
msgstr "рэпазітары"
 

	
 
#: kallithea/templates/index_base.html:20
 
#: kallithea/templates/index_base.html:25
 
#: kallithea/templates/admin/repos/repo_add.html:5
 
#: kallithea/templates/admin/repos/repo_add.html:19
 
#: kallithea/templates/admin/repos/repos.html:22
 
msgid "Add Repository"
 
msgstr "Дадаць рэпазітар"
 

	
 
#: kallithea/templates/index_base.html:22
 
#: kallithea/templates/index_base.html:27
 
#: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 
#: kallithea/templates/admin/repo_groups/repo_group_add.html:13
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:26
 
msgid "Add Repository Group"
 
msgstr "Дадаць групу рэпазітароў"
 

	
 
#: kallithea/templates/index_base.html:32
 
msgid "You have admin right to this group, and can edit it"
 
msgstr "Вы маеце адміністратарскія правы на гэту групу і можаце рэдагаваць яе"
 

	
 
#: kallithea/templates/index_base.html:32
 
msgid "Edit Repository Group"
 
msgstr "Змяніць групу рэпазітароў"
 

	
 
#: kallithea/templates/index_base.html:45
 
msgid "Group Name"
 
msgstr "Імя групы"
 

	
 
#: kallithea/templates/index_base.html:46
 
#: kallithea/templates/index_base.html:131
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
 
#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:47
 
#: kallithea/templates/admin/repos/repo_add_base.html:32
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:72
 
#: kallithea/templates/admin/repos/repos.html:48
 
#: kallithea/templates/admin/user_groups/user_group_add.html:40
 
#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
 
#: kallithea/templates/admin/user_groups/user_groups.html:47
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:64
 
#: kallithea/templates/email_templates/changeset_comment.html:18
 
#: kallithea/templates/email_templates/pull_request.html:12
 
#: kallithea/templates/forks/fork.html:38
 
#: kallithea/templates/pullrequests/pullrequest.html:40
 
#: kallithea/templates/pullrequests/pullrequest_show.html:38
 
#: kallithea/templates/pullrequests/pullrequest_show.html:63
 
#: kallithea/templates/summary/summary.html:84
 
msgid "Description"
 
msgstr "Апісанне"
 

	
 
#: kallithea/templates/index_base.html:129
 
#: kallithea/templates/admin/my_account/my_account_repos.html:46
 
#: kallithea/templates/admin/my_account/my_account_watched.html:46
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:46
 
#: kallithea/templates/admin/repos/repo_add_base.html:9
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 
#: kallithea/templates/admin/repos/repos.html:47
 
#: kallithea/templates/admin/user_groups/user_groups.html:46
 
#: kallithea/templates/base/perms_summary.html:53
 
#: kallithea/templates/bookmarks/bookmarks.html:49
 
#: kallithea/templates/bookmarks/bookmarks_data.html:7
 
#: kallithea/templates/branches/branches.html:49
 
#: kallithea/templates/branches/branches_data.html:7
 
#: kallithea/templates/files/files_browser.html:60
 
#: kallithea/templates/journal/journal.html:187
 
#: kallithea/templates/journal/journal.html:278
 
#: kallithea/templates/tags/tags.html:49
 
#: kallithea/templates/tags/tags_data.html:7
 
msgid "Name"
 
msgstr "Імя"
 

	
 
#: kallithea/templates/index_base.html:132
 
msgid "Last Change"
 
msgstr "Апошняя змена"
 

	
 
#: kallithea/templates/index_base.html:134
 
#: kallithea/templates/admin/my_account/my_account_repos.html:48
 
#: kallithea/templates/admin/my_account/my_account_watched.html:48
 
#: kallithea/templates/admin/repos/repos.html:49
 
#: kallithea/templates/journal/journal.html:189
 
#: kallithea/templates/journal/journal.html:280
 
msgid "Tip"
 
msgstr "Стан"
 

	
 
#: kallithea/templates/index_base.html:136
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:49
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:60
 
#: kallithea/templates/admin/repos/repos.html:50
 
#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
 
#: kallithea/templates/admin/user_groups/user_groups.html:50
 
#: kallithea/templates/summary/summary.html:137
 
msgid "Owner"
 
msgstr "Уладальнік"
 

	
 
#: kallithea/templates/index_base.html:144
 
#: kallithea/templates/admin/my_account/my_account_repos.html:57
 
#: kallithea/templates/admin/my_account/my_account_watched.html:57
 
#: kallithea/templates/base/root.html:44
 
#: kallithea/templates/bookmarks/bookmarks.html:79
 
#: kallithea/templates/branches/branches.html:79
 
#: kallithea/templates/journal/journal.html:198
 
#: kallithea/templates/journal/journal.html:289
 
#: kallithea/templates/tags/tags.html:79
 
msgid "Click to sort ascending"
 
msgstr "Па ўзрастанні"
 

	
 
#: kallithea/templates/index_base.html:145
 
#: kallithea/templates/admin/my_account/my_account_repos.html:58
 
#: kallithea/templates/admin/my_account/my_account_watched.html:58
 
#: kallithea/templates/base/root.html:45
 
#: kallithea/templates/bookmarks/bookmarks.html:80
 
#: kallithea/templates/branches/branches.html:80
 
#: kallithea/templates/journal/journal.html:199
 
#: kallithea/templates/journal/journal.html:290
 
#: kallithea/templates/tags/tags.html:80
 
msgid "Click to sort descending"
 
msgstr "Па змяншэнні"
 

	
 
#: kallithea/templates/index_base.html:146
 
msgid "No repositories found."
 
msgstr "Рэпазітары не знойдзены."
 

	
 
#: kallithea/templates/index_base.html:147
 
#: kallithea/templates/admin/my_account/my_account_repos.html:60
 
#: kallithea/templates/admin/my_account/my_account_watched.html:60
 
#: kallithea/templates/base/root.html:47
 
#: kallithea/templates/bookmarks/bookmarks.html:82
 
#: kallithea/templates/branches/branches.html:82
 
#: kallithea/templates/journal/journal.html:201
 
#: kallithea/templates/journal/journal.html:292
 
#: kallithea/templates/tags/tags.html:82
 
msgid "Data error."
 
msgstr "Памылка дадзеных."
 

	
 
#: kallithea/templates/index_base.html:148
 
#: kallithea/templates/admin/my_account/my_account_repos.html:61
 
#: kallithea/templates/admin/my_account/my_account_watched.html:61
 
#: kallithea/templates/base/base.html:143
 
#: kallithea/templates/base/root.html:48
kallithea/i18n/kallithea.pot
Show inline comments
 
# Translations template for Kallithea.
 
# Copyright (C) 2015 Various authors, licensing as GPLv3
 
# This file is distributed under the same license as the Kallithea project.
 
# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
 
##, fuzzy
 
msgid ""
 
msgstr ""
 
"Project-Id-Version: Kallithea 0.1\n"
 
"Project-Id-Version: Kallithea 0.2.2\n"
 
"Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
 
"POT-Creation-Date: 2015-04-01 03:17+0200\n"
 
"POT-Creation-Date: 2015-07-12 18:32+0200\n"
 
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 
"Language-Team: LANGUAGE <LL@li.org>\n"
 
"MIME-Version: 1.0\n"
 
"Content-Type: text/plain; charset=UTF-8\n"
 
"Content-Transfer-Encoding: 8bit\n"
 

	
 
#: kallithea/controllers/changelog.py:86
 
#: kallithea/controllers/pullrequests.py:247 kallithea/lib/base.py:449
 
msgid "There are no changesets yet"
 
msgstr ""
 

	
 
#: kallithea/controllers/changelog.py:157
 
#: kallithea/controllers/admin/permissions.py:62
 
#: kallithea/controllers/admin/permissions.py:66
 
#: kallithea/controllers/admin/permissions.py:70
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
 
msgid "None"
 
msgstr ""
 

	
 
#: kallithea/controllers/changelog.py:160 kallithea/controllers/files.py:197
 
msgid "(closed)"
 
msgstr ""
 

	
 
#: kallithea/controllers/changeset.py:89
 
msgid "Show whitespace"
 
msgstr ""
 

	
 
#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
 
#: kallithea/templates/files/diff_2way.html:55
 
msgid "Ignore whitespace"
 
msgstr ""
 

	
 
#: kallithea/controllers/changeset.py:169
 
#, python-format
 
msgid "increase diff context to %(num)s lines"
 
msgstr ""
 

	
 
#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:97
 
#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:746
 
#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:745
 
msgid "Such revision does not exist for this repository"
 
msgstr ""
 

	
 
#: kallithea/controllers/changeset.py:352
 
#: kallithea/controllers/pullrequests.py:699
 
msgid "No comments."
 
msgstr ""
 

	
 
#: kallithea/controllers/changeset.py:382
 
msgid "Changing status on a changeset associated with a closed pull request is not allowed"
 
msgstr ""
 

	
 
#: kallithea/controllers/compare.py:158 kallithea/templates/base/root.html:42
 
msgid "Select changeset"
 
msgstr ""
 

	
 
#: kallithea/controllers/compare.py:255
 
msgid "Cannot compare repositories without using common ancestor"
 
msgstr ""
 

	
 
#: kallithea/controllers/error.py:96
 
msgid "The request could not be understood by the server due to malformed syntax."
 
msgstr ""
 

	
 
#: kallithea/controllers/error.py:99
 
msgid "Unauthorized access to resource"
 
msgstr ""
 

	
 
#: kallithea/controllers/error.py:101
 
msgid "You don't have permission to view this page"
 
msgstr ""
 

	
 
#: kallithea/controllers/error.py:103
 
msgid "The resource could not be found"
 
msgstr ""
 

	
 
#: kallithea/controllers/error.py:105
 
msgid "The server encountered an unexpected condition which prevented it from fulfilling the request."
 
msgstr ""
 

	
 
#: kallithea/controllers/feed.py:55
 
#, python-format
 
msgid "Changes on %s repository"
 
msgstr ""
 

	
 
#: kallithea/controllers/feed.py:56
 
#, python-format
 
msgid "%s %s feed"
 
msgstr ""
 

	
 
#: kallithea/controllers/feed.py:89
 
#: kallithea/templates/changeset/changeset.html:153
 
#: kallithea/templates/changeset/changeset.html:166
 
#: kallithea/templates/compare/compare_diff.html:78
 
#: kallithea/templates/compare/compare_diff.html:89
 
#: kallithea/templates/pullrequests/pullrequest_show.html:328
 
#: kallithea/templates/pullrequests/pullrequest_show.html:351
 
msgid "Changeset was too big and was cut off..."
 
msgstr ""
 

	
 
#: kallithea/controllers/feed.py:93
 
#, python-format
 
msgid "%s committed on %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:92
 
msgid "Click here to add new file"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:93
 
#, python-format
 
msgid "There are no files yet. %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:194
 
#, python-format
 
msgid "%s at %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:306 kallithea/controllers/files.py:366
 
#: kallithea/controllers/files.py:433
 
#, python-format
 
msgid "This repository has been locked by %s on %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:318
 
msgid "You can only delete files with revision being a valid branch "
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:329
 
#, python-format
 
msgid "Deleted file %s via Kallithea"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:351
 
#, python-format
 
msgid "Successfully deleted file %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:355 kallithea/controllers/files.py:421
 
#: kallithea/controllers/files.py:502
 
msgid "Error occurred during commit"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:378
 
msgid "You can only edit files with revision being a valid branch "
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:392
 
#, python-format
 
msgid "Edited file %s via Kallithea"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:408
 
msgid "No changes"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:417 kallithea/controllers/files.py:491
 
#, python-format
 
msgid "Successfully committed to %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:444
 
msgid "Added file via Kallithea"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:465
 
msgid "No content"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:469
 
msgid "No filename"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:494
 
msgid "Location must be relative path and must not contain .. in path"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:528
 
msgid "Downloads disabled"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:539
 
#, python-format
 
msgid "Unknown revision %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:541
 
msgid "Empty repository"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:543
 
msgid "Unknown archive type"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:775
 
#: kallithea/controllers/files.py:774
 
#: kallithea/templates/changeset/changeset_range.html:9
 
#: kallithea/templates/email_templates/pull_request.html:15
 
#: kallithea/templates/pullrequests/pullrequest.html:116
 
msgid "Changesets"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:776 kallithea/controllers/pullrequests.py:182
 
#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:182
 
#: kallithea/controllers/summary.py:74 kallithea/model/scm.py:816
 
#: kallithea/templates/switch_to_list.html:3
 
#: kallithea/templates/branches/branches.html:10
 
msgid "Branches"
 
msgstr ""
 

	
 
#: kallithea/controllers/files.py:777 kallithea/controllers/pullrequests.py:183
 
#: kallithea/controllers/files.py:776 kallithea/controllers/pullrequests.py:183
 
#: kallithea/controllers/summary.py:75 kallithea/model/scm.py:827
 
#: kallithea/templates/switch_to_list.html:25
 
#: kallithea/templates/tags/tags.html:10
 
msgid "Tags"
 
msgstr ""
 

	
 
#: kallithea/controllers/forks.py:187
 
#, python-format
 
msgid "An error occurred during repository forking %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/home.py:84
 
msgid "Groups"
 
msgstr ""
 

	
 
#: kallithea/controllers/home.py:89
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
 
#: kallithea/templates/admin/repos/repo_add.html:12
 
#: kallithea/templates/admin/repos/repo_add.html:16
 
#: kallithea/templates/admin/repos/repos.html:9
 
#: kallithea/templates/admin/users/user_edit_advanced.html:6
 
#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
 
#: kallithea/templates/base/base.html:127
 
#: kallithea/templates/base/base.html:390
 
#: kallithea/templates/base/base.html:562
 
#: kallithea/templates/base/base.html:131
 
#: kallithea/templates/base/base.html:394
 
#: kallithea/templates/base/base.html:566
 
msgid "Repositories"
 
msgstr ""
 

	
 
#: kallithea/controllers/home.py:130
 
#: kallithea/templates/files/files_add.html:32
 
#: kallithea/templates/files/files_delete.html:23
 
#: kallithea/templates/files/files_edit.html:32
 
msgid "Branch"
 
msgstr ""
 

	
 
#: kallithea/controllers/home.py:136
 
msgid "Tag"
 
msgstr ""
 

	
 
#: kallithea/controllers/home.py:142
 
msgid "Bookmark"
 
msgstr ""
 

	
 
#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
 
msgid "public journal"
 
msgstr ""
 

	
 
#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
 
msgid "journal"
 
msgstr ""
 

	
 
#: kallithea/controllers/login.py:188 kallithea/controllers/login.py:234
 
msgid "bad captcha"
 
msgstr ""
 

	
 
#: kallithea/controllers/login.py:194
 
msgid "You have successfully registered into Kallithea"
 
msgstr ""
 

	
 
#: kallithea/controllers/login.py:239
 
msgid "Your password reset link was sent"
 
msgstr ""
 

	
 
#: kallithea/controllers/login.py:260
 
msgid "Your password reset was successful, new password has been sent to your email"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:130
 
#, python-format
 
msgid "%s (closed)"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:158
 
#: kallithea/templates/changeset/changeset.html:12
 
#: kallithea/templates/email_templates/changeset_comment.html:17
 
msgid "Changeset"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:179
 
msgid "Special"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:180
 
msgid "Peer branches"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:181 kallithea/model/scm.py:822
 
#: kallithea/templates/switch_to_list.html:38
 
#: kallithea/templates/bookmarks/bookmarks.html:10
 
msgid "Bookmarks"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:312
 
#, python-format
 
msgid "Error creating pull request: %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:356
 
#: kallithea/controllers/pullrequests.py:497
 
msgid "No description"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:363
 
msgid "Successfully opened new pull request"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:366
 
#: kallithea/controllers/pullrequests.py:450
 
msgid "Error occurred while creating pull request"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:398
 
msgid "Missing changesets since the previous pull request:"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:405
 
#, python-format
 
msgid "New changesets on %s %s since the previous pull request:"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:412
 
msgid "Ancestor didn't change - show diff since previous version:"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:419
 
#, python-format
 
msgid "This pull request is based on another %s revision and there is no simple diff."
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:421
 
#, python-format
 
msgid "No changes found on %s %s since previous version."
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:456
 
#, python-format
 
msgid "Closed, replaced by %s ."
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:464
 
msgid "Pull request update created"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:503
 
msgid "Pull request updated"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:518
 
msgid "Successfully deleted pull request"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:577
 
#, python-format
 
msgid "This pull request has already been merged to %s."
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:579
 
msgid "This pull request has been closed and can not be updated."
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:597
 
#, python-format
 
msgid "This pull request can be updated with changes on %s:"
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:600
 
msgid "No changesets found for updating this pull request."
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:608
 
#, python-format
 
msgid "Note: Branch %s has another head: %s."
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:614
 
msgid "Git pull requests don't support updates yet."
 
msgstr ""
 

	
 
#: kallithea/controllers/pullrequests.py:701
 
msgid "Closing."
 
msgstr ""
 

	
 
#: kallithea/controllers/search.py:135
 
msgid "Invalid search query. Try quoting it."
 
msgstr ""
 

	
 
#: kallithea/controllers/search.py:140
 
msgid "There is no index to search in. Please run whoosh indexer"
 
msgstr ""
 

	
 
#: kallithea/controllers/search.py:144
 
msgid "An error occurred during search operation."
 
msgstr ""
 

	
 
#: kallithea/controllers/summary.py:199
 
#: kallithea/templates/summary/summary.html:387
 
#: kallithea/templates/summary/summary.html:388
 
msgid "No data ready yet"
 
msgstr ""
 

	
 
#: kallithea/controllers/summary.py:202
 
#: kallithea/templates/summary/summary.html:101
 
#: kallithea/templates/summary/summary.html:102
 
msgid "Statistics are disabled for this repository"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/auth_settings.py:125
 
msgid "Auth settings updated successfully"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/auth_settings.py:136
 
msgid "error occurred during update of auth settings"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/defaults.py:97
 
msgid "Default settings updated successfully"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/defaults.py:112
 
msgid "Error occurred during update of defaults"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/gists.py:59
 
#: kallithea/controllers/admin/my_account.py:238
 
#: kallithea/controllers/admin/users.py:288
 
msgid "forever"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/gists.py:60
 
#: kallithea/controllers/admin/my_account.py:239
 
#: kallithea/controllers/admin/users.py:289
 
msgid "5 minutes"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/gists.py:61
 
#: kallithea/controllers/admin/my_account.py:240
 
#: kallithea/controllers/admin/users.py:290
 
msgid "1 hour"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/gists.py:62
 
#: kallithea/controllers/admin/my_account.py:241
 
#: kallithea/controllers/admin/users.py:291
 
msgid "1 day"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/gists.py:63
 
#: kallithea/controllers/admin/my_account.py:242
 
#: kallithea/controllers/admin/users.py:292
 
msgid "1 month"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/gists.py:67
 
#: kallithea/controllers/admin/my_account.py:244
 
#: kallithea/controllers/admin/users.py:294
 
msgid "Lifetime"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/gists.py:146
 
msgid "Error occurred during gist creation"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/gists.py:184
 
#, python-format
 
msgid "Deleted gist %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/gists.py:233
 
msgid "unmodified"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/gists.py:262
 
msgid "Successfully updated gist content"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/gists.py:267
 
msgid "Successfully updated gist data"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/gists.py:270
 
#, python-format
 
msgid "Error occurred during update of gist %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/my_account.py:70
 
msgid "You can't edit this user since it's crucial for entire application"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/my_account.py:128
 
msgid "Your account was updated successfully"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/my_account.py:143
 
#: kallithea/controllers/admin/users.py:206
 
#, python-format
 
msgid "Error occurred during update of user %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/my_account.py:162
 
msgid "Successfully updated password"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/my_account.py:173
 
msgid "Error occurred during update of user password"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/my_account.py:215
 
#: kallithea/controllers/admin/users.py:431
 
#, python-format
 
msgid "Added email %s to user"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/my_account.py:221
 
#: kallithea/controllers/admin/users.py:437
 
msgid "An error occurred during email saving"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/my_account.py:230
 
#: kallithea/controllers/admin/users.py:448
 
msgid "Removed email from user"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/my_account.py:254
 
#: kallithea/controllers/admin/users.py:314
 
msgid "Api key successfully created"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/my_account.py:266
 
#: kallithea/controllers/admin/users.py:330
 
msgid "Api key successfully reset"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/my_account.py:270
 
#: kallithea/controllers/admin/users.py:334
 
msgid "Api key successfully deleted"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/permissions.py:63
 
#: kallithea/controllers/admin/permissions.py:67
 
#: kallithea/controllers/admin/permissions.py:71
 
msgid "Read"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/permissions.py:64
 
#: kallithea/controllers/admin/permissions.py:68
 
#: kallithea/controllers/admin/permissions.py:72
 
msgid "Write"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/permissions.py:65
 
#: kallithea/controllers/admin/permissions.py:69
 
#: kallithea/controllers/admin/permissions.py:73
 
#: kallithea/templates/admin/auth/auth_settings.html:9
 
#: kallithea/templates/admin/defaults/defaults.html:9
 
#: kallithea/templates/admin/permissions/permissions.html:9
 
#: kallithea/templates/admin/repo_groups/repo_group_add.html:9
 
#: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:10
 
#: kallithea/templates/admin/repos/repo_add.html:10
 
#: kallithea/templates/admin/repos/repo_add.html:14
 
#: kallithea/templates/admin/repos/repos.html:9
 
#: kallithea/templates/admin/settings/settings.html:9
 
#: kallithea/templates/admin/user_groups/user_group_add.html:8
 
#: kallithea/templates/admin/user_groups/user_group_edit.html:9
 
#: kallithea/templates/admin/user_groups/user_groups.html:10
 
#: kallithea/templates/admin/users/user_add.html:8
 
#: kallithea/templates/admin/users/user_edit.html:9
 
#: kallithea/templates/admin/users/user_edit_profile.html:114
 
#: kallithea/templates/admin/users/users.html:10
 
#: kallithea/templates/admin/users/users.html:55
 
#: kallithea/templates/base/base.html:255
 
#: kallithea/templates/base/base.html:256
 
#: kallithea/templates/base/base.html:262
 
#: kallithea/templates/base/base.html:263
 
#: kallithea/templates/base/base.html:259
 
#: kallithea/templates/base/base.html:260
 
#: kallithea/templates/base/base.html:266
 
#: kallithea/templates/base/base.html:267
 
msgid "Admin"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/permissions.py:76
 
#: kallithea/controllers/admin/permissions.py:87
 
#: kallithea/controllers/admin/permissions.py:92
 
#: kallithea/controllers/admin/permissions.py:95
 
#: kallithea/controllers/admin/permissions.py:98
 
#: kallithea/controllers/admin/permissions.py:101
 
msgid "Disabled"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/permissions.py:78
 
msgid "Allowed with manual account activation"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/permissions.py:80
 
msgid "Allowed with automatic account activation"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/permissions.py:83
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1684
 
msgid "Manual activation of external account"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/permissions.py:84
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1685
 
msgid "Automatic activation of external account"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/permissions.py:88
 
#: kallithea/controllers/admin/permissions.py:91
 
#: kallithea/controllers/admin/permissions.py:96
 
#: kallithea/controllers/admin/permissions.py:99
 
#: kallithea/controllers/admin/permissions.py:102
 
msgid "Enabled"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/permissions.py:125
 
msgid "Global permissions updated successfully"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/permissions.py:140
 
msgid "Error occurred during update of permissions"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repo_groups.py:184
 
#, python-format
 
msgid "Created repository group %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repo_groups.py:197
 
#, python-format
 
msgid "Error occurred during creation of repository group %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repo_groups.py:255
 
#, python-format
 
msgid "Updated repository group %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repo_groups.py:271
 
#, python-format
 
msgid "Error occurred during update of repository group %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repo_groups.py:289
 
#, python-format
 
msgid "This group contains %s repositories and cannot be deleted"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repo_groups.py:296
 
#, python-format
 
msgid "This group contains %s subgroups and cannot be deleted"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repo_groups.py:302
 
#, python-format
 
msgid "Removed repository group %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repo_groups.py:307
 
#, python-format
 
msgid "Error occurred during deletion of repository group %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repo_groups.py:420
 
#: kallithea/controllers/admin/repo_groups.py:455
 
#: kallithea/controllers/admin/user_groups.py:340
 
msgid "Cannot revoke permission for yourself as admin"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repo_groups.py:435
 
msgid "Repository Group permissions updated"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repo_groups.py:472
 
#: kallithea/controllers/admin/repos.py:430
 
#: kallithea/controllers/admin/repos.py:429
 
#: kallithea/controllers/admin/user_groups.py:352
 
msgid "An error occurred during revoking of permission"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:163
 
#: kallithea/controllers/admin/repos.py:162
 
#, python-format
 
msgid "Error creating repository %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:238
 
#: kallithea/controllers/admin/repos.py:237
 
#, python-format
 
msgid "Created repository %s from %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:247
 
#: kallithea/controllers/admin/repos.py:246
 
#, python-format
 
msgid "Forked repository %s as %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:250
 
#: kallithea/controllers/admin/repos.py:249
 
#, python-format
 
msgid "Created repository %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:290
 
#: kallithea/controllers/admin/repos.py:289
 
#, python-format
 
msgid "Repository %s updated successfully"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:309
 
#: kallithea/controllers/admin/repos.py:308
 
#, python-format
 
msgid "Error occurred during update of repository %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:336
 
#: kallithea/controllers/admin/repos.py:335
 
#, python-format
 
msgid "Detached %s forks"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:339
 
#: kallithea/controllers/admin/repos.py:338
 
#, python-format
 
msgid "Deleted %s forks"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:344
 
#: kallithea/controllers/admin/repos.py:343
 
#, python-format
 
msgid "Deleted repository %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:347
 
#: kallithea/controllers/admin/repos.py:346
 
#, python-format
 
msgid "Cannot delete %s it still contains attached forks"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:352
 
#: kallithea/controllers/admin/repos.py:351
 
#, python-format
 
msgid "An error occurred during deletion of %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:406
 
#: kallithea/controllers/admin/repos.py:405
 
msgid "Repository permissions updated"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:462
 
#: kallithea/controllers/admin/repos.py:461
 
msgid "An error occurred during creation of field"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:476
 
#: kallithea/controllers/admin/repos.py:475
 
msgid "An error occurred during removal of field"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:492
 
#: kallithea/controllers/admin/repos.py:491
 
msgid "-- Not a fork --"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:522
 
msgid "Updated repository visibility in public journal"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:526
 
msgid "Updated repository visibility in public journal"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:530
 
msgid "An error occurred during setting this repository in public journal"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:535 kallithea/model/validators.py:340
 
msgid "Token mismatch"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:550
 
#: kallithea/controllers/admin/repos.py:543
 
msgid "Nothing"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:545
 
#, python-format
 
msgid "Marked repo %s as fork of %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:552
 
#, python-format
 
msgid "Marked repo %s as fork of %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:559
 
msgid "An error occurred during this operation"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:575
 
#: kallithea/controllers/admin/repos.py:568
 
msgid "Locked repository"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:578
 
#: kallithea/controllers/admin/repos.py:571
 
msgid "Unlocked repository"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:581
 
#: kallithea/controllers/admin/repos.py:608
 
#: kallithea/controllers/admin/repos.py:574
 
#: kallithea/controllers/admin/repos.py:601
 
msgid "An error occurred during unlocking"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:599
 
#: kallithea/controllers/admin/repos.py:592
 
msgid "Unlocked"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:602
 
#: kallithea/controllers/admin/repos.py:595
 
msgid "Locked"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:604
 
#: kallithea/controllers/admin/repos.py:597
 
#, python-format
 
msgid "Repository has been %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:622
 
#: kallithea/controllers/admin/repos.py:615
 
msgid "Cache invalidation successful"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:626
 
#: kallithea/controllers/admin/repos.py:619
 
msgid "An error occurred during cache invalidation"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:641
 
#: kallithea/controllers/admin/repos.py:634
 
msgid "Pulled from remote location"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:644
 
#: kallithea/controllers/admin/repos.py:637
 
msgid "An error occurred during pull from remote location"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/repos.py:677
 
#: kallithea/controllers/admin/repos.py:670
 
msgid "An error occurred during deletion of repository stats"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/settings.py:170
 
msgid "Updated VCS settings"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/settings.py:174
 
msgid "Unable to activate hgsubversion support. The \"hgsubversion\" library is missing"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/settings.py:180
 
#: kallithea/controllers/admin/settings.py:274
 
msgid "Error occurred during updating application settings"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/settings.py:213
 
#, python-format
 
msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/settings.py:270
 
msgid "Updated application settings"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/settings.py:327
 
msgid "Updated visualisation settings"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/settings.py:332
 
msgid "Error occurred during updating visualisation settings"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/settings.py:358
 
msgid "Please enter email address"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/settings.py:373
 
msgid "Send email task created"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/settings.py:404
 
msgid "Added new hook"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/settings.py:418
 
msgid "Updated hooks"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/settings.py:422
 
msgid "Error occurred during hook creation"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/settings.py:448
 
msgid "Whoosh reindex task scheduled"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/user_groups.py:150
 
#, python-format
 
msgid "Created user group %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/user_groups.py:163
 
#, python-format
 
msgid "Error occurred during creation of user group %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/user_groups.py:201
 
#, python-format
 
msgid "Updated user group %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/user_groups.py:224
 
#, python-format
 
msgid "Error occurred during update of user group %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/user_groups.py:242
 
msgid "Successfully deleted user group"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/user_groups.py:247
 
msgid "An error occurred during deletion of user group"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/user_groups.py:314
 
msgid "Target group cannot be the same"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/user_groups.py:320
 
msgid "User Group permissions updated"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/user_groups.py:440
 
#: kallithea/controllers/admin/users.py:396
 
msgid "Updated permissions"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/user_groups.py:444
 
#: kallithea/controllers/admin/users.py:400
 
msgid "An error occurred during permissions saving"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/users.py:132
 
#, python-format
 
msgid "Created user %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/users.py:147
 
#, python-format
 
msgid "Error occurred during creation of user %s"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/users.py:186
 
msgid "User updated successfully"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/users.py:222
 
msgid "Successfully deleted user"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/users.py:227
 
msgid "An error occurred during deletion of user"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/users.py:241
 
#: kallithea/controllers/admin/users.py:259
 
#: kallithea/controllers/admin/users.py:282
 
#: kallithea/controllers/admin/users.py:307
 
#: kallithea/controllers/admin/users.py:320
 
#: kallithea/controllers/admin/users.py:344
 
#: kallithea/controllers/admin/users.py:407
 
#: kallithea/controllers/admin/users.py:454
 
msgid "You can't edit this user"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/users.py:482
 
#, python-format
 
msgid "Added IP address %s to user whitelist"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/users.py:488
 
msgid "An error occurred during ip saving"
 
msgstr ""
 

	
 
#: kallithea/controllers/admin/users.py:502
 
msgid "Removed IP address from user whitelist"
 
msgstr ""
 

	
 
#: kallithea/lib/auth.py:745
 
#: kallithea/lib/auth.py:746
 
#, python-format
 
msgid "IP %s not allowed"
 
msgstr ""
 

	
 
#: kallithea/lib/auth.py:806
 
#: kallithea/lib/auth.py:814
 
msgid "You need to be a registered user to perform this action"
 
msgstr ""
 

	
 
#: kallithea/lib/auth.py:843
 
#: kallithea/lib/auth.py:851
 
msgid "You need to be signed in to view this page"
 
msgstr ""
 

	
 
#: kallithea/lib/base.py:427
 
msgid "Repository not found in the filesystem"
 
msgstr ""
 

	
 
#: kallithea/lib/base.py:453 kallithea/lib/helpers.py:643
 
#: kallithea/lib/base.py:453 kallithea/lib/helpers.py:626
 
msgid "Changeset not found"
 
msgstr ""
 

	
 
#: kallithea/lib/diffs.py:66
 
msgid "Binary file"
 
msgstr ""
 

	
 
#: kallithea/lib/diffs.py:82
 
msgid "Changeset was too big and was cut off, use diff menu to display this diff"
 
msgstr ""
 

	
 
#: kallithea/lib/diffs.py:92
 
msgid "No changes detected"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:627
 
#: kallithea/lib/helpers.py:610
 
#, python-format
 
msgid "Deleted branch: %s"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:630
 
#: kallithea/lib/helpers.py:613
 
#, python-format
 
msgid "Created tag: %s"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:693
 
#: kallithea/lib/helpers.py:676
 
#, python-format
 
msgid "Show all combined changesets %s->%s"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:699
 
#: kallithea/lib/helpers.py:682
 
msgid "compare view"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:718
 
#: kallithea/lib/helpers.py:701
 
msgid "and"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:719
 
#: kallithea/lib/helpers.py:702
 
#, python-format
 
msgid "%s more"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:720 kallithea/templates/changelog/changelog.html:44
 
#: kallithea/lib/helpers.py:703 kallithea/templates/changelog/changelog.html:44
 
msgid "revisions"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:727
 
#, python-format
 
msgid "fork name %s"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:744
 
#, python-format
 
msgid "fork name %s"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:761
 
#, python-format
 
msgid "Pull request #%s"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:771
 
#: kallithea/lib/helpers.py:754
 
msgid "[deleted] repository"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:773 kallithea/lib/helpers.py:785
 
#: kallithea/lib/helpers.py:756 kallithea/lib/helpers.py:768
 
msgid "[created] repository"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:775
 
#: kallithea/lib/helpers.py:758
 
msgid "[created] repository as fork"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:777 kallithea/lib/helpers.py:787
 
#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770
 
msgid "[forked] repository"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:779 kallithea/lib/helpers.py:789
 
#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 
msgid "[updated] repository"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:781
 
#: kallithea/lib/helpers.py:764
 
msgid "[downloaded] archive from repository"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:783
 
#: kallithea/lib/helpers.py:766
 
msgid "[delete] repository"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:791
 
#: kallithea/lib/helpers.py:774
 
msgid "[created] user"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:793
 
#: kallithea/lib/helpers.py:776
 
msgid "[updated] user"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:795
 
#: kallithea/lib/helpers.py:778
 
msgid "[created] user group"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:797
 
#: kallithea/lib/helpers.py:780
 
msgid "[updated] user group"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:799
 
#: kallithea/lib/helpers.py:782
 
msgid "[commented] on revision in repository"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:801
 
#: kallithea/lib/helpers.py:784
 
msgid "[commented] on pull request for"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:803
 
#: kallithea/lib/helpers.py:786
 
msgid "[closed] pull request for"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:805
 
#: kallithea/lib/helpers.py:788
 
msgid "[pushed] into"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:807
 
#: kallithea/lib/helpers.py:790
 
msgid "[committed via Kallithea] into repository"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:809
 
#: kallithea/lib/helpers.py:792
 
msgid "[pulled from remote] into repository"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:811
 
#: kallithea/lib/helpers.py:794
 
msgid "[pulled] from"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:813
 
#: kallithea/lib/helpers.py:796
 
msgid "[started following] repository"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:815
 
#: kallithea/lib/helpers.py:798
 
msgid "[stopped following] repository"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:1144
 
#: kallithea/lib/helpers.py:1127
 
#, python-format
 
msgid " and %s more"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:1148
 
#: kallithea/lib/helpers.py:1131
 
msgid "No Files"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:1214
 
#: kallithea/lib/helpers.py:1197
 
msgid "new file"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:1217
 
#: kallithea/lib/helpers.py:1200
 
msgid "mod"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:1220
 
#: kallithea/lib/helpers.py:1203
 
msgid "del"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:1223
 
#: kallithea/lib/helpers.py:1206
 
msgid "rename"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:1228
 
#: kallithea/lib/helpers.py:1211
 
msgid "chmod"
 
msgstr ""
 

	
 
#: kallithea/lib/helpers.py:1460
 
#: kallithea/lib/helpers.py:1443
 
#, python-format
 
msgid "%s repository is not mapped to db perhaps it was created or renamed from the filesystem please run the application again in order to rescan repositories"
 
msgstr ""
 

	
 
#: kallithea/lib/utils2.py:425
 
#, python-format
 
msgid "%d year"
 
msgid_plural "%d years"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/lib/utils2.py:426
 
#, python-format
 
msgid "%d month"
 
msgid_plural "%d months"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/lib/utils2.py:427
 
#, python-format
 
msgid "%d day"
 
msgid_plural "%d days"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/lib/utils2.py:428
 
#, python-format
 
msgid "%d hour"
 
msgid_plural "%d hours"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/lib/utils2.py:429
 
#, python-format
 
msgid "%d minute"
 
msgid_plural "%d minutes"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/lib/utils2.py:430
 
#, python-format
 
msgid "%d second"
 
msgid_plural "%d seconds"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/lib/utils2.py:446
 
#, python-format
 
msgid "in %s"
 
msgstr ""
 

	
 
#: kallithea/lib/utils2.py:448
 
#, python-format
 
msgid "%s ago"
 
msgstr ""
 

	
 
#: kallithea/lib/utils2.py:450
 
#, python-format
 
msgid "in %s and %s"
 
msgstr ""
 

	
 
#: kallithea/lib/utils2.py:453
 
#, python-format
 
msgid "%s and %s ago"
 
msgstr ""
 

	
 
#: kallithea/lib/utils2.py:456
 
msgid "just now"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
 
#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
 
#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
 
#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649 kallithea/model/db.py:1651
 
msgid "Repository no access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
 
#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
 
#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
 
#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650 kallithea/model/db.py:1652
 
msgid "Repository read access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
 
#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
 
#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
 
#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651 kallithea/model/db.py:1653
 
msgid "Repository write access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
 
#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
 
#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
 
#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652 kallithea/model/db.py:1654
 
msgid "Repository admin access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
 
#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
 
#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
 
msgid "Repository Group no access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
 
#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
 
#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
 
msgid "Repository Group read access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
 
#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
 
#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
 
msgid "Repository Group write access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
 
#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
 
#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
 
msgid "Repository Group admin access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
 
#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
 
#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
 
#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1649
 
msgid "Kallithea Administrator"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
 
#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
 
#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
 
#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670 kallithea/model/db.py:1672
 
msgid "Repository creation disabled"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
 
#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
 
#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
 
#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
 
@@ -1496,2198 +1492,2193 @@ msgstr ""
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1500
 
msgid "top level"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654 kallithea/model/db.py:1656
 
msgid "Repository group no access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655 kallithea/model/db.py:1657
 
msgid "Repository group read access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656 kallithea/model/db.py:1658
 
msgid "Repository group write access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657 kallithea/model/db.py:1659
 
msgid "Repository group admin access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659 kallithea/model/db.py:1661
 
msgid "User group no access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660 kallithea/model/db.py:1662
 
msgid "User group read access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661 kallithea/model/db.py:1663
 
msgid "User group write access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662 kallithea/model/db.py:1664
 
msgid "User group admin access"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664 kallithea/model/db.py:1666
 
msgid "Repository Group creation disabled"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665 kallithea/model/db.py:1667
 
msgid "Repository Group creation enabled"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667 kallithea/model/db.py:1669
 
msgid "User Group creation disabled"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668 kallithea/model/db.py:1670
 
msgid "User Group creation enabled"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1680
 
msgid "Registration disabled"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679 kallithea/model/db.py:1681
 
msgid "User Registration with manual account activation"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
 
#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
 
#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
 
#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
 
#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
 
#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680 kallithea/model/db.py:1682
 
msgid "User Registration with automatic account activation"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1674
 
msgid "Repository creation enabled with write permission to a repository group"
 
msgstr ""
 

	
 
#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
 
#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1675
 
msgid "Repository creation disabled with write permission to a repository group"
 
msgstr ""
 

	
 
#: kallithea/model/comment.py:76
 
#, python-format
 
msgid "on line %s"
 
msgstr ""
 

	
 
#: kallithea/model/comment.py:231 kallithea/model/pull_request.py:164
 
#: kallithea/model/comment.py:231 kallithea/model/pull_request.py:165
 
msgid "[Mention]"
 
msgstr ""
 

	
 
#: kallithea/model/forms.py:57
 
msgid "Please enter a login"
 
msgstr ""
 

	
 
#: kallithea/model/forms.py:58
 
#, python-format
 
msgid "Enter a value %(min)i characters long or more"
 
msgstr ""
 

	
 
#: kallithea/model/forms.py:66
 
msgid "Please enter a password"
 
msgstr ""
 

	
 
#: kallithea/model/forms.py:67
 
#, python-format
 
msgid "Enter %(min)i characters or more"
 
msgstr ""
 

	
 
#: kallithea/model/forms.py:156
 
msgid "Name must not contain only digits"
 
msgstr ""
 

	
 
#: kallithea/model/notification.py:252
 
#, python-format
 
msgid "%(user)s commented on changeset at %(when)s"
 
msgstr ""
 

	
 
#: kallithea/model/notification.py:253
 
#, python-format
 
msgid "%(user)s sent message at %(when)s"
 
msgstr ""
 

	
 
#: kallithea/model/notification.py:254
 
#, python-format
 
msgid "%(user)s mentioned you at %(when)s"
 
msgstr ""
 

	
 
#: kallithea/model/notification.py:255
 
#, python-format
 
msgid "%(user)s registered in Kallithea at %(when)s"
 
msgstr ""
 

	
 
#: kallithea/model/notification.py:256
 
#, python-format
 
msgid "%(user)s opened new pull request at %(when)s"
 
msgstr ""
 

	
 
#: kallithea/model/notification.py:257
 
#, python-format
 
msgid "%(user)s commented on pull request at %(when)s"
 
msgstr ""
 

	
 
#: kallithea/model/notification.py:296
 
#, python-format
 
msgid "Comment on %(repo_name)s changeset %(short_id)s on %(branch)s by %(comment_username)s"
 
msgstr ""
 

	
 
#: kallithea/model/notification.py:299
 
#, python-format
 
msgid "New user %(new_username)s registered"
 
msgstr ""
 

	
 
#: kallithea/model/notification.py:301
 
#, python-format
 
msgid "Review request on %(repo_name)s pull request #%(pr_id)s from %(ref)s by %(pr_username)s"
 
msgstr ""
 

	
 
#: kallithea/model/notification.py:302
 
#, python-format
 
msgid "Comment on %(repo_name)s pull request #%(pr_id)s from %(ref)s by %(comment_username)s"
 
msgstr ""
 

	
 
#: kallithea/model/notification.py:315
 
msgid "Closing"
 
msgstr ""
 

	
 
#: kallithea/model/pull_request.py:132
 
#: kallithea/model/pull_request.py:133
 
#, python-format
 
msgid "%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s"
 
msgstr ""
 

	
 
#: kallithea/model/scm.py:808
 
msgid "latest tip"
 
msgstr ""
 

	
 
#: kallithea/model/user.py:194
 
msgid "New user registration"
 
msgstr ""
 

	
 
#: kallithea/model/user.py:214 kallithea/model/user.py:236
 
msgid "You can't Edit this user since it's crucial for entire application"
 
msgstr ""
 

	
 
#: kallithea/model/user.py:255
 
msgid "You can't remove this user since it's crucial for entire application"
 
msgstr ""
 

	
 
#: kallithea/model/user.py:261
 
#, python-format
 
msgid "User \"%s\" still owns %s repositories and cannot be removed. Switch owners or remove those repositories: %s"
 
msgstr ""
 

	
 
#: kallithea/model/user.py:268
 
#, python-format
 
msgid "User \"%s\" still owns %s repository groups and cannot be removed. Switch owners or remove those repository groups: %s"
 
msgstr ""
 

	
 
#: kallithea/model/user.py:275
 
#, python-format
 
msgid "User \"%s\" still owns %s user groups and cannot be removed. Switch owners or remove those user groups: %s"
 
msgstr ""
 

	
 
#: kallithea/model/user.py:305
 
msgid "Password reset link"
 
msgstr ""
 

	
 
#: kallithea/model/user.py:328
 
msgid "Your new password"
 
msgstr ""
 

	
 
#: kallithea/model/user.py:329
 
#, python-format
 
msgid "Your new Kallithea password:%s"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:83 kallithea/model/validators.py:84
 
msgid "Value cannot be an empty list"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:101
 
#, python-format
 
msgid "Username \"%(username)s\" already exists"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:103
 
#, python-format
 
msgid "Username \"%(username)s\" is forbidden"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:105
 
msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character or underscore"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:132
 
msgid "The input is not valid"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:139
 
#, python-format
 
msgid "Username %(username)s is not valid"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:158
 
msgid "Invalid user group name"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:159
 
#, python-format
 
msgid "User group \"%(usergroup)s\" already exists"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:161
 
msgid "user group name may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:199
 
msgid "Cannot assign this group as parent"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:200
 
#, python-format
 
msgid "Group \"%(group_name)s\" already exists"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:202
 
#, python-format
 
msgid "Repository with name \"%(group_name)s\" already exists"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:260
 
msgid "Invalid characters (non-ascii) in password"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:275
 
msgid "Invalid old password"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:291
 
msgid "Passwords do not match"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:308
 
msgid "invalid password"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:309
 
msgid "invalid user name"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:310
 
msgid "Your account is disabled"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:340
 
msgid "Token mismatch"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:354
 
#, python-format
 
msgid "Repository name %(repo)s is disallowed"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:356
 
#, python-format
 
msgid "Repository named %(repo)s already exists"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:357
 
#, python-format
 
msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:359
 
#, python-format
 
msgid "Repository group with name \"%(repo)s\" already exists"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:474
 
msgid "invalid clone URL"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:475
 
msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:500
 
msgid "Fork has to be the same type as parent"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:515
 
msgid "You don't have permissions to create repository in this group"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:517
 
msgid "no permission to create repository in root location"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:566
 
msgid "You don't have permissions to create a group in this location"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:607
 
msgid "This username or user group name is not valid"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:700
 
msgid "This is not a valid path"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:715
 
msgid "This e-mail address is already taken"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:735
 
#, python-format
 
msgid "e-mail \"%(email)s\" does not exist."
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:772
 
msgid "The LDAP Login attribute of the CN must be specified - this is the name of the attribute that is equivalent to \"username\""
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:785
 
#, python-format
 
msgid "Revisions %(revs)s are already part of pull request or have set status"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:817
 
msgid "Please enter a valid IPv4 or IPv6 address"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:818
 
#, python-format
 
msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:851
 
msgid "Key name can only consist of letters, underscore, dash or numbers"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:865
 
msgid "Filename cannot be inside a directory"
 
msgstr ""
 

	
 
#: kallithea/model/validators.py:881
 
#, python-format
 
msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 
msgstr ""
 

	
 
#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
 
msgid "About"
 
msgstr ""
 

	
 
#: kallithea/templates/index.html:5
 
msgid "Dashboard"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:6
 
#: kallithea/templates/admin/my_account/my_account_repos.html:3
 
#: kallithea/templates/admin/my_account/my_account_watched.html:3
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 
#: kallithea/templates/admin/repos/repos.html:9
 
#: kallithea/templates/admin/user_groups/user_groups.html:9
 
#: kallithea/templates/admin/users/users.html:9
 
#: kallithea/templates/bookmarks/bookmarks.html:9
 
#: kallithea/templates/branches/branches.html:9
 
#: kallithea/templates/journal/journal.html:9
 
#: kallithea/templates/journal/journal.html:48
 
#: kallithea/templates/journal/journal.html:49
 
#: kallithea/templates/tags/tags.html:9
 
msgid "quick filter..."
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:6
 
msgid "repositories"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:20
 
#: kallithea/templates/index_base.html:25
 
#: kallithea/templates/admin/repos/repo_add.html:5
 
#: kallithea/templates/admin/repos/repo_add.html:19
 
#: kallithea/templates/admin/repos/repos.html:22
 
msgid "Add Repository"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:22
 
#: kallithea/templates/index_base.html:27
 
#: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 
#: kallithea/templates/admin/repo_groups/repo_group_add.html:13
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:26
 
msgid "Add Repository Group"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:32
 
msgid "You have admin right to this group, and can edit it"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:32
 
msgid "Edit Repository Group"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:45
 
msgid "Group Name"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:46
 
#: kallithea/templates/index_base.html:131
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
 
#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:47
 
#: kallithea/templates/admin/repos/repo_add_base.html:32
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:72
 
#: kallithea/templates/admin/repos/repos.html:48
 
#: kallithea/templates/admin/user_groups/user_group_add.html:40
 
#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
 
#: kallithea/templates/admin/user_groups/user_groups.html:47
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:64
 
#: kallithea/templates/email_templates/changeset_comment.html:18
 
#: kallithea/templates/email_templates/pull_request.html:12
 
#: kallithea/templates/forks/fork.html:38
 
#: kallithea/templates/pullrequests/pullrequest.html:40
 
#: kallithea/templates/pullrequests/pullrequest_show.html:38
 
#: kallithea/templates/pullrequests/pullrequest_show.html:63
 
#: kallithea/templates/summary/summary.html:84
 
#: kallithea/templates/summary/summary.html:85
 
msgid "Description"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:129
 
#: kallithea/templates/admin/my_account/my_account_repos.html:46
 
#: kallithea/templates/admin/my_account/my_account_watched.html:46
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:46
 
#: kallithea/templates/admin/repos/repo_add_base.html:9
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 
#: kallithea/templates/admin/repos/repos.html:47
 
#: kallithea/templates/admin/user_groups/user_groups.html:46
 
#: kallithea/templates/base/perms_summary.html:53
 
#: kallithea/templates/bookmarks/bookmarks.html:49
 
#: kallithea/templates/bookmarks/bookmarks_data.html:7
 
#: kallithea/templates/branches/branches.html:49
 
#: kallithea/templates/branches/branches_data.html:7
 
#: kallithea/templates/files/files_browser.html:60
 
#: kallithea/templates/journal/journal.html:187
 
#: kallithea/templates/journal/journal.html:278
 
#: kallithea/templates/tags/tags.html:49
 
#: kallithea/templates/tags/tags_data.html:7
 
msgid "Name"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:132
 
msgid "Last Change"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:134
 
#: kallithea/templates/admin/my_account/my_account_repos.html:48
 
#: kallithea/templates/admin/my_account/my_account_watched.html:48
 
#: kallithea/templates/admin/repos/repos.html:49
 
#: kallithea/templates/journal/journal.html:189
 
#: kallithea/templates/journal/journal.html:280
 
msgid "Tip"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:136
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:49
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:60
 
#: kallithea/templates/admin/repos/repos.html:50
 
#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
 
#: kallithea/templates/admin/user_groups/user_groups.html:50
 
#: kallithea/templates/summary/summary.html:137
 
#: kallithea/templates/summary/summary.html:138
 
msgid "Owner"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:144
 
#: kallithea/templates/admin/my_account/my_account_repos.html:57
 
#: kallithea/templates/admin/my_account/my_account_watched.html:57
 
#: kallithea/templates/base/root.html:44
 
#: kallithea/templates/bookmarks/bookmarks.html:79
 
#: kallithea/templates/branches/branches.html:79
 
#: kallithea/templates/journal/journal.html:198
 
#: kallithea/templates/journal/journal.html:289
 
#: kallithea/templates/tags/tags.html:79
 
msgid "Click to sort ascending"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:145
 
#: kallithea/templates/admin/my_account/my_account_repos.html:58
 
#: kallithea/templates/admin/my_account/my_account_watched.html:58
 
#: kallithea/templates/base/root.html:45
 
#: kallithea/templates/bookmarks/bookmarks.html:80
 
#: kallithea/templates/branches/branches.html:80
 
#: kallithea/templates/journal/journal.html:199
 
#: kallithea/templates/journal/journal.html:290
 
#: kallithea/templates/tags/tags.html:80
 
msgid "Click to sort descending"
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:146
 
msgid "No repositories found."
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:147
 
#: kallithea/templates/admin/my_account/my_account_repos.html:60
 
#: kallithea/templates/admin/my_account/my_account_watched.html:60
 
#: kallithea/templates/base/root.html:47
 
#: kallithea/templates/bookmarks/bookmarks.html:82
 
#: kallithea/templates/branches/branches.html:82
 
#: kallithea/templates/journal/journal.html:201
 
#: kallithea/templates/journal/journal.html:292
 
#: kallithea/templates/tags/tags.html:82
 
msgid "Data error."
 
msgstr ""
 

	
 
#: kallithea/templates/index_base.html:148
 
#: kallithea/templates/admin/my_account/my_account_repos.html:61
 
#: kallithea/templates/admin/my_account/my_account_watched.html:61
 
#: kallithea/templates/base/base.html:143 kallithea/templates/base/root.html:48
 
#: kallithea/templates/base/base.html:147 kallithea/templates/base/root.html:48
 
#: kallithea/templates/bookmarks/bookmarks.html:83
 
#: kallithea/templates/branches/branches.html:83
 
#: kallithea/templates/journal/journal.html:202
 
#: kallithea/templates/journal/journal.html:293
 
#: kallithea/templates/tags/tags.html:83
 
msgid "Loading..."
 
msgstr ""
 

	
 
#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
 
#: kallithea/templates/base/base.html:329
 
#: kallithea/templates/base/base.html:333
 
msgid "Log In"
 
msgstr ""
 

	
 
#: kallithea/templates/login.html:13
 
#, python-format
 
msgid "Log In to %s"
 
msgstr ""
 

	
 
#: kallithea/templates/login.html:27 kallithea/templates/register.html:24
 
#: kallithea/templates/admin/admin_log.html:5
 
#: kallithea/templates/admin/my_account/my_account_profile.html:32
 
#: kallithea/templates/admin/users/user_add.html:32
 
#: kallithea/templates/admin/users/user_edit_profile.html:33
 
#: kallithea/templates/admin/users/users.html:50
 
#: kallithea/templates/base/base.html:305
 
#: kallithea/templates/base/base.html:309
 
msgid "Username"
 
msgstr ""
 

	
 
#: kallithea/templates/login.html:36 kallithea/templates/register.html:33
 
#: kallithea/templates/admin/my_account/my_account.html:36
 
#: kallithea/templates/admin/users/user_add.html:41
 
#: kallithea/templates/base/base.html:314
 
#: kallithea/templates/base/base.html:318
 
msgid "Password"
 
msgstr ""
 

	
 
#: kallithea/templates/login.html:46
 
msgid "Remember me"
 
msgstr ""
 

	
 
#: kallithea/templates/login.html:50
 
msgid "Sign In"
 
msgstr ""
 

	
 
#: kallithea/templates/login.html:56
 
msgid "Forgot your password ?"
 
msgstr ""
 

	
 
#: kallithea/templates/login.html:59 kallithea/templates/base/base.html:325
 
#: kallithea/templates/login.html:59 kallithea/templates/base/base.html:329
 
msgid "Don't have an account ?"
 
msgstr ""
 

	
 
#: kallithea/templates/password_reset.html:5
 
msgid "Password Reset"
 
msgstr ""
 

	
 
#: kallithea/templates/password_reset.html:12
 
#, python-format
 
msgid "Reset Your Password to %s"
 
msgstr ""
 

	
 
#: kallithea/templates/password_reset.html:14
 
msgid "Reset Your Password"
 
msgstr ""
 

	
 
#: kallithea/templates/password_reset.html:25
 
msgid "Email Address"
 
msgstr ""
 

	
 
#: kallithea/templates/password_reset.html:35
 
#: kallithea/templates/register.html:79
 
msgid "Captcha"
 
msgstr ""
 

	
 
#: kallithea/templates/password_reset.html:46
 
msgid "Send Password Reset Email"
 
msgstr ""
 

	
 
#: kallithea/templates/password_reset.html:47
 
msgid "Password reset link will be sent to the email address matching your username."
 
msgstr ""
 

	
 
#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
 
#: kallithea/templates/register.html:90
 
msgid "Sign Up"
 
msgstr ""
 

	
 
#: kallithea/templates/register.html:12
 
#, python-format
 
msgid "Sign Up to %s"
 
msgstr ""
 

	
 
#: kallithea/templates/register.html:42
 
msgid "Re-enter password"
 
msgstr ""
 

	
 
#: kallithea/templates/register.html:51
 
#: kallithea/templates/admin/my_account/my_account_profile.html:43
 
#: kallithea/templates/admin/users/user_add.html:59
 
#: kallithea/templates/admin/users/user_edit_profile.html:87
 
#: kallithea/templates/admin/users/users.html:51
 
msgid "First Name"
 
msgstr ""
 

	
 
#: kallithea/templates/register.html:60
 
#: kallithea/templates/admin/my_account/my_account_profile.html:52
 
#: kallithea/templates/admin/users/user_add.html:68
 
#: kallithea/templates/admin/users/user_edit_profile.html:96
 
#: kallithea/templates/admin/users/users.html:52
 
msgid "Last Name"
 
msgstr ""
 

	
 
#: kallithea/templates/register.html:69
 
#: kallithea/templates/admin/my_account/my_account_profile.html:61
 
#: kallithea/templates/admin/settings/settings.html:31
 
#: kallithea/templates/admin/users/user_add.html:77
 
#: kallithea/templates/admin/users/user_edit_profile.html:42
 
msgid "Email"
 
msgstr ""
 

	
 
#: kallithea/templates/register.html:92
 
msgid "Registered accounts are ready to use and need no further action."
 
msgstr ""
 

	
 
#: kallithea/templates/register.html:94
 
msgid "Please wait for an administrator to activate your account."
 
msgstr ""
 

	
 
#: kallithea/templates/switch_to_list.html:10
 
#: kallithea/templates/branches/branches_data.html:69
 
msgid "There are no branches yet"
 
msgstr ""
 

	
 
#: kallithea/templates/switch_to_list.html:16
 
msgid "Closed Branches"
 
msgstr ""
 

	
 
#: kallithea/templates/switch_to_list.html:32
 
#: kallithea/templates/tags/tags_data.html:44
 
msgid "There are no tags yet"
 
msgstr ""
 

	
 
#: kallithea/templates/switch_to_list.html:45
 
#: kallithea/templates/bookmarks/bookmarks_data.html:43
 
msgid "There are no bookmarks yet"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/admin.html:5
 
#: kallithea/templates/admin/admin.html:13
 
#: kallithea/templates/base/base.html:59
 
msgid "Admin Journal"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/admin.html:10
 
msgid "journal filter..."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/admin.html:12
 
#: kallithea/templates/journal/journal.html:11
 
msgid "Filter"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/admin.html:13
 
#: kallithea/templates/journal/journal.html:12
 
#, python-format
 
msgid "%s Entry"
 
msgid_plural "%s Entries"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/templates/admin/admin_log.html:6
 
#: kallithea/templates/admin/my_account/my_account_repos.html:50
 
#: kallithea/templates/admin/my_account/my_account_watched.html:50
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:50
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 
#: kallithea/templates/admin/repos/repos.html:52
 
#: kallithea/templates/admin/user_groups/user_groups.html:51
 
#: kallithea/templates/admin/users/users.html:57
 
#: kallithea/templates/journal/journal.html:191
 
#: kallithea/templates/journal/journal.html:282
 
msgid "Action"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/admin_log.html:7
 
#: kallithea/templates/admin/permissions/permissions_globals.html:18
 
msgid "Repository"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/admin_log.html:8
 
#: kallithea/templates/bookmarks/bookmarks.html:51
 
#: kallithea/templates/bookmarks/bookmarks_data.html:9
 
#: kallithea/templates/branches/branches.html:51
 
#: kallithea/templates/branches/branches_data.html:9
 
#: kallithea/templates/tags/tags.html:51
 
#: kallithea/templates/tags/tags_data.html:9
 
msgid "Date"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/admin_log.html:9
 
msgid "From IP"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/admin_log.html:63
 
msgid "No actions yet"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/auth/auth_settings.html:5
 
msgid "Authentication Settings"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/auth/auth_settings.html:11
 
#: kallithea/templates/base/base.html:65
 
msgid "Authentication"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/auth/auth_settings.html:28
 
msgid "Authentication Plugins"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/auth/auth_settings.html:31
 
msgid "Enabled Plugins"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/auth/auth_settings.html:33
 
msgid "Comma separated list of plugins. Order of plugins is also order in which Kallithea will try to authenticate user"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/auth/auth_settings.html:34
 
msgid "Available built-in plugins"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/auth/auth_settings.html:40
 
#: kallithea/templates/base/root.html:40
 
msgid "enabled"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/auth/auth_settings.html:40
 
#: kallithea/templates/base/root.html:41
 
msgid "disabled"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/auth/auth_settings.html:51
 
msgid "Plugin"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/auth/auth_settings.html:101
 
#: kallithea/templates/admin/defaults/defaults.html:84
 
#: kallithea/templates/admin/defaults/defaults.html:82
 
#: kallithea/templates/admin/my_account/my_account_password.html:33
 
#: kallithea/templates/admin/my_account/my_account_profile.html:70
 
#: kallithea/templates/admin/permissions/permissions_globals.html:108
 
#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
 
#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:134
 
#: kallithea/templates/admin/settings/settings_hooks.html:53
 
#: kallithea/templates/admin/user_groups/user_group_add.html:57
 
#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
 
#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
 
#: kallithea/templates/admin/users/user_add.html:96
 
#: kallithea/templates/admin/users/user_edit_profile.html:122
 
#: kallithea/templates/base/default_perms_box.html:64
 
msgid "Save"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/defaults/defaults.html:5
 
#: kallithea/templates/admin/defaults/defaults.html:25
 
msgid "Repository Defaults"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/defaults/defaults.html:11
 
#: kallithea/templates/base/base.html:66
 
msgid "Defaults"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/defaults/defaults.html:35
 
#: kallithea/templates/admin/defaults/defaults.html:33
 
#: kallithea/templates/admin/repos/repo_add_base.html:59
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 
msgid "Type"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/defaults/defaults.html:44
 
#: kallithea/templates/admin/defaults/defaults.html:42
 
#: kallithea/templates/admin/repos/repo_add_base.html:77
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:82
 
#: kallithea/templates/data_table/_dt_elements.html:74
 
#: kallithea/templates/data_table/_dt_elements.html:72
 
msgid "Private repository"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/defaults/defaults.html:48
 
#: kallithea/templates/admin/defaults/defaults.html:46
 
#: kallithea/templates/admin/repos/repo_add_base.html:81
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:86
 
#: kallithea/templates/forks/fork.html:72
 
msgid "Private repositories are only visible to people explicitly added as collaborators."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/defaults/defaults.html:55
 
#: kallithea/templates/admin/defaults/defaults.html:53
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:91
 
msgid "Enable statistics"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/defaults/defaults.html:59
 
#: kallithea/templates/admin/defaults/defaults.html:57
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:95
 
msgid "Enable statistics window on summary page."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/defaults/defaults.html:65
 
#: kallithea/templates/admin/defaults/defaults.html:63
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:100
 
msgid "Enable downloads"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/defaults/defaults.html:69
 
#: kallithea/templates/admin/defaults/defaults.html:67
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:104
 
msgid "Enable download menu on summary page."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/defaults/defaults.html:75
 
#: kallithea/templates/admin/defaults/defaults.html:73
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:109
 
msgid "Enable locking"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/defaults/defaults.html:79
 
#: kallithea/templates/admin/defaults/defaults.html:77
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:113
 
msgid "Enable lock-by-pulling on repository."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/edit.html:5
 
#: kallithea/templates/admin/gists/edit.html:18
 
msgid "Edit Gist"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/edit.html:36
 
#, python-format
 
msgid "Gist was update since you started editing. Copy your changes and click %(here)s to reload new version."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/edit.html:55
 
#: kallithea/templates/admin/gists/new.html:39
 
msgid "Gist description ..."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/edit.html:57
 
#: kallithea/templates/admin/gists/new.html:41
 
msgid "Gist lifetime"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/edit.html:61
 
#: kallithea/templates/admin/gists/edit.html:63
 
#: kallithea/templates/admin/gists/index.html:57
 
#: kallithea/templates/admin/gists/index.html:59
 
#: kallithea/templates/admin/gists/show.html:47
 
#: kallithea/templates/admin/gists/show.html:49
 
msgid "Expires"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/edit.html:61
 
#: kallithea/templates/admin/gists/index.html:57
 
#: kallithea/templates/admin/gists/show.html:47
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:8
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:27
 
msgid "never"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/edit.html:145
 
msgid "Update Gist"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/edit.html:146
 
#: kallithea/templates/changeset/changeset_file_comment.html:89
 
msgid "Cancel"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/index.html:6
 
#: kallithea/templates/admin/gists/index.html:16
 
#, python-format
 
msgid "Private Gists for User %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/index.html:8
 
#: kallithea/templates/admin/gists/index.html:18
 
#, python-format
 
msgid "Public Gists for User %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/index.html:10
 
#: kallithea/templates/admin/gists/index.html:20
 
msgid "Public Gists"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/index.html:37
 
#: kallithea/templates/admin/gists/show.html:25
 
#: kallithea/templates/base/base.html:240
 
#: kallithea/templates/base/base.html:244
 
msgid "Create New Gist"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/index.html:54
 
#: kallithea/templates/data_table/_dt_elements.html:143
 
#: kallithea/templates/data_table/_dt_elements.html:141
 
msgid "Created"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/index.html:74
 
msgid "There are no gists yet"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/new.html:5
 
#: kallithea/templates/admin/gists/new.html:18
 
msgid "New Gist"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/new.html:47
 
msgid "name this file..."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/new.html:56
 
msgid "Create Private Gist"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/new.html:57
 
msgid "Create Public Gist"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/new.html:58
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
 
#: kallithea/templates/admin/my_account/my_account_emails.html:46
 
#: kallithea/templates/admin/my_account/my_account_password.html:34
 
#: kallithea/templates/admin/my_account/my_account_profile.html:71
 
#: kallithea/templates/admin/permissions/permissions_globals.html:109
 
#: kallithea/templates/admin/permissions/permissions_ips.html:41
 
#: kallithea/templates/admin/permissions/permissions_ips.html:39
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:59
 
#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:135
 
#: kallithea/templates/admin/settings/settings_global.html:57
 
#: kallithea/templates/admin/settings/settings_vcs.html:81
 
#: kallithea/templates/admin/settings/settings_visual.html:117
 
#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:70
 
#: kallithea/templates/admin/users/user_edit_emails.html:46
 
#: kallithea/templates/admin/users/user_edit_ips.html:50
 
#: kallithea/templates/admin/users/user_edit_profile.html:123
 
#: kallithea/templates/base/default_perms_box.html:65
 
#: kallithea/templates/files/files_add.html:65
 
#: kallithea/templates/files/files_delete.html:44
 
#: kallithea/templates/files/files_edit.html:68
 
#: kallithea/templates/pullrequests/pullrequest.html:89
 
msgid "Reset"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/show.html:5
 
#: kallithea/templates/admin/gists/show.html:9
 
msgid "Gist"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/show.html:10
 
#: kallithea/templates/email_templates/changeset_comment.html:15
 
#: kallithea/templates/email_templates/pull_request.html:10
 
#: kallithea/templates/email_templates/pull_request_comment.html:15
 
msgid "URL"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/show.html:37
 
msgid "Public Gist"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/show.html:39
 
msgid "Private Gist"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/show.html:56
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:76
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
 
#: kallithea/templates/changeset/changeset_file_comment.html:50
 
#: kallithea/templates/files/files_source.html:39
 
#: kallithea/templates/files/files_source.html:42
 
#: kallithea/templates/files/files_source.html:45
 
msgid "Delete"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/show.html:56
 
msgid "Confirm to delete this Gist"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/show.html:63
 
#: kallithea/templates/changeset/changeset_file_comment.html:91
 
#: kallithea/templates/changeset/changeset_file_comment.html:207
 
#: kallithea/templates/data_table/_dt_elements.html:167
 
#: kallithea/templates/data_table/_dt_elements.html:183
 
#: kallithea/templates/data_table/_dt_elements.html:165
 
#: kallithea/templates/data_table/_dt_elements.html:181
 
#: kallithea/templates/files/diff_2way.html:56
 
#: kallithea/templates/files/files_source.html:41
 
#: kallithea/templates/files/files_source.html:44
 
#: kallithea/templates/pullrequests/pullrequest_show.html:41
 
msgid "Edit"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/show.html:65
 
#: kallithea/templates/files/files_edit.html:49
 
#: kallithea/templates/files/files_source.html:34
 
msgid "Show as Raw"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/show.html:73
 
msgid "created"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/gists/show.html:86
 
#: kallithea/templates/files/files_source.html:73
 
msgid "Show as raw"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account.html:5
 
#: kallithea/templates/admin/my_account/my_account.html:9
 
#: kallithea/templates/base/base.html:346
 
#: kallithea/templates/base/base.html:350
 
msgid "My Account"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account.html:35
 
#: kallithea/templates/admin/users/user_edit.html:29
 
msgid "Profile"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account.html:37
 
#: kallithea/templates/admin/users/user_edit.html:30
 
msgid "API Keys"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account.html:38
 
msgid "My Emails"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account.html:39
 
msgid "My Repositories"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account.html:40
 
#: kallithea/templates/journal/journal.html:53
 
msgid "Watched"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account.html:41
 
msgid "My Permissions"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:6
 
msgid "Built-in"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:8
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:27
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:32
 
msgid "expires"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:14
 
#, python-format
 
msgid "Confirm to reset this api key: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:15
 
msgid "reset"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:30
 
msgid "expired"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:40
 
#, python-format
 
msgid "Confirm to remove this api key: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:42
 
msgid "remove"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:49
 
msgid "No additional api keys specified"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:61
 
msgid "New api key"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
 
#: kallithea/templates/admin/my_account/my_account_emails.html:45
 
#: kallithea/templates/admin/permissions/permissions_ips.html:40
 
#: kallithea/templates/admin/permissions/permissions_ips.html:38
 
#: kallithea/templates/admin/repos/repo_add_base.html:85
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:58
 
#: kallithea/templates/admin/users/user_edit_api_keys.html:69
 
#: kallithea/templates/admin/users/user_edit_emails.html:45
 
#: kallithea/templates/admin/users/user_edit_ips.html:49
 
msgid "Add"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_emails.html:7
 
#: kallithea/templates/admin/users/user_edit_emails.html:7
 
msgid "Primary"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_emails.html:19
 
#: kallithea/templates/admin/permissions/permissions_ips.html:14
 
#: kallithea/templates/admin/permissions/permissions_ips.html:12
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:18
 
#: kallithea/templates/admin/settings/settings_hooks.html:36
 
#: kallithea/templates/admin/users/user_edit_emails.html:19
 
#: kallithea/templates/admin/users/user_edit_ips.html:22
 
#: kallithea/templates/data_table/_dt_elements.html:131
 
#: kallithea/templates/data_table/_dt_elements.html:159
 
#: kallithea/templates/data_table/_dt_elements.html:175
 
#: kallithea/templates/data_table/_dt_elements.html:191
 
#: kallithea/templates/data_table/_dt_elements.html:129
 
#: kallithea/templates/data_table/_dt_elements.html:157
 
#: kallithea/templates/data_table/_dt_elements.html:173
 
#: kallithea/templates/data_table/_dt_elements.html:189
 
msgid "delete"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_emails.html:20
 
#: kallithea/templates/admin/users/user_edit_emails.html:20
 
#, python-format
 
msgid "Confirm to delete this email: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_emails.html:26
 
#: kallithea/templates/admin/users/user_edit_emails.html:26
 
msgid "No additional emails specified."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_emails.html:38
 
#: kallithea/templates/admin/users/user_edit_emails.html:38
 
msgid "New email address"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_password.html:1
 
msgid "Change Your Account Password"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_password.html:7
 
msgid "Current password"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_password.html:16
 
#: kallithea/templates/admin/users/user_edit_profile.html:69
 
msgid "New password"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_password.html:25
 
msgid "Confirm new password"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_profile.html:11
 
msgid "Change your avatar at"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_profile.html:12
 
#: kallithea/templates/admin/users/user_edit_profile.html:9
 
msgid "Using"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_profile.html:14
 
#: kallithea/templates/admin/users/user_edit_profile.html:11
 
msgid "Avatars are disabled"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_profile.html:15
 
msgid "Missing email, please update your user email address."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_profile.html:16
 
#: kallithea/templates/admin/users/user_edit_profile.html:15
 
msgid "current IP"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_profile.html:28
 
msgid "Your user is in an external Source of Record; some details cannot be managed here"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_repos.html:1
 
msgid "Repositories You Own"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_repos.html:59
 
#: kallithea/templates/admin/my_account/my_account_watched.html:59
 
#: kallithea/templates/base/root.html:46
 
#: kallithea/templates/bookmarks/bookmarks.html:81
 
#: kallithea/templates/branches/branches.html:81
 
#: kallithea/templates/journal/journal.html:200
 
#: kallithea/templates/journal/journal.html:291
 
#: kallithea/templates/tags/tags.html:81
 
msgid "No records found."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/my_account/my_account_watched.html:1
 
msgid "Repositories You are Watching"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/notifications/notifications.html:5
 
#: kallithea/templates/admin/notifications/notifications.html:9
 
msgid "My Notifications"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/notifications/notifications.html:24
 
msgid "All"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/notifications/notifications.html:25
 
msgid "Comments"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/notifications/notifications.html:26
 
#: kallithea/templates/base/base.html:186
 
#: kallithea/templates/base/base.html:190
 
msgid "Pull Requests"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/notifications/notifications.html:30
 
msgid "Mark All Read"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/notifications/notifications_data.html:40
 
msgid "No notifications here yet"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/notifications/show_notification.html:5
 
#: kallithea/templates/admin/notifications/show_notification.html:11
 
msgid "Show Notification"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/notifications/show_notification.html:9
 
#: kallithea/templates/base/base.html:345
 
#: kallithea/templates/base/base.html:349
 
msgid "Notifications"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions.html:5
 
msgid "Permissions Administration"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions.html:11
 
#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
 
#: kallithea/templates/admin/repos/repo_edit.html:43
 
#: kallithea/templates/admin/user_groups/user_group_edit.html:32
 
#: kallithea/templates/base/base.html:64
 
msgid "Permissions"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions.html:28
 
#: kallithea/templates/admin/settings/settings.html:29
 
msgid "Global"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions.html:29
 
#: kallithea/templates/admin/users/user_edit.html:34
 
msgid "IP Whitelist"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions.html:30
 
msgid "Overview"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:7
 
msgid "Anonymous access"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:13
 
#, python-format
 
msgid "Allow access to Kallithea without needing to log in. Anonymous users use %s user permissions."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:26
 
msgid "All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:27
 
#: kallithea/templates/admin/permissions/permissions_globals.html:40
 
#: kallithea/templates/admin/permissions/permissions_globals.html:54
 
msgid "Overwrite existing settings"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:32
 
#: kallithea/templates/admin/repos/repo_add_base.html:41
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:42
 
#: kallithea/templates/data_table/_dt_elements.html:204
 
#: kallithea/templates/data_table/_dt_elements.html:202
 
#: kallithea/templates/forks/fork.html:48
 
msgid "Repository group"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:39
 
msgid "All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:46
 
#: kallithea/templates/data_table/_dt_elements.html:211
 
#: kallithea/templates/data_table/_dt_elements.html:209
 
msgid "User group"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:53
 
msgid "All default permissions on each user group will be reset to chosen permission, note that all custom default permission on repository groups will be lost"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:60
 
msgid "Repository creation"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:68
 
msgid "Repository creation with group write access"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:72
 
msgid "Write permission to a repository group allows creating repositories inside that group."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:77
 
msgid "User group creation"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:85
 
msgid "Repository forking"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:93
 
msgid "Registration"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_globals.html:101
 
msgid "External auth account activation"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_ips.html:1
 
msgid "Default IP Whitelist for All Users"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_ips.html:15
 
#: kallithea/templates/admin/permissions/permissions_ips.html:13
 
#: kallithea/templates/admin/users/user_edit_ips.html:23
 
#, python-format
 
msgid "Confirm to delete this ip: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_ips.html:21
 
#: kallithea/templates/admin/permissions/permissions_ips.html:19
 
#: kallithea/templates/admin/users/user_edit_ips.html:30
 
msgid "All IP addresses are allowed."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_ips.html:32
 
#: kallithea/templates/admin/permissions/permissions_ips.html:30
 
#: kallithea/templates/admin/users/user_edit_ips.html:42
 
msgid "New IP address"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/permissions/permissions_perms.html:1
 
msgid "Default User Permissions Overview"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 
#: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:10
 
#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
 
msgid "Repository Groups"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
 
#: kallithea/templates/admin/user_groups/user_group_add.html:32
 
#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
 
msgid "Group name"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
 
msgid "Group parent"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
 
#: kallithea/templates/admin/repos/repo_add_base.html:50
 
msgid "Copy parent group permissions"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
 
#: kallithea/templates/admin/repos/repo_add_base.html:54
 
msgid "Copy permission set from parent repository group."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit.html:5
 
#, python-format
 
msgid "%s Repository Group Settings"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
 
msgid "Add Child Group"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
 
#: kallithea/templates/admin/repos/repo_edit.html:12
 
#: kallithea/templates/admin/repos/repo_edit.html:40
 
#: kallithea/templates/admin/settings/settings.html:11
 
#: kallithea/templates/admin/user_groups/user_group_edit.html:29
 
#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:154
 
#: kallithea/templates/data_table/_dt_elements.html:43
 
#: kallithea/templates/data_table/_dt_elements.html:47
 
#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:158
 
#: kallithea/templates/data_table/_dt_elements.html:45
 
#: kallithea/templates/data_table/_dt_elements.html:49
 
msgid "Settings"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
 
#: kallithea/templates/admin/repos/repo_edit.html:46
 
#: kallithea/templates/admin/user_groups/user_group_edit.html:30
 
#: kallithea/templates/admin/users/user_edit.html:31
 
msgid "Advanced"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:1
 
#, python-format
 
msgid "Repository Group: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:6
 
msgid "Top level repositories"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:7
 
msgid "Total repositories"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:8
 
msgid "Children groups"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 
#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 
#: kallithea/templates/admin/users/user_edit_advanced.html:8
 
#: kallithea/templates/pullrequests/pullrequest_show.html:147
 
msgid "Created on"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
 
#: kallithea/templates/data_table/_dt_elements.html:192
 
#: kallithea/templates/data_table/_dt_elements.html:190
 
#, python-format
 
msgid "Confirm to delete this group: %s with %s repository"
 
msgid_plural "Confirm to delete this group: %s with %s repositories"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:25
 
msgid "Delete this repository group"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
 
#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
 
#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 
#: kallithea/templates/base/perms_summary.html:14
 
msgid "none"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
 
#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
 
#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
 
#: kallithea/templates/base/perms_summary.html:15
 
msgid "read"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
 
#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 
#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
 
#: kallithea/templates/base/perms_summary.html:16
 
msgid "write"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 
#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 
#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 
#: kallithea/templates/base/perms_summary.html:17
 
msgid "admin"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
 
#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
 
#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 
msgid "user/user group"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
 
#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
 
#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
 
#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 
#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 
msgid "default"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
 
#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
 
#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
 
#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 
#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 
msgid "revoke"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
 
#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
 
msgid "delegated admin"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
 
#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
 
#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
 
msgid "Add new"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
 
msgid "apply to children"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
 
msgid "Both"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
 
msgid "Set or revoke permission to all children of that group, including non-private repositories and other groups if selected."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
 
msgid "Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
 
msgid "Remove this group"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
 
msgid "Confirm to delete this group"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_show.html:4
 
#, python-format
 
msgid "%s Repository group dashboard"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
 
msgid "Home"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
 
msgid "with"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:5
 
msgid "Repository Groups Administration"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repo_groups/repo_groups.html:48
 
msgid "Number of Top-level Repositories"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_add_base.html:14
 
msgid "Import existing repository ?"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_add_base.html:23
 
#: kallithea/templates/summary/summary.html:29
 
msgid "Clone from"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_add_base.html:27
 
msgid "Optional URL from which repository should be cloned."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_add_base.html:36
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:76
 
#: kallithea/templates/forks/fork.html:42
 
msgid "Keep it short and to the point. Use a README file for longer descriptions."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_add_base.html:45
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:46
 
#: kallithea/templates/forks/fork.html:52
 
msgid "Optionally select a group to put this repository into."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_add_base.html:63
 
msgid "Type of repository to create."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_add_base.html:68
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:51
 
#: kallithea/templates/forks/fork.html:58
 
msgid "Landing revision"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_add_base.html:72
 
msgid "Default revision for files page, downloads, full text search index and readme generation"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_creating.html:9
 
#, python-format
 
msgid "%s Creating Repository"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_creating.html:13
 
msgid "Creating repository"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_creating.html:27
 
#, python-format
 
msgid "Repository \"%(repo_name)s\" is being created, you will be redirected when this process is finished.repo_name"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_creating.html:39
 
msgid "We're sorry but error occurred during this operation. Please check your Kallithea server logs, or contact administrator."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit.html:8
 
#, python-format
 
msgid "%s Repository Settings"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit.html:49
 
msgid "Extra Fields"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit.html:52
 
msgid "Caches"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit.html:55
 
msgid "Remote"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit.html:58
 
#: kallithea/templates/summary/statistics.html:8
 
#: kallithea/templates/summary/summary.html:174
 
#: kallithea/templates/summary/summary.html:175
 
#: kallithea/templates/summary/summary.html:176
 
msgid "Statistics"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:1
 
msgid "Parent"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:5
 
#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 
msgid "Set"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
 
#: kallithea/templates/admin/repos/repo_edit_fork.html:9
 
msgid "Manually set this repository as a fork of another from the list."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
 
msgid "Public Journal Visibility"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:30
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
 
msgid "Remove from public journal"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:35
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
 
msgid "Add to Public Journal"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:41
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
 
msgid "All actions done in this repository will be visible to everyone in the public journal."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:47
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
 
msgid "Change Locking"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:53
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
 
msgid "Confirm to unlock repository."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:55
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
 
msgid "Unlock Repository"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:61
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
 
msgid "Confirm to lock repository."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
 
msgid "Lock Repository"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:65
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
 
msgid "Repository is not locked"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:69
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
 
msgid "Force locking on the repository. Works only when anonymous access is disabled. Triggering a pull locks the repository.  The user who is pulling locks the repository; only the user who pulled and locked it can unlock it by doing a push."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 
#: kallithea/templates/data_table/_dt_elements.html:132
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
 
#: kallithea/templates/data_table/_dt_elements.html:130
 
#, python-format
 
msgid "Confirm to delete this repository: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:82
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
 
msgid "Delete this Repository"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 
#, python-format
 
msgid "This repository has %s fork"
 
msgid_plural "This repository has %s forks"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
 
msgid "Detach forks"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:87
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
 
msgid "Delete forks"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:91
 
#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
 
msgid "The deleted repository will be moved away and hidden until the administrator expires it. The administrator can both permanently delete it or restore it."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_caches.html:4
 
msgid "Invalidate Repository Cache"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_caches.html:4
 
msgid "Confirm to invalidate repository cache."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_caches.html:7
 
msgid "Manually invalidate cache for this repository. On first access, the repository will be cached again."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_caches.html:12
 
msgid "List of Cached Values"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_caches.html:15
 
msgid "Prefix"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_caches.html:16
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 
msgid "Key"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_caches.html:17
 
#: kallithea/templates/admin/user_groups/user_group_add.html:49
 
#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
 
#: kallithea/templates/admin/user_groups/user_groups.html:49
 
#: kallithea/templates/admin/users/user_add.html:88
 
#: kallithea/templates/admin/users/user_edit_profile.html:105
 
#: kallithea/templates/admin/users/users.html:54
 
msgid "Active"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:5
 
msgid "Label"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:19
 
#, python-format
 
msgid "Confirm to delete this field: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:33
 
msgid "New field key"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:41
 
msgid "New field label"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:44
 
msgid "Enter short label"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:50
 
msgid "New field description"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:53
 
msgid "Enter description of a field"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_fields.html:66
 
msgid "Extra fields are disabled."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
 
msgid "private repository"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_remote.html:3
 
msgid "Remote URL"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_remote.html:8
 
msgid "Pull Changes from Remote Location"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_remote.html:8
 
msgid "Confirm to pull changes from remote side."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_remote.html:14
 
msgid "This repository does not have a remote URL set."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:11
 
msgid "Non-changeable id"
 
msgid "Permanent Repository ID"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:11
 
msgid "What is that?"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:13
 
msgid "URL by id"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:14
 
msgid ""
 
"In case this repository is renamed or moved into another group the repository URL changes.\n"
 
"                               Using the above URL guarantees that this repository will always be accessible under such URL.\n"
 
"                               Useful for CI systems, or any other cases that you need to hardcode the URL into 3rd party service."
 
"                               Using the above permanent URL guarantees that this repository always will be accessible on that URL.\n"
 
"                               This is useful for CI systems, or any other cases that you need to hardcode the URL into a 3rd party service."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:21
 
#: kallithea/templates/summary/summary.html:72
 
msgid "Clone URL"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:27
 
#: kallithea/templates/base/perms_summary.html:43
 
#: kallithea/templates/base/perms_summary.html:79
 
#: kallithea/templates/base/perms_summary.html:81
 
#: kallithea/templates/data_table/_dt_elements.html:124
 
#: kallithea/templates/data_table/_dt_elements.html:125
 
#: kallithea/templates/data_table/_dt_elements.html:152
 
#: kallithea/templates/data_table/_dt_elements.html:153
 
#: kallithea/templates/data_table/_dt_elements.html:169
 
#: kallithea/templates/data_table/_dt_elements.html:185
 
#: kallithea/templates/data_table/_dt_elements.html:122
 
#: kallithea/templates/data_table/_dt_elements.html:123
 
#: kallithea/templates/data_table/_dt_elements.html:150
 
#: kallithea/templates/data_table/_dt_elements.html:151
 
#: kallithea/templates/data_table/_dt_elements.html:167
 
#: kallithea/templates/data_table/_dt_elements.html:183
 
msgid "edit"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:30
 
msgid "new value"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:37
 
msgid "URL used for doing remote pulls."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:55
 
msgid "Default revision for files page, downloads, whoosh and readme"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_settings.html:65
 
msgid "Change owner of this repository."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
 
msgid "Processed commits"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
 
msgid "Processed progress"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_statistics.html:10
 
msgid "Reset Statistics"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repo_edit_statistics.html:10
 
msgid "Confirm to remove current statistics."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repos.html:5
 
msgid "Repositories Administration"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/repos/repos.html:51
 
msgid "State"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings.html:5
 
msgid "Settings Administration"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings.html:27
 
msgid "VCS"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings.html:28
 
msgid "Remap and Rescan"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings.html:30
 
msgid "Visual"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings.html:32
 
#: kallithea/templates/admin/settings/settings_vcs.html:19
 
msgid "Hooks"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings.html:33
 
msgid "Full Text Search"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings.html:34
 
msgid "System Info"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_email.html:4
 
msgid "Email prefix"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_email.html:5
 
msgid "Kallithea email from"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_email.html:6
 
msgid "Error email from"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_email.html:7
 
msgid "Error email recipients"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_email.html:9
 
msgid "SMTP server"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_email.html:10
 
msgid "SMTP username"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_email.html:11
 
msgid "SMTP password"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_email.html:12
 
msgid "SMTP port"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_email.html:14
 
msgid "SMTP use TLS"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_email.html:15
 
msgid "SMTP use SSL"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_email.html:16
 
msgid "SMTP auth"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_email.html:31
 
msgid "Send test email to"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_email.html:39
 
msgid "Send"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_global.html:8
 
msgid "Site branding"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_global.html:12
 
msgid "Set a custom title for your Kallithea Service."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_global.html:18
 
msgid "HTTP authentication realm"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_global.html:27
 
msgid "Analytics HTML block"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_global.html:31
 
msgid "HTML with JavaScript for web analytics systems like Google Analytics or Piwik. This will be added at the bottom of every page."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_global.html:37
 
msgid "ReCaptcha public key"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_global.html:41
 
msgid "Public key for reCaptcha system."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_global.html:47
 
msgid "ReCaptcha private key"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_global.html:51
 
msgid "Private key for reCaptcha system. Setting this value will enable captcha on registration."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_global.html:56
 
#: kallithea/templates/admin/settings/settings_vcs.html:80
 
#: kallithea/templates/admin/settings/settings_visual.html:116
 
msgid "Save Settings"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_hooks.html:1
 
msgid "Built-in Mercurial Hooks (Read-Only)"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_hooks.html:15
 
msgid "Hooks can be used to trigger actions on certain events such as push / pull. They can trigger Python functions or external applications."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_hooks.html:19
 
msgid "Custom Hooks"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_hooks.html:68
 
msgid "Failed to remove hook"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_mapping.html:6
 
msgid "Rescan option"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_mapping.html:11
 
msgid "Destroy old data"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_mapping.html:13
 
msgid "Check this option to remove references to repositories that no longer exist in on the filesystem."
 
@@ -3768,1944 +3759,1947 @@ msgstr ""
 
#: kallithea/templates/admin/settings/settings_system.html:22
 
msgid "Python Packages"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:6
 
msgid "Web"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:11
 
msgid "Require SSL for vcs operations"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:13
 
msgid "Activate to require SSL both pushing and pulling. If SSL certificate is missing, it will return an HTTP Error 406: Not Acceptable."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:24
 
msgid "Show repository size after push"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:28
 
msgid "Log user push commands"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:32
 
msgid "Log user pull commands"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:36
 
msgid "Update repository after push (hg update)"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:42
 
msgid "Mercurial extensions"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:47
 
msgid "Enable largefiles extension"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:51
 
msgid "Enable hgsubversion extension"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:53
 
msgid "Requires hgsubversion library to be installed. Enables cloning of remote Subversion repositories while converting them to Mercurial."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:64
 
msgid "Location of repositories"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:69
 
msgid "Click to unlock. You must restart Kallithea in order to make this setting take effect."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_vcs.html:72
 
msgid "Filesystem location where repositories are stored. After changing this value, a restart and rescan of the repository folder are both required."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:8
 
msgid "General"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:13
 
msgid "Use repository extra fields"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:15
 
msgid "Allows storing additional customized fields per repository."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:18
 
msgid "Show Kallithea version"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:20
 
msgid "Shows or hides a version number of Kallithea displayed in the footer."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:24
 
msgid "Use Gravatars in Kallithea"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:30
 
msgid ""
 
"Gravatar URL allows you to use another avatar server application.\n"
 
"                                                        The following variables of the URL will be replaced accordingly.\n"
 
"                                                        {scheme}    'http' or 'https' sent from running Kallithea server,\n"
 
"                                                        {email}     user email,\n"
 
"                                                        {md5email}  md5 hash of the user email (like at gravatar.com),\n"
 
"                                                        {size}      size of the image that is expected from the server application,\n"
 
"                                                        {netloc}    network location/server host of running Kallithea server"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:42
 
msgid ""
 
"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'.\n"
 
"                                                        The following variables are available:\n"
 
"                                                        {scheme} 'http' or 'https' sent from running Kallithea server,\n"
 
"                                                        {user}   current user username,\n"
 
"                                                        {netloc} network location/server host of running Kallithea server,\n"
 
"                                                        {repo}   full repository name,\n"
 
"                                                        {repoid} ID of repository, can be used to contruct clone-by-id"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:55
 
msgid "Dashboard items"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:59
 
msgid "Number of items displayed in the main page dashboard before pagination is shown."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:65
 
msgid "Admin pages items"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:69
 
msgid "Number of items displayed in the admin pages grids before pagination is shown."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:75
 
msgid "Icons"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:80
 
msgid "Show public repo icon on repositories"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:84
 
msgid "Show private repo icon on repositories"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:86
 
msgid "Show public/private icons next to repository names."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:92
 
msgid "Meta-Tagging"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:97
 
msgid "Stylify recognised meta tags:"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/settings/settings_visual.html:111
 
msgid "Parses meta tags from the repository description field and turns them into colored tags."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_group_add.html:5
 
msgid "Add user group"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_group_add.html:10
 
#: kallithea/templates/admin/user_groups/user_group_edit.html:11
 
#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
 
msgid "User Groups"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_group_add.html:12
 
#: kallithea/templates/admin/user_groups/user_groups.html:25
 
msgid "Add User Group"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_group_add.html:44
 
#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
 
msgid "Short, optional description for this user group."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_group_edit.html:5
 
#, python-format
 
msgid "%s user group settings"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_group_edit.html:31
 
msgid "Default permissions"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_group_edit.html:33
 
#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
 
#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
 
#: kallithea/templates/admin/user_groups/user_groups.html:48
 
msgid "Members"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:1
 
#, python-format
 
msgid "User Group: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
 
#: kallithea/templates/data_table/_dt_elements.html:176
 
#: kallithea/templates/data_table/_dt_elements.html:174
 
#, python-format
 
msgid "Confirm to delete this user group: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:21
 
msgid "Delete this user group"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
 
msgid "No members yet"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
 
msgid "Chosen group members"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
 
msgid "Available members"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_groups.html:5
 
msgid "User Groups Administration"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/user_groups/user_groups.html:10
 
msgid "user groups"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_add.html:5
 
msgid "Add user"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_add.html:10
 
#: kallithea/templates/admin/users/user_edit.html:11
 
#: kallithea/templates/admin/users/users.html:10
 
#: kallithea/templates/base/base.html:62
 
msgid "Users"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_add.html:12
 
#: kallithea/templates/admin/users/users.html:24
 
msgid "Add User"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_add.html:50
 
msgid "Password confirmation"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit.html:5
 
#, python-format
 
msgid "%s user settings"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit.html:32
 
msgid "Default Permissions"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit.html:33
 
msgid "Emails"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit_advanced.html:1
 
#, python-format
 
msgid "User: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit_advanced.html:7
 
#: kallithea/templates/admin/users/user_edit_profile.html:51
 
msgid "Source of Record"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit_advanced.html:9
 
#: kallithea/templates/admin/users/users.html:53
 
msgid "Last Login"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit_advanced.html:10
 
msgid "Member of User groups"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit_advanced.html:21
 
#: kallithea/templates/data_table/_dt_elements.html:160
 
#: kallithea/templates/data_table/_dt_elements.html:158
 
#, python-format
 
msgid "Confirm to delete this user: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit_advanced.html:23
 
msgid "Delete this user"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit_ips.html:8
 
#, python-format
 
msgid "Inherited from %s"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit_profile.html:8
 
msgid "Change avatar at"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit_profile.html:12
 
msgid "Missing email, please update this user email address."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit_profile.html:27
 
#, python-format
 
msgid "This user is in an external Source of Record (%s); some details cannot be managed here."
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit_profile.html:60
 
msgid "Name in Source of Record"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/user_edit_profile.html:78
 
msgid "New password confirmation"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/users.html:5
 
msgid "Users Administration"
 
msgstr ""
 

	
 
#: kallithea/templates/admin/users/users.html:56
 
msgid "Auth Type"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:18
 
#, python-format
 
msgid "Server instance: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:30
 
msgid "Support"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:122
 
#: kallithea/templates/base/base.html:90
 
msgid "Mercurial repository"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:93
 
msgid "Git repository"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:126
 
msgid "Create Fork"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:133
 
#: kallithea/templates/data_table/_dt_elements.html:11
 
#: kallithea/templates/data_table/_dt_elements.html:15
 
#: kallithea/templates/base/base.html:137
 
#: kallithea/templates/data_table/_dt_elements.html:13
 
#: kallithea/templates/data_table/_dt_elements.html:17
 
#: kallithea/templates/summary/summary.html:8
 
msgid "Summary"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:135
 
#: kallithea/templates/base/base.html:137
 
#: kallithea/templates/changelog/changelog.html:14
 
#: kallithea/templates/data_table/_dt_elements.html:19
 
#: kallithea/templates/data_table/_dt_elements.html:23
 
msgid "Changelog"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:139
 
#: kallithea/templates/data_table/_dt_elements.html:27
 
#: kallithea/templates/data_table/_dt_elements.html:31
 
#: kallithea/templates/base/base.html:141
 
#: kallithea/templates/changelog/changelog.html:14
 
#: kallithea/templates/data_table/_dt_elements.html:21
 
#: kallithea/templates/data_table/_dt_elements.html:25
 
msgid "Changelog"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:143
 
#: kallithea/templates/data_table/_dt_elements.html:29
 
#: kallithea/templates/data_table/_dt_elements.html:33
 
#: kallithea/templates/files/files.html:11
 
msgid "Files"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:141
 
#: kallithea/templates/base/base.html:145
 
msgid "Switch To"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:148
 
#: kallithea/templates/base/base.html:150
 
#: kallithea/templates/base/base.html:152
 
#: kallithea/templates/base/base.html:154
 
msgid "Options"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:158
 
#: kallithea/templates/base/base.html:162
 
#: kallithea/templates/forks/forks_data.html:21
 
msgid "Compare Fork"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:160
 
#: kallithea/templates/base/base.html:164
 
#: kallithea/templates/bookmarks/bookmarks.html:56
 
#: kallithea/templates/bookmarks/bookmarks_data.html:13
 
#: kallithea/templates/branches/branches.html:56
 
#: kallithea/templates/branches/branches_data.html:13
 
#: kallithea/templates/tags/tags.html:56
 
#: kallithea/templates/tags/tags_data.html:13
 
msgid "Compare"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:162
 
#: kallithea/templates/base/base.html:250
 
#: kallithea/templates/base/base.html:166
 
#: kallithea/templates/base/base.html:254
 
#: kallithea/templates/search/search.html:14
 
#: kallithea/templates/search/search.html:54
 
msgid "Search"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:166
 
#: kallithea/templates/base/base.html:170
 
msgid "Unlock"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:168
 
#: kallithea/templates/base/base.html:172
 
msgid "Lock"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:176
 
msgid "Follow"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:177
 
msgid "Unfollow"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:180
 
#: kallithea/templates/data_table/_dt_elements.html:35
 
#: kallithea/templates/data_table/_dt_elements.html:39
 
#: kallithea/templates/forks/fork.html:9
 
msgid "Fork"
 
msgid "Follow"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:181
 
msgid "Unfollow"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:184
 
#: kallithea/templates/data_table/_dt_elements.html:37
 
#: kallithea/templates/data_table/_dt_elements.html:41
 
#: kallithea/templates/forks/fork.html:9
 
msgid "Fork"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:185
 
#: kallithea/templates/pullrequests/pullrequest.html:88
 
msgid "Create Pull Request"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:186
 
#: kallithea/templates/base/base.html:190
 
#, python-format
 
msgid "Show Pull Requests for %s"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:224
 
#: kallithea/templates/base/base.html:228
 
msgid "Show recent activity"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:225
 
#: kallithea/templates/base/base.html:229
 
#: kallithea/templates/journal/journal.html:4
 
#: kallithea/templates/journal/journal.html:12
 
msgid "Journal"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:230
 
#: kallithea/templates/base/base.html:231
 
#: kallithea/templates/base/base.html:234
 
#: kallithea/templates/base/base.html:235
 
msgid "Public journal"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:236
 
#: kallithea/templates/base/base.html:240
 
msgid "Show public gists"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:237
 
msgid "Gists"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:241
 
msgid "Gists"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:245
 
msgid "All Public Gists"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:243
 
#: kallithea/templates/base/base.html:247
 
msgid "My Public Gists"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:244
 
#: kallithea/templates/base/base.html:248
 
msgid "My Private Gists"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:249
 
#: kallithea/templates/base/base.html:253
 
msgid "Search in repositories"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:272
 
#: kallithea/templates/base/base.html:273
 
#: kallithea/templates/base/base.html:276
 
#: kallithea/templates/base/base.html:277
 
#: kallithea/templates/pullrequests/pullrequest_show_my.html:4
 
#: kallithea/templates/pullrequests/pullrequest_show_my.html:8
 
msgid "My Pull Requests"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:292
 
#: kallithea/templates/base/base.html:296
 
msgid "Not Logged In"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:299
 
#: kallithea/templates/base/base.html:303
 
msgid "Login to Your Account"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:322
 
#: kallithea/templates/base/base.html:326
 
msgid "Forgot password ?"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:347
 
#: kallithea/templates/base/base.html:351
 
msgid "Log Out"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:395
 
#: kallithea/templates/base/base.html:399
 
msgid "No matches found"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:524
 
#: kallithea/templates/base/base.html:528
 
msgid "Keyboard shortcuts"
 
msgstr ""
 

	
 
#: kallithea/templates/base/base.html:533
 
#: kallithea/templates/base/base.html:537
 
msgid "Site-wide shortcuts"
 
msgstr ""
 

	
 
#: kallithea/templates/base/default_perms_box.html:14
 
msgid "Inherit from defaults"
 
msgstr ""
 

	
 
#: kallithea/templates/base/default_perms_box.html:19
 
#, python-format
 
msgid "Select to inherit permissions from %s permissions settings, and default IP address whitelist."
 
msgstr ""
 

	
 
#: kallithea/templates/base/default_perms_box.html:28
 
msgid "Create repositories"
 
msgstr ""
 

	
 
#: kallithea/templates/base/default_perms_box.html:33
 
msgid "Select this option to allow repository creation for this user"
 
msgstr ""
 

	
 
#: kallithea/templates/base/default_perms_box.html:40
 
msgid "Create user groups"
 
msgstr ""
 

	
 
#: kallithea/templates/base/default_perms_box.html:45
 
msgid "Select this option to allow user group creation for this user"
 
msgstr ""
 

	
 
#: kallithea/templates/base/default_perms_box.html:52
 
msgid "Fork repositories"
 
msgstr ""
 

	
 
#: kallithea/templates/base/default_perms_box.html:57
 
msgid "Select this option to allow repository forking for this user"
 
msgstr ""
 

	
 
#: kallithea/templates/base/perms_summary.html:13
 
msgid "show"
 
msgstr ""
 

	
 
#: kallithea/templates/base/perms_summary.html:22
 
msgid "No permissions defined yet"
 
msgstr ""
 

	
 
#: kallithea/templates/base/perms_summary.html:30
 
#: kallithea/templates/base/perms_summary.html:54
 
msgid "Permission"
 
msgstr ""
 

	
 
#: kallithea/templates/base/perms_summary.html:32
 
#: kallithea/templates/base/perms_summary.html:56
 
msgid "Edit Permission"
 
msgstr ""
 

	
 
#: kallithea/templates/base/perms_summary.html:90
 
msgid "No permission defined"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:22
 
msgid "Add Another Comment"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:23
 
#: kallithea/templates/data_table/_dt_elements.html:216
 
#: kallithea/templates/data_table/_dt_elements.html:214
 
msgid "Stop following this repository"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:24
 
msgid "Start following this repository"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:25
 
msgid "Group"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:26
 
msgid "members"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:27
 
msgid "Loading ..."
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:28
 
msgid "loading ..."
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:29
 
msgid "Search truncated"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:30
 
msgid "No matching files"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:31
 
#: kallithea/templates/pullrequests/pullrequest_show_all.html:30
 
msgid "Open New Pull Request"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:32
 
msgid "Open New Pull Request for Selected Changesets"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:33
 
msgid "Show Selected Changesets __S &rarr; __E"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:34
 
msgid "Show Selected Changeset __S"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:35
 
msgid "Selection Link"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:36
 
#: kallithea/templates/changeset/diff_block.html:8
 
msgid "Collapse Diff"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:37
 
msgid "Expand Diff"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:38
 
msgid "Failed to revoke permission"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:39
 
msgid "Confirm to revoke permission for {0}: {1} ?"
 
msgstr ""
 

	
 
#: kallithea/templates/base/root.html:43
 
msgid "Specify changeset"
 
msgstr ""
 

	
 
#: kallithea/templates/bookmarks/bookmarks.html:5
 
#, python-format
 
msgid "%s Bookmarks"
 
msgstr ""
 

	
 
#: kallithea/templates/bookmarks/bookmarks.html:26
 
msgid "Compare Bookmarks"
 
msgstr ""
 

	
 
#: kallithea/templates/bookmarks/bookmarks.html:53
 
#: kallithea/templates/bookmarks/bookmarks_data.html:10
 
#: kallithea/templates/branches/branches.html:53
 
#: kallithea/templates/branches/branches_data.html:10
 
#: kallithea/templates/changelog/changelog_summary_data.html:10
 
#: kallithea/templates/pullrequests/pullrequest_data.html:16
 
#: kallithea/templates/tags/tags.html:53
 
#: kallithea/templates/tags/tags_data.html:10
 
msgid "Author"
 
msgstr ""
 

	
 
#: kallithea/templates/bookmarks/bookmarks.html:54
 
#: kallithea/templates/bookmarks/bookmarks_data.html:12
 
#: kallithea/templates/branches/branches.html:54
 
#: kallithea/templates/branches/branches_data.html:12
 
#: kallithea/templates/changelog/changelog_summary_data.html:7
 
#: kallithea/templates/pullrequests/pullrequest.html:62
 
#: kallithea/templates/pullrequests/pullrequest.html:78
 
#: kallithea/templates/tags/tags.html:54
 
#: kallithea/templates/tags/tags_data.html:12
 
msgid "Revision"
 
msgstr ""
 

	
 
#: kallithea/templates/branches/branches.html:5
 
#, python-format
 
msgid "%s Branches"
 
msgstr ""
 

	
 
#: kallithea/templates/branches/branches.html:26
 
msgid "Compare Branches"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:6
 
#, python-format
 
msgid "%s Changelog"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:21
 
#, python-format
 
msgid "showing %d out of %d revision"
 
msgid_plural "showing %d out of %d revisions"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/templates/changelog/changelog.html:42
 
msgid "Show"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:52
 
msgid "Clear selection"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:55
 
msgid "Go to tip of repository"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:60
 
#: kallithea/templates/forks/forks_data.html:19
 
#, python-format
 
msgid "Compare fork with %s"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:62
 
#, python-format
 
msgid "Compare fork with parent repo (%s)"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:66
 
#: kallithea/templates/files/files.html:29
 
msgid "Branch filter:"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:92
 
#: kallithea/templates/changelog/changelog_summary_data.html:20
 
#, python-format
 
msgid ""
 
"Changeset status: %s\n"
 
"Click to open associated pull request #%s"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:96
 
#: kallithea/templates/compare/compare_cs.html:24
 
#, python-format
 
msgid "Changeset status: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:115
 
#: kallithea/templates/compare/compare_cs.html:48
 
msgid "Expand commit message"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:124
 
#: kallithea/templates/compare/compare_cs.html:30
 
msgid "Changeset has comments"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:134
 
#: kallithea/templates/changelog/changelog_summary_data.html:54
 
#: kallithea/templates/changeset/changeset.html:94
 
#: kallithea/templates/changeset/changeset_range.html:92
 
#, python-format
 
msgid "Bookmark %s"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:140
 
#: kallithea/templates/changelog/changelog_summary_data.html:60
 
#: kallithea/templates/changeset/changeset.html:101
 
#: kallithea/templates/changeset/changeset_range.html:98
 
#, python-format
 
msgid "Tag %s"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:145
 
#: kallithea/templates/changelog/changelog_summary_data.html:65
 
#: kallithea/templates/changeset/changeset.html:106
 
#: kallithea/templates/changeset/changeset_range.html:102
 
#, python-format
 
msgid "Branch %s"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog.html:290
 
msgid "There are no changes yet"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog_details.html:4
 
#: kallithea/templates/changeset/changeset.html:77
 
msgid "Removed"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog_details.html:5
 
#: kallithea/templates/changeset/changeset.html:78
 
msgid "Changed"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog_details.html:6
 
#: kallithea/templates/changeset/changeset.html:79
 
#: kallithea/templates/changeset/diff_block.html:80
 
msgid "Added"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog_details.html:8
 
#: kallithea/templates/changelog/changelog_details.html:9
 
#: kallithea/templates/changelog/changelog_details.html:10
 
#: kallithea/templates/changeset/changeset.html:81
 
#: kallithea/templates/changeset/changeset.html:82
 
#: kallithea/templates/changeset/changeset.html:83
 
#, python-format
 
msgid "Affected %s files"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog_summary_data.html:8
 
#: kallithea/templates/files/files_add.html:60
 
#: kallithea/templates/files/files_delete.html:39
 
#: kallithea/templates/files/files_edit.html:63
 
msgid "Commit Message"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog_summary_data.html:9
 
#: kallithea/templates/pullrequests/pullrequest_data.html:17
 
msgid "Age"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog_summary_data.html:11
 
msgid "Refs"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog_summary_data.html:91
 
#: kallithea/templates/changelog/changelog_summary_data.html:81
 
msgid "Add or upload files directly via Kallithea"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog_summary_data.html:94
 
#: kallithea/templates/changelog/changelog_summary_data.html:84
 
#: kallithea/templates/files/files_add.html:21
 
#: kallithea/templates/files/files_ypjax.html:9
 
msgid "Add New File"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog_summary_data.html:100
 
#: kallithea/templates/changelog/changelog_summary_data.html:90
 
msgid "Push new repo"
 
msgstr ""
 

	
 
#: kallithea/templates/changelog/changelog_summary_data.html:108
 
#: kallithea/templates/changelog/changelog_summary_data.html:98
 
msgid "Existing repository?"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset.html:8
 
#, python-format
 
msgid "%s Changeset"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset.html:36
 
msgid "parent rev."
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset.html:42
 
msgid "child rev."
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset.html:50
 
#: kallithea/templates/changeset/changeset_file_comment.html:43
 
#: kallithea/templates/changeset/changeset_range.html:48
 
msgid "Changeset status"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset.html:54
 
#: kallithea/templates/changeset/diff_block.html:27
 
#: kallithea/templates/files/diff_2way.html:49
 
msgid "Raw diff"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset.html:57
 
msgid "Patch diff"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset.html:60
 
#: kallithea/templates/changeset/diff_block.html:30
 
#: kallithea/templates/files/diff_2way.html:52
 
msgid "Download diff"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset.html:89
 
#: kallithea/templates/changeset/changeset_range.html:88
 
msgid "merge"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset.html:123
 
msgid "Grafted from:"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset.html:129
 
msgid "Transplanted from:"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset.html:137
 
#: kallithea/templates/compare/compare_diff.html:54
 
#: kallithea/templates/pullrequests/pullrequest_show.html:307
 
#, python-format
 
msgid "%s file changed"
 
msgid_plural "%s files changed"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/templates/changeset/changeset.html:139
 
#: kallithea/templates/compare/compare_diff.html:56
 
#: kallithea/templates/pullrequests/pullrequest_show.html:309
 
#, python-format
 
msgid "%s file changed with %s insertions and %s deletions"
 
msgid_plural "%s files changed with %s insertions and %s deletions"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/templates/changeset/changeset.html:153
 
#: kallithea/templates/changeset/changeset.html:166
 
#: kallithea/templates/pullrequests/pullrequest_show.html:328
 
#: kallithea/templates/pullrequests/pullrequest_show.html:351
 
msgid "Show full diff anyway"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset.html:224
 
#: kallithea/templates/changeset/changeset.html:261
 
msgid "no revisions"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:24
 
msgid "Status change from pull request"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:25
 
#: kallithea/templates/changeset/changeset_file_comment.html:28
 
msgid "No title"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:27
 
msgid "Comment from pull request"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:32
 
msgid "Status change on changeset"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:34
 
msgid "Comment on changeset"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:50
 
msgid "Delete comment?"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:67
 
msgid "Commenting on line {1}."
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:68
 
#: kallithea/templates/changeset/changeset_file_comment.html:163
 
#, python-format
 
msgid "Comments parsed using %s syntax with %s support."
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:70
 
#: kallithea/templates/changeset/changeset_file_comment.html:165
 
msgid "Use @username inside this text to notify another user"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:80
 
#: kallithea/templates/changeset/changeset_file_comment.html:199
 
msgid "Comment preview"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:85
 
msgid "Submitting ..."
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:88
 
#: kallithea/templates/changeset/changeset_file_comment.html:205
 
msgid "Comment"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:90
 
#: kallithea/templates/changeset/changeset_file_comment.html:206
 
msgid "Preview"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:98
 
msgid "You need to be logged in to comment."
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:98
 
msgid "Login now"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:102
 
msgid "Hide"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:114
 
#, python-format
 
msgid "%d comment"
 
msgid_plural "%d comments"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:115
 
#, python-format
 
msgid "%d inline"
 
msgid_plural "%d inline"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:116
 
#, python-format
 
msgid "%d general"
 
msgid_plural "%d general"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:165
 
msgid "Use @username inside this text to notify another user."
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:172
 
msgid "Vote for pull request status"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:174
 
msgid "Set changeset status"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:178
 
msgid "No change"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_file_comment.html:191
 
msgid "Close"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_range.html:5
 
#, python-format
 
msgid "%s Changesets"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/changeset_range.html:56
 
msgid "Files affected"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/diff_block.html:21
 
#: kallithea/templates/files/diff_2way.html:43
 
msgid "Show full diff for this file"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/diff_block.html:24
 
#: kallithea/templates/changeset/diff_block.html:99
 
#: kallithea/templates/files/diff_2way.html:46
 
msgid "Show full side-by-side diff for this file"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/diff_block.html:38
 
msgid "Show inline comments"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/diff_block.html:87
 
msgid "Deleted"
 
msgstr ""
 

	
 
#: kallithea/templates/changeset/diff_block.html:90
 
msgid "Renamed"
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_cs.html:4
 
msgid "No changesets"
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_cs.html:8
 
msgid "Ancestor"
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_cs.html:61
 
msgid "Show merge diff"
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_cs.html:71
 
#: kallithea/templates/pullrequests/pullrequest_show.html:299
 
msgid "Common ancestor"
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_cs.html:75
 
msgid "No common ancestor found - repositories are unrelated"
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_cs.html:83
 
msgid "is"
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_cs.html:84
 
#, python-format
 
msgid "%s changesets"
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_cs.html:85
 
msgid "behind"
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_diff.html:6
 
#: kallithea/templates/compare/compare_diff.html:8
 
#, python-format
 
msgid "%s Compare"
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_diff.html:13
 
#: kallithea/templates/compare/compare_diff.html:35
 
msgid "Compare Revisions"
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_diff.html:33
 
msgid "Swap"
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_diff.html:42
 
msgid "Compare revisions, branches, bookmarks, or tags."
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_diff.html:47
 
#: kallithea/templates/pullrequests/pullrequest_show.html:294
 
#, python-format
 
msgid "Showing %s commit"
 
msgid_plural "Showing %s commits"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/templates/compare/compare_diff.html:65
 
#: kallithea/templates/pullrequests/pullrequest_show.html:315
 
msgid "No files"
 
msgstr ""
 

	
 
#: kallithea/templates/compare/compare_diff.html:78
 
#: kallithea/templates/compare/compare_diff.html:89
 
msgid "Show full diff"
 
msgstr ""
 

	
 
#: kallithea/templates/data_table/_dt_elements.html:67
 
msgid "Mercurial repository"
 
msgstr ""
 

	
 
#: kallithea/templates/data_table/_dt_elements.html:69
 
msgid "Git repository"
 
msgstr ""
 

	
 
#: kallithea/templates/data_table/_dt_elements.html:76
 
#: kallithea/templates/data_table/_dt_elements.html:74
 
msgid "Public repository"
 
msgstr ""
 

	
 
#: kallithea/templates/data_table/_dt_elements.html:86
 
#: kallithea/templates/data_table/_dt_elements.html:84
 
msgid "Repository creation in progress..."
 
msgstr ""
 

	
 
#: kallithea/templates/data_table/_dt_elements.html:100
 
#: kallithea/templates/data_table/_dt_elements.html:98
 
msgid "No changesets yet"
 
msgstr ""
 

	
 
#: kallithea/templates/data_table/_dt_elements.html:105
 
#: kallithea/templates/data_table/_dt_elements.html:107
 
#: kallithea/templates/data_table/_dt_elements.html:109
 
#, python-format
 
msgid "Subscribe to %s rss feed"
 
msgstr ""
 

	
 
#: kallithea/templates/data_table/_dt_elements.html:113
 
#: kallithea/templates/data_table/_dt_elements.html:115
 
#: kallithea/templates/data_table/_dt_elements.html:117
 
#, python-format
 
msgid "Subscribe to %s atom feed"
 
msgstr ""
 

	
 
#: kallithea/templates/data_table/_dt_elements.html:141
 
#: kallithea/templates/data_table/_dt_elements.html:139
 
msgid "Creating"
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/changeset_comment.html:5
 
#, python-format
 
msgid "Comment from %s on %s changeset %s mentioned you"
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/changeset_comment.html:7
 
#, python-format
 
msgid "Comment from %s on %s changeset %s"
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/changeset_comment.html:12
 
msgid "The changeset status was changed to"
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/main.html:6
 
msgid "This is an automatic notification. Don't reply to this mail."
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/password_reset.html:4
 
#, python-format
 
msgid "Hello %s"
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/password_reset.html:6
 
msgid "We received a request to create a new password for your account."
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/password_reset.html:7
 
msgid "You can generate it by clicking following URL"
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/password_reset.html:10
 
msgid "Please ignore this email if you did not request a new password ."
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/pull_request.html:5
 
#, python-format
 
msgid "%s mentioned you on %s pull request \"%s\""
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/pull_request.html:7
 
#, python-format
 
msgid "%s requested your review of %s pull request \"%s\""
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/pull_request_comment.html:4
 
#, python-format
 
msgid "Comment from %s on %s pull request \"%s\""
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/pull_request_comment.html:9
 
msgid "The comment closed the pull request with status"
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/pull_request_comment.html:11
 
msgid "The comment was made with status"
 
msgstr ""
 

	
 
#: kallithea/templates/email_templates/registration.html:6
 
msgid "View this user here"
 
msgstr ""
 

	
 
#: kallithea/templates/files/diff_2way.html:15
 
#, python-format
 
msgid "%s File side-by-side diff"
 
msgstr ""
 

	
 
#: kallithea/templates/files/diff_2way.html:19
 
#: kallithea/templates/files/file_diff.html:8
 
msgid "File diff"
 
msgstr ""
 

	
 
#: kallithea/templates/files/file_diff.html:4
 
#, python-format
 
msgid "%s File Diff"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files.html:4
 
#: kallithea/templates/files/files.html:80
 
#, python-format
 
msgid "%s Files"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_add.html:4
 
#, python-format
 
msgid "%s Files Add"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_add.html:40
 
#: kallithea/templates/files/files_edit.html:38
 
#: kallithea/templates/files/files_ypjax.html:3
 
msgid "Location"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_add.html:42
 
msgid "Enter filename..."
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_add.html:44
 
#: kallithea/templates/files/files_add.html:48
 
msgid "or"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_add.html:44
 
msgid "Upload File"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_add.html:48
 
msgid "Create New File"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_add.html:53
 
msgid "New file mode"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_add.html:64
 
#: kallithea/templates/files/files_delete.html:43
 
#: kallithea/templates/files/files_edit.html:67
 
msgid "Commit Changes"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_browser.html:32
 
msgid "revision"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_browser.html:33
 
msgid "Previous revision"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_browser.html:35
 
msgid "Next revision"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_browser.html:41
 
msgid "Follow current branch"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_browser.html:44
 
msgid "Search File List"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_browser.html:48
 
msgid "Loading file list..."
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_browser.html:61
 
msgid "Size"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_browser.html:62
 
msgid "Mimetype"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_browser.html:63
 
msgid "Last Revision"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_browser.html:64
 
msgid "Last Modified"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_browser.html:65
 
msgid "Last Committer"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_delete.html:4
 
#, python-format
 
msgid "%s Files Delete"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_delete.html:12
 
#: kallithea/templates/files/files_delete.html:31
 
msgid "Delete file"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_edit.html:4
 
#, python-format
 
msgid "%s File Edit"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_edit.html:21
 
msgid "Edit file"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_edit.html:48
 
#: kallithea/templates/files/files_source.html:32
 
msgid "Show Annotation"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_edit.html:50
 
#: kallithea/templates/files/files_source.html:35
 
msgid "Download as Raw"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_edit.html:53
 
msgid "Source"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_edit.html:58
 
msgid "Editing file"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_history_box.html:2
 
#, python-format
 
msgid "%s author"
 
msgid_plural "%s authors"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/templates/files/files_source.html:7
 
msgid "Diff to Revision"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_source.html:8
 
msgid "Show at Revision"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_source.html:10
 
msgid "Show Full History"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_source.html:11
 
msgid "Show Authors"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_source.html:30
 
msgid "Show Source"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_source.html:38
 
#, python-format
 
msgid "Edit on Branch:%s"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_source.html:41
 
msgid "Editing binary files not allowed"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_source.html:44
 
msgid "Editing files allowed only when on branch head revision"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_source.html:45
 
msgid "Deleting files allowed only when on branch head revision"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_source.html:63
 
#, python-format
 
msgid "Binary file (%s)"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_source.html:73
 
msgid "File is too big to display"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_ypjax.html:5
 
msgid "annotation"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_ypjax.html:23
 
msgid "Go Back"
 
msgstr ""
 

	
 
#: kallithea/templates/files/files_ypjax.html:24
 
msgid "No files at given path"
 
msgstr ""
 

	
 
#: kallithea/templates/followers/followers.html:5
 
#, python-format
 
msgid "%s Followers"
 
msgstr ""
 

	
 
#: kallithea/templates/followers/followers.html:9
 
#: kallithea/templates/summary/summary.html:145
 
#: kallithea/templates/summary/summary.html:146
 
#: kallithea/templates/summary/summary.html:147
 
msgid "Followers"
 
msgstr ""
 

	
 
#: kallithea/templates/followers/followers_data.html:12
 
msgid "Started following -"
 
msgstr ""
 

	
 
#: kallithea/templates/forks/fork.html:5
 
#, python-format
 
msgid "Fork repository %s"
 
msgstr ""
 

	
 
#: kallithea/templates/forks/fork.html:27
 
msgid "Fork name"
 
msgstr ""
 

	
 
#: kallithea/templates/forks/fork.html:62
 
msgid "Default revision for files page, downloads, whoosh, and readme."
 
msgstr ""
 

	
 
#: kallithea/templates/forks/fork.html:68
 
msgid "Private"
 
msgstr ""
 

	
 
#: kallithea/templates/forks/fork.html:77
 
msgid "Copy permissions"
 
msgstr ""
 

	
 
#: kallithea/templates/forks/fork.html:81
 
msgid "Copy permissions from forked repository"
 
msgstr ""
 

	
 
#: kallithea/templates/forks/fork.html:87
 
msgid "Update after clone"
 
msgstr ""
 

	
 
#: kallithea/templates/forks/fork.html:91
 
msgid "Checkout source after making a clone"
 
msgstr ""
 

	
 
#: kallithea/templates/forks/fork.html:96
 
msgid "Fork this Repository"
 
msgstr ""
 

	
 
#: kallithea/templates/forks/forks.html:5
 
#, python-format
 
msgid "%s Forks"
 
msgstr ""
 

	
 
#: kallithea/templates/forks/forks.html:9
 
#: kallithea/templates/summary/summary.html:151
 
#: kallithea/templates/summary/summary.html:152
 
#: kallithea/templates/summary/summary.html:153
 
msgid "Forks"
 
msgstr ""
 

	
 
#: kallithea/templates/forks/forks_data.html:17
 
msgid "Forked"
 
msgstr ""
 

	
 
#: kallithea/templates/forks/forks_data.html:43
 
#: kallithea/templates/forks/forks_data.html:30
 
msgid "There are no forks yet"
 
msgstr ""
 

	
 
#: kallithea/templates/journal/journal.html:21
 
msgid "ATOM journal feed"
 
msgstr ""
 

	
 
#: kallithea/templates/journal/journal.html:22
 
msgid "RSS journal feed"
 
msgstr ""
 

	
 
#: kallithea/templates/journal/journal.html:56
 
msgid "My Repos"
 
msgstr ""
 

	
 
#: kallithea/templates/journal/journal_data.html:61
 
#: kallithea/templates/journal/journal_data.html:43
 
msgid "No entries yet"
 
msgstr ""
 

	
 
#: kallithea/templates/journal/public_journal.html:4
 
#: kallithea/templates/journal/public_journal.html:21
 
msgid "Public Journal"
 
msgstr ""
 

	
 
#: kallithea/templates/journal/public_journal.html:13
 
msgid "ATOM public journal feed"
 
msgstr ""
 

	
 
#: kallithea/templates/journal/public_journal.html:14
 
msgid "RSS public journal feed"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest.html:4
 
#: kallithea/templates/pullrequests/pullrequest.html:8
 
msgid "New Pull Request"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest.html:31
 
#: kallithea/templates/pullrequests/pullrequest_data.html:15
 
#: kallithea/templates/pullrequests/pullrequest_show.html:29
 
#: kallithea/templates/pullrequests/pullrequest_show.html:54
 
msgid "Title"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest.html:34
 
msgid "Summarize the changes - or leave empty"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest.html:43
 
#: kallithea/templates/pullrequests/pullrequest_show.html:66
 
msgid "Write a short description on this pull request"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest.html:49
 
msgid "Changeset flow"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest.html:56
 
msgid "Origin repository"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest.html:72
 
msgid "Destination repository"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest.html:97
 
#: kallithea/templates/pullrequests/pullrequest_show.html:210
 
msgid "Pull Request Reviewers"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest.html:107
 
#: kallithea/templates/pullrequests/pullrequest_show.html:239
 
msgid "Type name of reviewer to add"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_data.html:6
 
msgid "No entries"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_data.html:18
 
msgid "From"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_data.html:19
 
msgid "To"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_data.html:27
 
#, python-format
 
msgid "Latest vote: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_data.html:29
 
msgid "Nobody voted"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_data.html:34
 
#, python-format
 
msgid "You voted: %s"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_data.html:36
 
msgid "You didn't vote"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_data.html:45
 
msgid "Delete Pull Request"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_data.html:46
 
msgid "Confirm to delete this pull request"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_data.html:54
 
msgid "(no title)"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_data.html:56
 
#: kallithea/templates/pullrequests/pullrequest_show.html:31
 
#: kallithea/templates/pullrequests/pullrequest_show.html:83
 
msgid "Closed"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:6
 
#, python-format
 
msgid "%s Pull Request #%s"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:10
 
#, python-format
 
msgid "Pull request #%s from %s#%s"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:57
 
msgid "Summarize the changes"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:74
 
msgid "Reviewer voting result"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:80
 
#: kallithea/templates/pullrequests/pullrequest_show.html:81
 
msgid "Pull request status calculated from votes"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:94
 
msgid "Still not reviewed by"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:98
 
#, python-format
 
msgid "%d reviewer"
 
msgid_plural "%d reviewers"
 
msgstr[0] ""
 
msgstr[1] ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:100
 
msgid "Pull request was reviewed by all reviewers"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:102
 
msgid "There are no reviewers"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:108
 
msgid "Origin"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:114
 
msgid "on"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:121
 
msgid "Target"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:132
 
msgid "Pull changes"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:155
 
msgid "Created by"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:170
 
msgid "Update"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:188
 
msgid "Current revision - no change"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:224
 
msgid "owner"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:224
 
msgid "reviewer"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:227
 
msgid "Remove reviewer"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:247
 
msgid "Potential Reviewers"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:250
 
msgid "Click to add the repository owner as reviewer:"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:273
 
msgid "Save Changes"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:274
 
msgid "Save as New Pull Request"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:275
 
msgid "Cancel Changes"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show.html:285
 
msgid "Pull Request Content"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show_all.html:4
 
#, python-format
 
msgid "%s Pull Requests"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show_all.html:9
 
#, python-format
 
msgid "Pull Requests from %s'"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show_all.html:11
 
#, python-format
 
msgid "Pull Requests to '%s'"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show_all.html:35
 
#, python-format
 
msgid "Show Pull Requests to %s"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
 
#, python-format
 
msgid "Show Pull Requests from '%s'"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show_all.html:47
 
#: kallithea/templates/pullrequests/pullrequest_show_my.html:26
 
msgid "Hide closed pull requests (only show open pull requests)"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
 
#: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 
msgid "Show closed pull requests (in addition to open pull requests)"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show_my_data.html:3
 
msgid "Pull Requests Created by Me"
 
msgstr ""
 

	
 
#: kallithea/templates/pullrequests/pullrequest_show_my_data.html:6
 
msgid "Pull Requests I Participate In"
 
msgstr ""
 

	
 
#: kallithea/templates/search/search.html:6
 
#, python-format
 
msgid "%s Search"
 
msgstr ""
 

	
 
#: kallithea/templates/search/search.html:8
 
#: kallithea/templates/search/search.html:16
 
msgid "Search in All Repositories"
 
msgstr ""
 

	
 
#: kallithea/templates/search/search.html:50
 
msgid "Search term"
 
msgstr ""
 

	
 
#: kallithea/templates/search/search.html:62
 
msgid "Search in"
 
msgstr ""
 

	
 
#: kallithea/templates/search/search.html:65
 
msgid "File contents"
 
msgstr ""
 

	
 
#: kallithea/templates/search/search.html:66
 
msgid "Commit messages"
 
msgstr ""
 

	
 
#: kallithea/templates/search/search.html:67
 
msgid "File names"
 
msgstr ""
 

	
 
#: kallithea/templates/search/search_commit.html:35
 
#: kallithea/templates/search/search_content.html:21
 
#: kallithea/templates/search/search_path.html:15
 
msgid "Permission denied"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:4
 
#, python-format
 
msgid "%s Statistics"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:16
 
#: kallithea/templates/summary/summary.html:39
 
#, python-format
 
msgid "%s ATOM feed"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:17
 
#: kallithea/templates/summary/summary.html:40
 
#, python-format
 
msgid "%s RSS feed"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:36
 
#: kallithea/templates/summary/summary.html:103
 
#: kallithea/templates/summary/summary.html:119
 
#: kallithea/templates/summary/summary.html:104
 
#: kallithea/templates/summary/summary.html:120
 
msgid "Enable"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:39
 
msgid "Stats gathered: "
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:88
 
#: kallithea/templates/summary/summary.html:352
 
#: kallithea/templates/summary/statistics.html:89
 
#: kallithea/templates/summary/summary.html:353
 
msgid "files"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:112
 
#: kallithea/templates/summary/summary.html:376
 
#: kallithea/templates/summary/statistics.html:113
 
#: kallithea/templates/summary/summary.html:377
 
msgid "Show more"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:389
 
msgid "commits"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:390
 
msgid "files added"
 
msgid "commits"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:391
 
msgid "files changed"
 
msgid "files added"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:392
 
msgid "files changed"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:393
 
msgid "files removed"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:394
 
msgid "commit"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:395
 
msgid "file added"
 
msgid "commit"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:396
 
msgid "file changed"
 
msgid "file added"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:397
 
msgid "file changed"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/statistics.html:398
 
msgid "file removed"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:4
 
#, python-format
 
msgid "%s Summary"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:13
 
#, python-format
 
msgid "Repository locked by %s"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:15
 
msgid "Repository unlocked"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:22
 
msgid "Fork of"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:77
 
#: kallithea/templates/summary/summary.html:78
 
msgid "Show by Name"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:78
 
#: kallithea/templates/summary/summary.html:79
 
msgid "Show by ID"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:95
 
#: kallithea/templates/summary/summary.html:96
 
msgid "Trending files"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:111
 
#: kallithea/templates/summary/summary.html:112
 
msgid "Download"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:115
 
#: kallithea/templates/summary/summary.html:116
 
msgid "There are no downloads yet"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:117
 
#: kallithea/templates/summary/summary.html:118
 
msgid "Downloads are disabled for this repository"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:123
 
#: kallithea/templates/summary/summary.html:124
 
msgid "Download as zip"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:128
 
#: kallithea/templates/summary/summary.html:129
 
msgid "Check this to download archive with subrepos"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:128
 
#: kallithea/templates/summary/summary.html:129
 
msgid "with subrepos"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:159
 
#: kallithea/templates/summary/summary.html:160
 
msgid "Repository Size"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:166
 
#: kallithea/templates/summary/summary.html:168
 
#: kallithea/templates/summary/summary.html:167
 
#: kallithea/templates/summary/summary.html:169
 
msgid "Feed"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:189
 
#: kallithea/templates/summary/summary.html:190
 
msgid "Latest Changes"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:191
 
#: kallithea/templates/summary/summary.html:192
 
msgid "Quick Start"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:205
 
#: kallithea/templates/summary/summary.html:206
 
#, python-format
 
msgid "Readme file from revision %s:%s"
 
msgstr ""
 

	
 
#: kallithea/templates/summary/summary.html:296
 
#: kallithea/templates/summary/summary.html:297
 
#, python-format
 
msgid "Download %s as %s"
 
msgstr ""
 

	
 
#: kallithea/templates/tags/tags.html:5
 
#, python-format
 
msgid "%s Tags"
 
msgstr ""
 

	
 
#: kallithea/templates/tags/tags.html:26
 
msgid "Compare Tags"
 
msgstr ""
 

	
kallithea/lib/auth.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.lib.auth
 
~~~~~~~~~~~~~~~~~~
 

	
 
authentication and permission libraries
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Apr 4, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 
from __future__ import with_statement
 
import time
 
import random
 
import os
 
import logging
 
import traceback
 
import hashlib
 
import itertools
 
import collections
 

	
 
from tempfile import _RandomNameSequence
 
from decorator import decorator
 

	
 
from pylons import url, request
 
from pylons.controllers.util import abort, redirect
 
from pylons.i18n.translation import _
 
from webhelpers.pylonslib import secure_form
 
from sqlalchemy import or_
 
from sqlalchemy.orm.exc import ObjectDeletedError
 
from sqlalchemy.orm import joinedload
 

	
 
from kallithea import __platform__, is_windows, is_unix
 
from kallithea.lib.vcs.utils.lazy import LazyProperty
 
from kallithea.model import meta
 
from kallithea.model.meta import Session
 
from kallithea.model.user import UserModel
 
from kallithea.model.db import User, Repository, Permission, \
 
    UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \
 
    RepoGroup, UserGroupRepoGroupToPerm, UserIpMap, UserGroupUserGroupToPerm, \
 
    UserGroup, UserApiKeys
 

	
 
from kallithea.lib.utils2 import safe_unicode, aslist
 
from kallithea.lib.utils import get_repo_slug, get_repo_group_slug, \
 
    get_user_group_slug, conditional_cache
 
from kallithea.lib.caching_query import FromCache
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class PasswordGenerator(object):
 
    """
 
    This is a simple class for generating password from different sets of
 
    characters
 
    usage::
 

	
 
        passwd_gen = PasswordGenerator()
 
        #print 8-letter password containing only big and small letters
 
            of alphabet
 
        passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
 
    """
 
    ALPHABETS_NUM = r'''1234567890'''
 
    ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
 
    ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
 
    ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
 
    ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
 
        + ALPHABETS_NUM + ALPHABETS_SPECIAL
 
    ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
 
    ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
 
    ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
 
    ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
 

	
 
    def __init__(self, passwd=''):
 
        self.passwd = passwd
 

	
 
    def gen_password(self, length, type_=None):
 
        if type_ is None:
 
            type_ = self.ALPHABETS_FULL
 
        self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
 
        return self.passwd
 
    def gen_password(self, length, alphabet=ALPHABETS_FULL):
 
        assert len(alphabet) <= 256, alphabet
 
        l = []
 
        while len(l) < length:
 
            i = ord(os.urandom(1))
 
            if i < len(alphabet):
 
                l.append(alphabet[i])
 
        return ''.join(l)
 

	
 

	
 
class KallitheaCrypto(object):
 

	
 
    @classmethod
 
    def hash_string(cls, str_):
 
        """
 
        Cryptographic function used for password hashing based on pybcrypt
 
        or pycrypto in windows
 

	
 
        :param password: password to hash
 
        """
 
        if is_windows:
 
            from hashlib import sha256
 
            return sha256(str_).hexdigest()
 
        elif is_unix:
 
            import bcrypt
 
            return bcrypt.hashpw(str_, bcrypt.gensalt(10))
 
        else:
 
            raise Exception('Unknown or unsupported platform %s' \
 
                            % __platform__)
 

	
 
    @classmethod
 
    def hash_check(cls, password, hashed):
 
        """
 
        Checks matching password with it's hashed value, runs different
 
        implementation based on platform it runs on
 

	
 
        :param password: password
 
        :param hashed: password in hashed form
 
        """
 

	
 
        if is_windows:
 
            from hashlib import sha256
 
            return sha256(password).hexdigest() == hashed
 
        elif is_unix:
 
            import bcrypt
 
            return bcrypt.hashpw(password, hashed) == hashed
 
        else:
 
            raise Exception('Unknown or unsupported platform %s' \
 
                            % __platform__)
 

	
 

	
 
def get_crypt_password(password):
 
    return KallitheaCrypto.hash_string(password)
 

	
 

	
 
def check_password(password, hashed):
 
    return KallitheaCrypto.hash_check(password, hashed)
 

	
 

	
 
class CookieStoreWrapper(object):
 

	
 
    def __init__(self, cookie_store):
 
        self.cookie_store = cookie_store
 

	
 
    def __repr__(self):
 
        return 'CookieStore<%s>' % (self.cookie_store)
 

	
 
    def get(self, key, other=None):
 
        if isinstance(self.cookie_store, dict):
 
            return self.cookie_store.get(key, other)
 
        elif isinstance(self.cookie_store, AuthUser):
 
            return self.cookie_store.__dict__.get(key, other)
 

	
 

	
 

	
 
def _cached_perms_data(user_id, user_is_admin, user_inherit_default_permissions,
 
                       explicit, algo):
 
    RK = 'repositories'
 
    GK = 'repositories_groups'
 
    UK = 'user_groups'
 
    GLOBAL = 'global'
 
    PERM_WEIGHTS = Permission.PERM_WEIGHTS
 
    permissions = {RK: {}, GK: {}, UK: {}, GLOBAL: set()}
 

	
 
    def _choose_perm(new_perm, cur_perm):
 
        new_perm_val = PERM_WEIGHTS[new_perm]
 
        cur_perm_val = PERM_WEIGHTS[cur_perm]
 
        if algo == 'higherwin':
 
            if new_perm_val > cur_perm_val:
 
                return new_perm
 
            return cur_perm
 
        elif algo == 'lowerwin':
 
            if new_perm_val < cur_perm_val:
 
                return new_perm
 
            return cur_perm
 

	
 
    #======================================================================
 
    # fetch default permissions
 
    #======================================================================
 
    default_user = User.get_by_username('default', cache=True)
 
    default_user_id = default_user.user_id
 

	
 
    default_repo_perms = Permission.get_default_perms(default_user_id)
 
    default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
 
    default_user_group_perms = Permission.get_default_user_group_perms(default_user_id)
 

	
 
    if user_is_admin:
 
        #==================================================================
 
        # admin user have all default rights for repositories
 
        # and groups set to admin
 
        #==================================================================
 
        permissions[GLOBAL].add('hg.admin')
 
        permissions[GLOBAL].add('hg.create.write_on_repogroup.true')
 

	
 
        # repositories
 
        for perm in default_repo_perms:
 
            r_k = perm.UserRepoToPerm.repository.repo_name
 
            p = 'repository.admin'
 
            permissions[RK][r_k] = p
 

	
 
        # repository groups
 
        for perm in default_repo_groups_perms:
 
            rg_k = perm.UserRepoGroupToPerm.group.group_name
 
            p = 'group.admin'
 
            permissions[GK][rg_k] = p
 

	
 
        # user groups
 
        for perm in default_user_group_perms:
 
            u_k = perm.UserUserGroupToPerm.user_group.users_group_name
 
            p = 'usergroup.admin'
 
            permissions[UK][u_k] = p
 
        return permissions
 

	
 
    #==================================================================
 
    # SET DEFAULTS GLOBAL, REPOS, REPOSITORY GROUPS
 
    #==================================================================
 
    uid = user_id
 

	
 
    # default global permissions taken from the default user
 
    default_global_perms = UserToPerm.query()\
 
        .filter(UserToPerm.user_id == default_user_id)\
 
        .options(joinedload(UserToPerm.permission))
 

	
 
    for perm in default_global_perms:
 
        permissions[GLOBAL].add(perm.permission.permission_name)
 

	
 
    # defaults for repositories, taken from default user
 
    for perm in default_repo_perms:
 
        r_k = perm.UserRepoToPerm.repository.repo_name
 
        if perm.Repository.private and not (perm.Repository.user_id == uid):
 
            # disable defaults for private repos,
 
            p = 'repository.none'
 
        elif perm.Repository.user_id == uid:
 
            # set admin if owner
 
            p = 'repository.admin'
 
        else:
 
            p = perm.Permission.permission_name
 

	
 
        permissions[RK][r_k] = p
 

	
 
    # defaults for repository groups taken from default user permission
 
    # on given group
 
    for perm in default_repo_groups_perms:
 
        rg_k = perm.UserRepoGroupToPerm.group.group_name
 
        p = perm.Permission.permission_name
 
        permissions[GK][rg_k] = p
 

	
 
    # defaults for user groups taken from default user permission
 
    # on given user group
 
    for perm in default_user_group_perms:
 
        u_k = perm.UserUserGroupToPerm.user_group.users_group_name
 
        p = perm.Permission.permission_name
 
        permissions[UK][u_k] = p
 

	
 
    #======================================================================
 
    # !! OVERRIDE GLOBALS !! with user permissions if any found
 
    #======================================================================
 
    # those can be configured from groups or users explicitly
 
    _configurable = set([
 
        'hg.fork.none', 'hg.fork.repository',
 
        'hg.create.none', 'hg.create.repository',
 
        'hg.usergroup.create.false', 'hg.usergroup.create.true'
 
    ])
 

	
 
    # USER GROUPS comes first
 
    # user group global permissions
 
    user_perms_from_users_groups = Session().query(UserGroupToPerm)\
 
        .options(joinedload(UserGroupToPerm.permission))\
 
        .join((UserGroupMember, UserGroupToPerm.users_group_id ==
 
               UserGroupMember.users_group_id))\
 
        .filter(UserGroupMember.user_id == uid)\
 
        .join((UserGroup, UserGroupMember.users_group_id ==
 
               UserGroup.users_group_id))\
 
        .filter(UserGroup.users_group_active == True)\
 
        .order_by(UserGroupToPerm.users_group_id)\
 
        .all()
 
    # need to group here by groups since user can be in more than
 
    # one group
 
    _grouped = [[x, list(y)] for x, y in
 
                itertools.groupby(user_perms_from_users_groups,
 
                                  lambda x:x.users_group)]
 
    for gr, perms in _grouped:
 
        # since user can be in multiple groups iterate over them and
 
        # select the lowest permissions first (more explicit)
 
        ##TODO: do this^^
 
        if not gr.inherit_default_permissions:
 
            # NEED TO IGNORE all configurable permissions and
 
            # replace them with explicitly set
 
            permissions[GLOBAL] = permissions[GLOBAL]\
 
                                            .difference(_configurable)
 
        for perm in perms:
 
            permissions[GLOBAL].add(perm.permission.permission_name)
 

	
 
    # user specific global permissions
 
    user_perms = Session().query(UserToPerm)\
 
            .options(joinedload(UserToPerm.permission))\
 
            .filter(UserToPerm.user_id == uid).all()
 

	
 
    if not user_inherit_default_permissions:
 
        # NEED TO IGNORE all configurable permissions and
 
        # replace them with explicitly set
 
        permissions[GLOBAL] = permissions[GLOBAL]\
 
                                        .difference(_configurable)
 

	
 
        for perm in user_perms:
 
            permissions[GLOBAL].add(perm.permission.permission_name)
 
    ## END GLOBAL PERMISSIONS
 

	
 
    #======================================================================
 
    # !! PERMISSIONS FOR REPOSITORIES !!
 
    #======================================================================
 
    #======================================================================
 
    # check if user is part of user groups for this repository and
 
    # fill in his permission from it. _choose_perm decides of which
 
    # permission should be selected based on selected method
 
    #======================================================================
 

	
 
    # user group for repositories permissions
 
    user_repo_perms_from_users_groups = \
 
     Session().query(UserGroupRepoToPerm, Permission, Repository,)\
 
        .join((Repository, UserGroupRepoToPerm.repository_id ==
 
               Repository.repo_id))\
 
        .join((Permission, UserGroupRepoToPerm.permission_id ==
 
               Permission.permission_id))\
 
        .join((UserGroup, UserGroupRepoToPerm.users_group_id ==
 
               UserGroup.users_group_id))\
 
        .filter(UserGroup.users_group_active == True)\
 
        .join((UserGroupMember, UserGroupRepoToPerm.users_group_id ==
 
               UserGroupMember.users_group_id))\
 
        .filter(UserGroupMember.user_id == uid)\
 
        .all()
 

	
 
    multiple_counter = collections.defaultdict(int)
 
    for perm in user_repo_perms_from_users_groups:
 
        r_k = perm.UserGroupRepoToPerm.repository.repo_name
 
        multiple_counter[r_k] += 1
 
        p = perm.Permission.permission_name
 
        cur_perm = permissions[RK][r_k]
 

	
 
        if perm.Repository.user_id == uid:
 
            # set admin if owner
 
            p = 'repository.admin'
 
        else:
 
            if multiple_counter[r_k] > 1:
 
                p = _choose_perm(p, cur_perm)
 
        permissions[RK][r_k] = p
 

	
 
    # user explicit permissions for repositories, overrides any specified
 
    # by the group permission
 
    user_repo_perms = Permission.get_default_perms(uid)
 
    for perm in user_repo_perms:
 
        r_k = perm.UserRepoToPerm.repository.repo_name
 
        cur_perm = permissions[RK][r_k]
 
        # set admin if owner
 
        if perm.Repository.user_id == uid:
 
            p = 'repository.admin'
 
        else:
 
            p = perm.Permission.permission_name
 
            if not explicit:
 
                p = _choose_perm(p, cur_perm)
 
        permissions[RK][r_k] = p
 

	
 
    #======================================================================
 
    # !! PERMISSIONS FOR REPOSITORY GROUPS !!
 
    #======================================================================
 
    #======================================================================
 
    # check if user is part of user groups for this repository groups and
 
    # fill in his permission from it. _choose_perm decides of which
 
    # permission should be selected based on selected method
 
    #======================================================================
 
    # user group for repo groups permissions
 
    user_repo_group_perms_from_users_groups = \
 
     Session().query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\
 
     .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
 
     .join((Permission, UserGroupRepoGroupToPerm.permission_id
 
            == Permission.permission_id))\
 
     .join((UserGroup, UserGroupRepoGroupToPerm.users_group_id ==
 
            UserGroup.users_group_id))\
 
     .filter(UserGroup.users_group_active == True)\
 
     .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id
 
            == UserGroupMember.users_group_id))\
 
     .filter(UserGroupMember.user_id == uid)\
 
     .all()
 

	
 
    multiple_counter = collections.defaultdict(int)
 
    for perm in user_repo_group_perms_from_users_groups:
 
        g_k = perm.UserGroupRepoGroupToPerm.group.group_name
 
        multiple_counter[g_k] += 1
 
        p = perm.Permission.permission_name
 
        cur_perm = permissions[GK][g_k]
 
        if multiple_counter[g_k] > 1:
 
            p = _choose_perm(p, cur_perm)
 
        permissions[GK][g_k] = p
 

	
 
    # user explicit permissions for repository groups
 
    user_repo_groups_perms = Permission.get_default_group_perms(uid)
 
    for perm in user_repo_groups_perms:
 
        rg_k = perm.UserRepoGroupToPerm.group.group_name
 
        p = perm.Permission.permission_name
 
        cur_perm = permissions[GK][rg_k]
 
        if not explicit:
 
            p = _choose_perm(p, cur_perm)
 
        permissions[GK][rg_k] = p
 

	
 
    #======================================================================
 
    # !! PERMISSIONS FOR USER GROUPS !!
 
    #======================================================================
 
    # user group for user group permissions
 
    user_group_user_groups_perms = \
 
     Session().query(UserGroupUserGroupToPerm, Permission, UserGroup)\
 
     .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id
 
            == UserGroup.users_group_id))\
 
     .join((Permission, UserGroupUserGroupToPerm.permission_id
 
            == Permission.permission_id))\
 
     .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id
 
            == UserGroupMember.users_group_id))\
 
     .filter(UserGroupMember.user_id == uid)\
 
     .join((UserGroup, UserGroupMember.users_group_id ==
 
            UserGroup.users_group_id), aliased=True, from_joinpoint=True)\
 
     .filter(UserGroup.users_group_active == True)\
 
     .all()
 

	
 
    multiple_counter = collections.defaultdict(int)
 
    for perm in user_group_user_groups_perms:
 
        g_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
 
        multiple_counter[g_k] += 1
 
        p = perm.Permission.permission_name
 
        cur_perm = permissions[UK][g_k]
 
        if multiple_counter[g_k] > 1:
 
            p = _choose_perm(p, cur_perm)
 
        permissions[UK][g_k] = p
 

	
 
    #user explicit permission for user groups
 
    user_user_groups_perms = Permission.get_default_user_group_perms(uid)
 
    for perm in user_user_groups_perms:
 
        u_k = perm.UserUserGroupToPerm.user_group.users_group_name
 
        p = perm.Permission.permission_name
 
        cur_perm = permissions[UK][u_k]
 
        if not explicit:
 
            p = _choose_perm(p, cur_perm)
 
        permissions[UK][u_k] = p
 

	
 
    return permissions
 

	
 

	
 
def allowed_api_access(controller_name, whitelist=None, api_key=None):
 
    """
 
    Check if given controller_name is in whitelist API access
 
    """
 
    if not whitelist:
 
        from kallithea import CONFIG
 
        whitelist = aslist(CONFIG.get('api_access_controllers_whitelist'),
 
                           sep=',')
 
        log.debug('whitelist of API access is: %s' % (whitelist))
 
    api_access_valid = controller_name in whitelist
 
    if api_access_valid:
 
        log.debug('controller:%s is in API whitelist' % (controller_name))
 
    else:
 
        msg = 'controller: %s is *NOT* in API whitelist' % (controller_name)
 
        if api_key:
 
            #if we use API key and don't have access it's a warning
 
            log.warning(msg)
 
        else:
 
            log.debug(msg)
 
    return api_access_valid
 

	
 

	
 
class AuthUser(object):
 
    """
 
    A simple object that handles all attributes of user in Kallithea
 

	
 
    It does lookup based on API key,given user, or user present in session
 
    Then it fills all required information for such user. It also checks if
 
    anonymous access is enabled and if so, it returns default user as logged in
 
    """
 

	
 
    def __init__(self, user_id=None, api_key=None, username=None):
 

	
 
        self.user_id = user_id
 
        self._api_key = api_key
 

	
 
        self.api_key = None
 
        self.username = username
 
        self.name = ''
 
        self.lastname = ''
 
        self.email = ''
 
        self.is_authenticated = False
 
        self.admin = False
 
        self.inherit_default_permissions = False
 

	
 
        self.propagate_data()
 
        self._instance = None
 

	
 
    @LazyProperty
 
    def permissions(self):
 
        return self.get_perms(user=self, cache=False)
 

	
 
    @property
 
    def api_keys(self):
 
        return self.get_api_keys()
 

	
 
    def propagate_data(self):
 
        user_model = UserModel()
 
        self.anonymous_user = User.get_default_user(cache=True)
 
        is_user_loaded = False
 

	
 
        # lookup by userid
 
        if self.user_id is not None and self.user_id != self.anonymous_user.user_id:
 
            log.debug('Auth User lookup by USER ID %s' % self.user_id)
 
            is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
 

	
 
        # try go get user by API key
 
        elif self._api_key and self._api_key != self.anonymous_user.api_key:
 
            log.debug('Auth User lookup by API key %s' % self._api_key)
 
            is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
 

	
 
        # lookup by username
 
        elif self.username:
 
            log.debug('Auth User lookup by USER NAME %s' % self.username)
 
            is_user_loaded = user_model.fill_data(self, username=self.username)
 
        else:
 
            log.debug('No data in %s that could been used to log in' % self)
 

	
 
        if not is_user_loaded:
 
            # if we cannot authenticate user try anonymous
 
            if self.anonymous_user.active:
 
                user_model.fill_data(self, user_id=self.anonymous_user.user_id)
 
                # then we set this user is logged in
 
                self.is_authenticated = True
 
            else:
 
                self.user_id = None
 
                self.username = None
 
                self.is_authenticated = False
 

	
 
        if not self.username:
 
            self.username = 'None'
 

	
 
        log.debug('Auth User is now %s' % self)
 

	
 
    def get_perms(self, user, explicit=True, algo='higherwin', cache=False):
 
        """
 
        Fills user permission attribute with permissions taken from database
 
        works for permissions given for repositories, and for permissions that
 
        are granted to groups
 

	
 
        :param user: instance of User object from database
 
        :param explicit: In case there are permissions both for user and a group
 
            that user is part of, explicit flag will define if user will
 
            explicitly override permissions from group, if it's False it will
 
            make decision based on the algo
 
        :param algo: algorithm to decide what permission should be choose if
 
            it's multiple defined, eg user in two different groups. It also
 
            decides if explicit flag is turned off how to specify the permission
 
            for case when user is in a group + have defined separate permission
 
        """
 
        user_id = user.user_id
 
        user_is_admin = user.is_admin
 
        user_inherit_default_permissions = user.inherit_default_permissions
 

	
 
        log.debug('Getting PERMISSION tree')
 
        compute = conditional_cache('short_term', 'cache_desc',
 
                                    condition=cache, func=_cached_perms_data)
 
        return compute(user_id, user_is_admin,
 
                       user_inherit_default_permissions, explicit, algo)
 

	
 
    def get_api_keys(self):
 
        api_keys = [self.api_key]
 
        for api_key in UserApiKeys.query()\
 
                .filter(UserApiKeys.user_id == self.user_id)\
 
                .filter(or_(UserApiKeys.expires == -1,
 
                            UserApiKeys.expires >= time.time())).all():
 
            api_keys.append(api_key.api_key)
 

	
 
        return api_keys
 

	
 
    @property
 
    def is_admin(self):
 
        return self.admin
 

	
 
    @property
 
    def repositories_admin(self):
 
        """
 
        Returns list of repositories you're an admin of
 
        """
 
        return [x[0] for x in self.permissions['repositories'].iteritems()
 
                if x[1] == 'repository.admin']
 

	
 
    @property
 
    def repository_groups_admin(self):
 
        """
 
        Returns list of repository groups you're an admin of
 
        """
 
        return [x[0] for x in self.permissions['repositories_groups'].iteritems()
 
                if x[1] == 'group.admin']
 

	
 
    @property
 
    def user_groups_admin(self):
 
        """
 
        Returns list of user groups you're an admin of
 
        """
 
        return [x[0] for x in self.permissions['user_groups'].iteritems()
 
                if x[1] == 'usergroup.admin']
 

	
 
    def is_ip_allowed(self, ip_addr):
 
        """
 
        Determine if `ip_addr` is on the list of allowed IP addresses
 
        for this user.
 
        """
 
        inherit = self.inherit_default_permissions
 
        return AuthUser.check_ip_allowed(self.user_id, ip_addr,
 
                                         inherit_from_default=inherit)
 

	
kallithea/lib/dbmigrate/schema/db_1_2_0.py
Show inline comments
 
@@ -147,385 +147,385 @@ class Setting(Base, BaseModel):
 
    __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
 
    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 

	
 
    def __init__(self, k='', v=''):
 
        self.app_settings_name = k
 
        self.app_settings_value = v
 

	
 

	
 
    @validates('_app_settings_value')
 
    def validate_settings_value(self, key, val):
 
        assert type(val) == unicode
 
        return val
 

	
 
    @hybrid_property
 
    def app_settings_value(self):
 
        v = self._app_settings_value
 
        if v == 'ldap_active':
 
            v = str2bool(v)
 
        return v
 

	
 
    @app_settings_value.setter
 
    def app_settings_value(self, val):
 
        """
 
        Setter that will always make sure we use unicode in app_settings_value
 

	
 
        :param val:
 
        """
 
        self._app_settings_value = safe_unicode(val)
 

	
 
    def __repr__(self):
 
        return "<%s('%s:%s')>" % (self.__class__.__name__,
 
                                  self.app_settings_name, self.app_settings_value)
 

	
 

	
 
    @classmethod
 
    def get_by_name(cls, ldap_key):
 
        return cls.query()\
 
            .filter(cls.app_settings_name == ldap_key).scalar()
 

	
 
    @classmethod
 
    def get_app_settings(cls, cache=False):
 

	
 
        ret = cls.query()
 

	
 
        if cache:
 
            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
 

	
 
        if not ret:
 
            raise Exception('Could not get application settings !')
 
        settings = {}
 
        for each in ret:
 
            settings[each.app_settings_name] = \
 
                each.app_settings_value
 

	
 
        return settings
 

	
 
    @classmethod
 
    def get_ldap_settings(cls, cache=False):
 
        ret = cls.query()\
 
                .filter(cls.app_settings_name.startswith('ldap_')).all()
 
        fd = {}
 
        for row in ret:
 
            fd.update({row.app_settings_name:row.app_settings_value})
 

	
 
        return fd
 

	
 

	
 
class Ui(Base, BaseModel):
 
    __tablename__ = DB_PREFIX + 'ui'
 
    __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
 

	
 
    HOOK_UPDATE = 'changegroup.update'
 
    HOOK_REPO_SIZE = 'changegroup.repo_size'
 
    HOOK_PUSH = 'pretxnchangegroup.push_logger'
 
    HOOK_PULL = 'preoutgoing.pull_logger'
 

	
 
    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
 

	
 

	
 
    @classmethod
 
    def get_by_key(cls, key):
 
        return cls.query().filter(cls.ui_key == key)
 

	
 

	
 
    @classmethod
 
    def get_builtin_hooks(cls):
 
        q = cls.query()
 
        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
 
                                    cls.HOOK_REPO_SIZE,
 
                                    cls.HOOK_PUSH, cls.HOOK_PULL]))
 
        return q.all()
 

	
 
    @classmethod
 
    def get_custom_hooks(cls):
 
        q = cls.query()
 
        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
 
                                    cls.HOOK_REPO_SIZE,
 
                                    cls.HOOK_PUSH, cls.HOOK_PULL]))
 
        q = q.filter(cls.ui_section == 'hooks')
 
        return q.all()
 

	
 
    @classmethod
 
    def create_or_update_hook(cls, key, val):
 
        new_ui = cls.get_by_key(key).scalar() or cls()
 
        new_ui.ui_section = 'hooks'
 
        new_ui.ui_active = True
 
        new_ui.ui_key = key
 
        new_ui.ui_value = val
 

	
 
        Session.add(new_ui)
 
        Session.commit()
 

	
 

	
 
class User(Base, BaseModel):
 
    __tablename__ = 'users'
 
    __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
 
    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    active = Column("active", Boolean(), nullable=True, unique=None, default=None)
 
    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
 
    name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
 
    ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 

	
 
    user_log = relationship('UserLog', cascade='all')
 
    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
 

	
 
    repositories = relationship('Repository')
 
    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
 
    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
 

	
 
    group_member = relationship('UserGroupMember', cascade='all')
 

	
 
    @property
 
    def full_contact(self):
 
        return '%s %s <%s>' % (self.name, self.lastname, self.email)
 

	
 
    @property
 
    def short_contact(self):
 
        return '%s %s' % (self.name, self.lastname)
 

	
 
    @property
 
    def is_admin(self):
 
        return self.admin
 

	
 
    def __repr__(self):
 
        try:
 
            return "<%s('id:%s:%s')>" % (self.__class__.__name__,
 
                                             self.user_id, self.username)
 
        except:
 
            return self.__class__.__name__
 

	
 
    @classmethod
 
    def get_by_username(cls, username, case_insensitive=False):
 
        if case_insensitive:
 
            return Session.query(cls).filter(cls.username.ilike(username)).scalar()
 
        else:
 
            return Session.query(cls).filter(cls.username == username).scalar()
 

	
 
    @classmethod
 
    def get_by_api_key(cls, api_key):
 
        return cls.query().filter(cls.api_key == api_key).one()
 

	
 
    def update_lastlogin(self):
 
        """Update user lastlogin"""
 

	
 
        self.last_login = datetime.datetime.now()
 
        Session.add(self)
 
        Session.commit()
 
        log.debug('updated user %s lastlogin' % self.username)
 

	
 
    @classmethod
 
    def create(cls, form_data):
 
        from kallithea.lib.auth import get_crypt_password
 

	
 
        try:
 
            new_user = cls()
 
            for k, v in form_data.items():
 
                if k == 'password':
 
                    v = get_crypt_password(v)
 
                setattr(new_user, k, v)
 

	
 
            new_user.api_key = generate_api_key(form_data['username'])
 
            new_user.api_key = generate_api_key()
 
            Session.add(new_user)
 
            Session.commit()
 
            return new_user
 
        except:
 
            log.error(traceback.format_exc())
 
            Session.rollback()
 
            raise
 

	
 
class UserLog(Base, BaseModel):
 
    __tablename__ = 'user_logs'
 
    __table_args__ = {'extend_existing':True}
 
    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 
    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
 
    repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
 

	
 
    @property
 
    def action_as_day(self):
 
        return date(*self.action_date.timetuple()[:3])
 

	
 
    user = relationship('User')
 
    repository = relationship('Repository')
 

	
 

	
 
class UserGroup(Base, BaseModel):
 
    __tablename__ = 'users_groups'
 
    __table_args__ = {'extend_existing':True}
 

	
 
    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
 
    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
 

	
 
    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
 

	
 
    def __repr__(self):
 
        return '<userGroup(%s)>' % (self.users_group_name)
 

	
 
    @classmethod
 
    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
 
        if case_insensitive:
 
            gr = cls.query()\
 
                .filter(cls.users_group_name.ilike(group_name))
 
        else:
 
            gr = cls.query()\
 
                .filter(cls.users_group_name == group_name)
 
        if cache:
 
            gr = gr.options(FromCache("sql_cache_short",
 
                                          "get_user_%s" % group_name))
 
        return gr.scalar()
 

	
 

	
 
    @classmethod
 
    def get(cls, users_group_id, cache=False):
 
        users_group = cls.query()
 
        if cache:
 
            users_group = users_group.options(FromCache("sql_cache_short",
 
                                    "get_users_group_%s" % users_group_id))
 
        return users_group.get(users_group_id)
 

	
 
    @classmethod
 
    def create(cls, form_data):
 
        try:
 
            new_users_group = cls()
 
            for k, v in form_data.items():
 
                setattr(new_users_group, k, v)
 

	
 
            Session.add(new_users_group)
 
            Session.commit()
 
            return new_users_group
 
        except:
 
            log.error(traceback.format_exc())
 
            Session.rollback()
 
            raise
 

	
 
    @classmethod
 
    def update(cls, users_group_id, form_data):
 

	
 
        try:
 
            users_group = cls.get(users_group_id, cache=False)
 

	
 
            for k, v in form_data.items():
 
                if k == 'users_group_members':
 
                    users_group.members = []
 
                    Session.flush()
 
                    members_list = []
 
                    if v:
 
                        v = [v] if isinstance(v, basestring) else v
 
                        for u_id in set(v):
 
                            member = UserGroupMember(users_group_id, u_id)
 
                            members_list.append(member)
 
                    setattr(users_group, 'members', members_list)
 
                setattr(users_group, k, v)
 

	
 
            Session.add(users_group)
 
            Session.commit()
 
        except:
 
            log.error(traceback.format_exc())
 
            Session.rollback()
 
            raise
 

	
 
    @classmethod
 
    def delete(cls, users_group_id):
 
        try:
 

	
 
            # check if this group is not assigned to repo
 
            assigned_groups = UserGroupRepoToPerm.query()\
 
                .filter(UserGroupRepoToPerm.users_group_id ==
 
                        users_group_id).all()
 

	
 
            if assigned_groups:
 
                raise UserGroupsAssignedException('RepoGroup assigned to %s' %
 
                                                   assigned_groups)
 

	
 
            users_group = cls.get(users_group_id, cache=False)
 
            Session.delete(users_group)
 
            Session.commit()
 
        except:
 
            log.error(traceback.format_exc())
 
            Session.rollback()
 
            raise
 

	
 
class UserGroupMember(Base, BaseModel):
 
    __tablename__ = 'users_groups_members'
 
    __table_args__ = {'extend_existing':True}
 

	
 
    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 

	
 
    user = relationship('User', lazy='joined')
 
    users_group = relationship('UserGroup')
 

	
 
    def __init__(self, gr_id='', u_id=''):
 
        self.users_group_id = gr_id
 
        self.user_id = u_id
 

	
 
    @staticmethod
 
    def add_user_to_group(group, user):
 
        ugm = UserGroupMember()
 
        ugm.users_group = group
 
        ugm.user = user
 
        Session.add(ugm)
 
        Session.commit()
 
        return ugm
 

	
 
class Repository(Base, BaseModel):
 
    __tablename__ = 'repositories'
 
    __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
 

	
 
    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
 
    clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
 
    repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
 
    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
 
    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
 
    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
 
    description = Column("description", String(length=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)
 

	
 
    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
 
    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
 

	
 

	
 
    user = relationship('User')
 
    fork = relationship('Repository', remote_side=repo_id)
 
    group = relationship('RepoGroup')
 
    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
 
    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
 
    stats = relationship('Statistics', cascade='all', uselist=False)
 

	
 
    followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
 

	
 
    logs = relationship('UserLog', cascade='all')
 

	
 
    def __repr__(self):
 
        return "<%s('%s:%s')>" % (self.__class__.__name__,
 
                                  self.repo_id, self.repo_name)
 

	
 
    @classmethod
 
    def url_sep(cls):
 
        return '/'
 

	
 
    @classmethod
 
    def get_by_repo_name(cls, repo_name):
 
        q = Session.query(cls).filter(cls.repo_name == repo_name)
 
        q = q.options(joinedload(Repository.fork))\
 
                .options(joinedload(Repository.user))\
 
                .options(joinedload(Repository.group))
kallithea/lib/helpers.py
Show inline comments
 
@@ -162,400 +162,400 @@ class _ToolTip(object):
 
        tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
 
        return tooltip_title
 
tooltip = _ToolTip()
 

	
 

	
 
class _FilesBreadCrumbs(object):
 

	
 
    def __call__(self, repo_name, rev, paths):
 
        if isinstance(paths, str):
 
            paths = safe_unicode(paths)
 
        url_l = [link_to(repo_name, url('files_home',
 
                                        repo_name=repo_name,
 
                                        revision=rev, f_path=''),
 
                         class_='ypjax-link')]
 
        paths_l = paths.split('/')
 
        for cnt, p in enumerate(paths_l):
 
            if p != '':
 
                url_l.append(link_to(p,
 
                                     url('files_home',
 
                                         repo_name=repo_name,
 
                                         revision=rev,
 
                                         f_path='/'.join(paths_l[:cnt + 1])
 
                                         ),
 
                                     class_='ypjax-link'
 
                                     )
 
                             )
 

	
 
        return literal('/'.join(url_l))
 

	
 
files_breadcrumbs = _FilesBreadCrumbs()
 

	
 

	
 
class CodeHtmlFormatter(HtmlFormatter):
 
    """
 
    My code Html Formatter for source codes
 
    """
 

	
 
    def wrap(self, source, outfile):
 
        return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
 

	
 
    def _wrap_code(self, source):
 
        for cnt, it in enumerate(source):
 
            i, t = it
 
            t = '<div id="L%s">%s</div>' % (cnt + 1, t)
 
            yield i, t
 

	
 
    def _wrap_tablelinenos(self, inner):
 
        dummyoutfile = StringIO.StringIO()
 
        lncount = 0
 
        for t, line in inner:
 
            if t:
 
                lncount += 1
 
            dummyoutfile.write(line)
 

	
 
        fl = self.linenostart
 
        mw = len(str(lncount + fl - 1))
 
        sp = self.linenospecial
 
        st = self.linenostep
 
        la = self.lineanchors
 
        aln = self.anchorlinenos
 
        nocls = self.noclasses
 
        if sp:
 
            lines = []
 

	
 
            for i in range(fl, fl + lncount):
 
                if i % st == 0:
 
                    if i % sp == 0:
 
                        if aln:
 
                            lines.append('<a href="#%s%d" class="special">%*d</a>' %
 
                                         (la, i, mw, i))
 
                        else:
 
                            lines.append('<span class="special">%*d</span>' % (mw, i))
 
                    else:
 
                        if aln:
 
                            lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
 
                        else:
 
                            lines.append('%*d' % (mw, i))
 
                else:
 
                    lines.append('')
 
            ls = '\n'.join(lines)
 
        else:
 
            lines = []
 
            for i in range(fl, fl + lncount):
 
                if i % st == 0:
 
                    if aln:
 
                        lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
 
                    else:
 
                        lines.append('%*d' % (mw, i))
 
                else:
 
                    lines.append('')
 
            ls = '\n'.join(lines)
 

	
 
        # in case you wonder about the seemingly redundant <div> here: since the
 
        # content in the other cell also is wrapped in a div, some browsers in
 
        # some configurations seem to mess up the formatting...
 
        if nocls:
 
            yield 0, ('<table class="%stable">' % self.cssclass +
 
                      '<tr><td><div class="linenodiv" '
 
                      'style="background-color: #f0f0f0; padding-right: 10px">'
 
                      '<pre style="line-height: 125%">' +
 
                      ls + '</pre></div></td><td id="hlcode" class="code">')
 
        else:
 
            yield 0, ('<table class="%stable">' % self.cssclass +
 
                      '<tr><td class="linenos"><div class="linenodiv"><pre>' +
 
                      ls + '</pre></div></td><td id="hlcode" class="code">')
 
        yield 0, dummyoutfile.getvalue()
 
        yield 0, '</td></tr></table>'
 

	
 

	
 
_whitespace_re = re.compile(r'(\t)|( )(?=\n|</div>)')
 

	
 
def _markup_whitespace(m):
 
    groups = m.groups()
 
    if groups[0]:
 
        return '<u>\t</u>'
 
    if groups[1]:
 
        return ' <i></i>'
 

	
 
def markup_whitespace(s):
 
    return _whitespace_re.sub(_markup_whitespace, s)
 

	
 
def pygmentize(filenode, **kwargs):
 
    """
 
    pygmentize function using pygments
 

	
 
    :param filenode:
 
    """
 
    lexer = get_custom_lexer(filenode.extension) or filenode.lexer
 
    return literal(markup_whitespace(
 
        code_highlight(filenode.content, lexer, CodeHtmlFormatter(**kwargs))))
 

	
 

	
 
def pygmentize_annotation(repo_name, filenode, **kwargs):
 
    """
 
    pygmentize function for annotation
 

	
 
    :param filenode:
 
    """
 

	
 
    color_dict = {}
 

	
 
    def gen_color(n=10000):
 
        """generator for getting n of evenly distributed colors using
 
        hsv color and golden ratio. It always return same order of colors
 

	
 
        :returns: RGB tuple
 
        """
 

	
 
        def hsv_to_rgb(h, s, v):
 
            if s == 0.0:
 
                return v, v, v
 
            i = int(h * 6.0)  # XXX assume int() truncates!
 
            f = (h * 6.0) - i
 
            p = v * (1.0 - s)
 
            q = v * (1.0 - s * f)
 
            t = v * (1.0 - s * (1.0 - f))
 
            i = i % 6
 
            if i == 0:
 
                return v, t, p
 
            if i == 1:
 
                return q, v, p
 
            if i == 2:
 
                return p, v, t
 
            if i == 3:
 
                return p, q, v
 
            if i == 4:
 
                return t, p, v
 
            if i == 5:
 
                return v, p, q
 

	
 
        golden_ratio = 0.618033988749895
 
        h = 0.22717784590367374
 

	
 
        for _unused in xrange(n):
 
            h += golden_ratio
 
            h %= 1
 
            HSV_tuple = [h, 0.95, 0.95]
 
            RGB_tuple = hsv_to_rgb(*HSV_tuple)
 
            yield map(lambda x: str(int(x * 256)), RGB_tuple)
 

	
 
    cgenerator = gen_color()
 

	
 
    def get_color_string(cs):
 
        if cs in color_dict:
 
            col = color_dict[cs]
 
        else:
 
            col = color_dict[cs] = cgenerator.next()
 
        return "color: rgb(%s)! important;" % (', '.join(col))
 

	
 
    def url_func(repo_name):
 

	
 
        def _url_func(changeset):
 
            author = changeset.author
 
            author = escape(changeset.author)
 
            date = changeset.date
 
            message = tooltip(changeset.message)
 
            message = escape(changeset.message)
 

	
 
            tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
 
                            " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
 
                            "</b> %s<br/></div>")
 

	
 
            tooltip_html = tooltip_html % (author, date, message)
 
            lnk_format = show_id(changeset)
 
            uri = link_to(
 
                    lnk_format,
 
                    url('changeset_home', repo_name=repo_name,
 
                        revision=changeset.raw_id),
 
                    style=get_color_string(changeset.raw_id),
 
                    class_='tooltip',
 
                    class_='tooltip safe-html-title',
 
                    title=tooltip_html
 
                  )
 

	
 
            uri += '\n'
 
            return uri
 
        return _url_func
 

	
 
    return literal(markup_whitespace(annotate_highlight(filenode, url_func(repo_name), **kwargs)))
 

	
 

	
 
def is_following_repo(repo_name, user_id):
 
    from kallithea.model.scm import ScmModel
 
    return ScmModel().is_following_repo(repo_name, user_id)
 

	
 
class _Message(object):
 
    """A message returned by ``Flash.pop_messages()``.
 

	
 
    Converting the message to a string returns the message text. Instances
 
    also have the following attributes:
 

	
 
    * ``message``: the message text.
 
    * ``category``: the category specified when the message was created.
 
    """
 

	
 
    def __init__(self, category, message):
 
        self.category = category
 
        self.message = message
 

	
 
    def __str__(self):
 
        return self.message
 

	
 
    __unicode__ = __str__
 

	
 
    def __html__(self):
 
        return escape(safe_unicode(self.message))
 

	
 
class Flash(_Flash):
 

	
 
    def __call__(self, message, category=None, ignore_duplicate=False, logf=None):
 
        """
 
        Show a message to the user _and_ log it through the specified function
 

	
 
        category: notice (default), warning, error, success
 
        logf: a custom log function - such as log.debug
 

	
 
        logf defaults to log.info, unless category equals 'success', in which
 
        case logf defaults to log.debug.
 
        """
 
        if logf is None:
 
            logf = log.info
 
            if category == 'success':
 
                logf = log.debug
 

	
 
        logf('Flash %s: %s', category, message)
 

	
 
        super(Flash, self).__call__(message, category, ignore_duplicate)
 

	
 
    def pop_messages(self):
 
        """Return all accumulated messages and delete them from the session.
 

	
 
        The return value is a list of ``Message`` objects.
 
        """
 
        from pylons import session
 
        messages = session.pop(self.session_key, [])
 
        session.save()
 
        return [_Message(*m) for m in messages]
 

	
 
flash = Flash()
 

	
 
#==============================================================================
 
# SCM FILTERS available via h.
 
#==============================================================================
 
from kallithea.lib.vcs.utils import author_name, author_email
 
from kallithea.lib.utils2 import credentials_filter, age as _age
 
from kallithea.model.db import User, ChangesetStatus, PullRequest
 

	
 
age = lambda  x, y=False: _age(x, y)
 
capitalize = lambda x: x.capitalize()
 
email = author_email
 
short_id = lambda x: x[:12]
 
hide_credentials = lambda x: ''.join(credentials_filter(x))
 

	
 

	
 
def show_id(cs):
 
    """
 
    Configurable function that shows ID
 
    by default it's r123:fffeeefffeee
 

	
 
    :param cs: changeset instance
 
    """
 
    from kallithea import CONFIG
 
    def_len = safe_int(CONFIG.get('show_sha_length', 12))
 
    show_rev = str2bool(CONFIG.get('show_revision_number', False))
 

	
 
    raw_id = cs.raw_id[:def_len]
 
    if show_rev:
 
        return 'r%s:%s' % (cs.revision, raw_id)
 
    else:
 
        return '%s' % (raw_id)
 

	
 

	
 
def fmt_date(date):
 
    if date:
 
        return date.strftime("%Y-%m-%d %H:%M:%S").decode('utf8')
 

	
 
    return ""
 

	
 

	
 
def is_git(repository):
 
    if hasattr(repository, 'alias'):
 
        _type = repository.alias
 
    elif hasattr(repository, 'repo_type'):
 
        _type = repository.repo_type
 
    else:
 
        _type = repository
 
    return _type == 'git'
 

	
 

	
 
def is_hg(repository):
 
    if hasattr(repository, 'alias'):
 
        _type = repository.alias
 
    elif hasattr(repository, 'repo_type'):
 
        _type = repository.repo_type
 
    else:
 
        _type = repository
 
    return _type == 'hg'
 

	
 

	
 
def user_or_none(author):
 
    email = author_email(author)
 
    if email:
 
        user = User.get_by_email(email, case_insensitive=True, cache=True)
 
        if user is not None:
 
            return user
 

	
 
    user = User.get_by_username(author_name(author), case_insensitive=True, cache=True)
 
    if user is not None:
 
        return user
 

	
 
    return None
 

	
 
def email_or_none(author):
 
    if not author:
 
        return None
 
    user = user_or_none(author)
 
    if user is not None:
 
        return user.email # always use main email address - not necessarily the one used to find user
 

	
 
    # extract email from the commit string
 
    email = author_email(author)
 
    if email:
 
        return email
 

	
 
    # No valid email, not a valid user in the system, none!
 
    return None
 

	
 
def person(author, show_attr="username"):
 
    """Find the user identified by 'author', return one of the users attributes,
 
    default to the username attribute, None if there is no user"""
 
    # attr to return from fetched user
 
    person_getter = lambda usr: getattr(usr, show_attr)
 

	
 
    # if author is already an instance use it for extraction
 
    if isinstance(author, User):
 
        return person_getter(author)
 

	
 
    user = user_or_none(author)
 
    if user is not None:
 
        return person_getter(user)
 

	
 
    # Still nothing?  Just pass back the author name if any, else the email
 
    return author_name(author) or email(author)
 

	
 

	
 
def person_by_id(id_, show_attr="username"):
 
    # attr to return from fetched user
 
    person_getter = lambda usr: getattr(usr, show_attr)
 

	
 
    #maybe it's an ID ?
 
    if str(id_).isdigit() or isinstance(id_, int):
 
        id_ = int(id_)
 
        user = User.get(id_)
 
        if user is not None:
 
            return person_getter(user)
 
    return id_
 

	
 

	
 
def desc_stylize(value):
 
    """
 
    converts tags from value into html equivalent
 

	
 
    :param value:
kallithea/lib/utils2.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.lib.utils
 
~~~~~~~~~~~~~~~~~~~
 

	
 
Some simple helper functions
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Jan 5, 2011
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 

	
 
import os
 
import re
 
import sys
 
import time
 
import uuid
 
import datetime
 
import urllib
 
import binascii
 

	
 
import webob
 
import urllib
 
import urlobject
 

	
 
from pylons.i18n.translation import _, ungettext
 
from kallithea.lib.vcs.utils.lazy import LazyProperty
 
from kallithea.lib.compat import json
 

	
 

	
 
def __get_lem():
 
    """
 
    Get language extension map based on what's inside pygments lexers
 
    """
 
    from pygments import lexers
 
    from string import lower
 
    from collections import defaultdict
 

	
 
    d = defaultdict(lambda: [])
 

	
 
    def __clean(s):
 
        s = s.lstrip('*')
 
        s = s.lstrip('.')
 

	
 
        if s.find('[') != -1:
 
            exts = []
 
            start, stop = s.find('['), s.find(']')
 

	
 
            for suffix in s[start + 1:stop]:
 
                exts.append(s[:s.find('[')] + suffix)
 
            return map(lower, exts)
 
        else:
 
            return map(lower, [s])
 

	
 
    for lx, t in sorted(lexers.LEXERS.items()):
 
        m = map(__clean, t[-2])
 
        if m:
 
            m = reduce(lambda x, y: x + y, m)
 
            for ext in m:
 
                desc = lx.replace('Lexer', '')
 
                d[ext].append(desc)
 

	
 
    return dict(d)
 

	
 

	
 
def str2bool(_str):
 
    """
 
    returs True/False value from given string, it tries to translate the
 
    string into boolean
 

	
 
    :param _str: string value to translate into boolean
 
    :rtype: boolean
 
    :returns: boolean from given string
 
    """
 
    if _str is None:
 
        return False
 
    if _str in (True, False):
 
        return _str
 
    _str = str(_str).strip().lower()
 
    return _str in ('t', 'true', 'y', 'yes', 'on', '1')
 

	
 

	
 
def aslist(obj, sep=None, strip=True):
 
    """
 
    Returns given string separated by sep as list
 

	
 
    :param obj:
 
    :param sep:
 
    :param strip:
 
    """
 
    if isinstance(obj, (basestring)):
 
        lst = obj.split(sep)
 
        if strip:
 
            lst = [v.strip() for v in lst]
 
        return lst
 
    elif isinstance(obj, (list, tuple)):
 
        return obj
 
    elif obj is None:
 
        return []
 
    else:
 
        return [obj]
 

	
 

	
 
def convert_line_endings(line, mode):
 
    """
 
    Converts a given line  "line end" according to given mode
 

	
 
    Available modes are::
 
        0 - Unix
 
        1 - Mac
 
        2 - DOS
 

	
 
    :param line: given line to convert
 
    :param mode: mode to convert to
 
    :rtype: str
 
    :return: converted line according to mode
 
    """
 
    from string import replace
 

	
 
    if mode == 0:
 
            line = replace(line, '\r\n', '\n')
 
            line = replace(line, '\r', '\n')
 
    elif mode == 1:
 
            line = replace(line, '\r\n', '\r')
 
            line = replace(line, '\n', '\r')
 
    elif mode == 2:
 
            line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
 
    return line
 

	
 

	
 
def detect_mode(line, default):
 
    """
 
    Detects line break for given line, if line break couldn't be found
 
    given default value is returned
 

	
 
    :param line: str line
 
    :param default: default
 
    :rtype: int
 
    :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
 
    """
 
    if line.endswith('\r\n'):
 
        return 2
 
    elif line.endswith('\n'):
 
        return 0
 
    elif line.endswith('\r'):
 
        return 1
 
    else:
 
        return default
 

	
 

	
 
def generate_api_key(username, salt=None):
 
def generate_api_key():
 
    """
 
    Generates unique API key for given username, if salt is not given
 
    it'll be generated from some random string
 

	
 
    :param username: username as string
 
    :param salt: salt to hash generate KEY
 
    :rtype: str
 
    :returns: sha1 hash from username+salt
 
    Generates a random (presumably unique) API key.
 
    """
 
    from tempfile import _RandomNameSequence
 
    import hashlib
 

	
 
    if salt is None:
 
        salt = _RandomNameSequence().next()
 

	
 
    return hashlib.sha1(username + salt).hexdigest()
 
    return binascii.hexlify(os.urandom(20))
 

	
 

	
 
def safe_int(val, default=None):
 
    """
 
    Returns int() of val if val is not convertable to int use default
 
    instead
 

	
 
    :param val:
 
    :param default:
 
    """
 

	
 
    try:
 
        val = int(val)
 
    except (ValueError, TypeError):
 
        val = default
 

	
 
    return val
 

	
 

	
 
def safe_unicode(str_, from_encoding=None):
 
    """
 
    safe unicode function. Does few trick to turn str_ into unicode
 

	
 
    In case of UnicodeDecode error we try to return it with encoding detected
 
    by chardet library if it fails fallback to unicode with errors replaced
 

	
 
    :param str_: string to decode
 
    :rtype: unicode
 
    :returns: unicode object
 
    """
 
    if isinstance(str_, unicode):
 
        return str_
 

	
 
    if not from_encoding:
 
        import kallithea
 
        DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
 
                                                        'utf8'), sep=',')
 
        from_encoding = DEFAULT_ENCODINGS
 

	
 
    if not isinstance(from_encoding, (list, tuple)):
 
        from_encoding = [from_encoding]
 

	
 
    try:
 
        return unicode(str_)
 
    except UnicodeDecodeError:
 
        pass
 

	
 
    for enc in from_encoding:
 
        try:
 
            return unicode(str_, enc)
 
        except UnicodeDecodeError:
 
            pass
 

	
 
    try:
 
        import chardet
 
        encoding = chardet.detect(str_)['encoding']
 
        if encoding is None:
 
            raise Exception()
 
        return str_.decode(encoding)
 
    except (ImportError, UnicodeDecodeError, Exception):
 
        return unicode(str_, from_encoding[0], 'replace')
 

	
 

	
 
def safe_str(unicode_, to_encoding=None):
 
    """
 
    safe str function. Does few trick to turn unicode_ into string
 

	
 
    In case of UnicodeEncodeError we try to return it with encoding detected
 
    by chardet library if it fails fallback to string with errors replaced
 

	
 
    :param unicode_: unicode to encode
 
    :rtype: str
 
    :returns: str object
 
    """
 

	
 
    # if it's not basestr cast to str
 
    if not isinstance(unicode_, basestring):
 
        return str(unicode_)
 

	
 
    if isinstance(unicode_, str):
 
        return unicode_
 

	
 
    if not to_encoding:
 
        import kallithea
 
        DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
 
                                                        'utf8'), sep=',')
 
        to_encoding = DEFAULT_ENCODINGS
 

	
 
    if not isinstance(to_encoding, (list, tuple)):
 
        to_encoding = [to_encoding]
 

	
 
    for enc in to_encoding:
 
        try:
 
            return unicode_.encode(enc)
 
        except UnicodeEncodeError:
 
            pass
 

	
 
    try:
 
        import chardet
 
        encoding = chardet.detect(unicode_)['encoding']
 
        if encoding is None:
 
            raise UnicodeEncodeError()
 

	
 
        return unicode_.encode(encoding)
 
    except (ImportError, UnicodeEncodeError):
 
        return unicode_.encode(to_encoding[0], 'replace')
 

	
 

	
 
def remove_suffix(s, suffix):
 
    if s.endswith(suffix):
 
        s = s[:-1 * len(suffix)]
 
    return s
 

	
 

	
 
def remove_prefix(s, prefix):
 
    if s.startswith(prefix):
 
        s = s[len(prefix):]
 
    return s
 

	
 

	
 
def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
 
    """
 
    Custom engine_from_config functions that makes sure we use NullPool for
 
    file based sqlite databases. This prevents errors on sqlite. This only
 
    applies to sqlalchemy versions < 0.7.0
 

	
 
    """
 
    import sqlalchemy
 
    from sqlalchemy import engine_from_config as efc
 
    import logging
 

	
 
    if int(sqlalchemy.__version__.split('.')[1]) < 7:
 

	
 
        # This solution should work for sqlalchemy < 0.7.0, and should use
 
        # proxy=TimerProxy() for execution time profiling
 

	
 
        from sqlalchemy.pool import NullPool
 
        url = configuration[prefix + 'url']
 

	
 
        if url.startswith('sqlite'):
 
            kwargs.update({'poolclass': NullPool})
 
        return efc(configuration, prefix, **kwargs)
 
    else:
 
        import time
 
        from sqlalchemy import event
 

	
 
        log = logging.getLogger('sqlalchemy.engine')
 
        BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
 
        engine = efc(configuration, prefix, **kwargs)
 

	
 
        def color_sql(sql):
 
            COLOR_SEQ = "\033[1;%dm"
 
            COLOR_SQL = YELLOW
 
            normal = '\x1b[0m'
 
            return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
 

	
 
        if configuration['debug']:
 
            #attach events only for debug configuration
 

	
 
            def before_cursor_execute(conn, cursor, statement,
 
                                    parameters, context, executemany):
 
                context._query_start_time = time.time()
 
                log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
 

	
 
            def after_cursor_execute(conn, cursor, statement,
 
                                    parameters, context, executemany):
 
                total = time.time() - context._query_start_time
 
                log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
 

	
 
            event.listen(engine, "before_cursor_execute",
 
                         before_cursor_execute)
 
            event.listen(engine, "after_cursor_execute",
 
                         after_cursor_execute)
 

	
 
    return engine
 

	
 

	
 
def age(prevdate, show_short_version=False, now=None):
 
    """
 
    turns a datetime into an age string.
 
    If show_short_version is True, then it will generate a not so accurate but shorter string,
 
    example: 2days ago, instead of 2 days and 23 hours ago.
 

	
 
    :param prevdate: datetime object
 
    :param show_short_version: if it should aproximate the date and return a shorter string
 
    :rtype: unicode
 
    :returns: unicode words describing age
 
    """
 
    now = now or datetime.datetime.now()
 
    order = ['year', 'month', 'day', 'hour', 'minute', 'second']
 
    deltas = {}
 
    future = False
kallithea/model/api_key.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.model.api_key
 
~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
API key model for Kallithea
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Sep 8, 2013
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
from __future__ import with_statement
 
import time
 
import logging
 
from sqlalchemy import or_
 

	
 
from kallithea.lib.utils2 import generate_api_key
 
from kallithea.model import BaseModel
 
from kallithea.model.db import UserApiKeys
 
from kallithea.model.meta import Session
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ApiKeyModel(BaseModel):
 
    cls = UserApiKeys
 

	
 
    def create(self, user, description, lifetime=-1):
 
        """
 
        :param user: user or user_id
 
        :param description: description of ApiKey
 
        :param lifetime: expiration time in seconds
 
        """
 
        user = self._get_user(user)
 

	
 
        new_api_key = UserApiKeys()
 
        new_api_key.api_key = generate_api_key(user.username)
 
        new_api_key.api_key = generate_api_key()
 
        new_api_key.user_id = user.user_id
 
        new_api_key.description = description
 
        new_api_key.expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
 
        Session().add(new_api_key)
 

	
 
        return new_api_key
 

	
 
    def delete(self, api_key, user=None):
 
        """
 
        Deletes given api_key, if user is set it also filters the object for
 
        deletion by given user.
 
        """
 
        api_key = UserApiKeys.query().filter(UserApiKeys.api_key == api_key)
 

	
 
        if user:
 
            user = self._get_user(user)
 
            api_key = api_key.filter(UserApiKeys.user_id == user.user_id)
 

	
 
        api_key = api_key.scalar()
 
        Session().delete(api_key)
 

	
 
    def get_api_keys(self, user, show_expired=True):
 
        user = self._get_user(user)
 
        user_api_keys = UserApiKeys.query()\
 
            .filter(UserApiKeys.user_id == user.user_id)
 
        if not show_expired:
 
            user_api_keys = user_api_keys\
 
                .filter(or_(UserApiKeys.expires == -1,
 
                            UserApiKeys.expires >= time.time()))
 
        return user_api_keys
kallithea/model/db.py
Show inline comments
 
@@ -366,384 +366,387 @@ class Ui(Base, BaseModel):
 
    def get_custom_hooks(cls):
 
        q = cls.query()
 
        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
 
                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
 
                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
 
        q = q.filter(cls.ui_section == 'hooks')
 
        return q.all()
 

	
 
    @classmethod
 
    def get_repos_location(cls):
 
        return cls.get_by_key('/').ui_value
 

	
 
    @classmethod
 
    def create_or_update_hook(cls, key, val):
 
        new_ui = cls.get_by_key(key) or cls()
 
        new_ui.ui_section = 'hooks'
 
        new_ui.ui_active = True
 
        new_ui.ui_key = key
 
        new_ui.ui_value = val
 

	
 
        Session().add(new_ui)
 

	
 
    def __repr__(self):
 
        return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
 
                                    self.ui_key, self.ui_value)
 

	
 

	
 
class User(Base, BaseModel):
 
    __tablename__ = 'users'
 
    __table_args__ = (
 
        UniqueConstraint('username'), UniqueConstraint('email'),
 
        Index('u_username_idx', 'username'),
 
        Index('u_email_idx', 'email'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
 
    )
 
    DEFAULT_USER = 'default'
 
    DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
 

	
 
    user_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
 
    username = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
    password = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
    active = Column(Boolean(), nullable=True, unique=None, default=True)
 
    admin = Column(Boolean(), nullable=True, unique=None, default=False)
 
    name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
    lastname = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
    _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
    last_login = Column(DateTime(timezone=False), nullable=True, unique=None, default=None)
 
    extern_type = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
    extern_name = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
    api_key = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
    inherit_default_permissions = Column(Boolean(), nullable=False, unique=None, default=True)
 
    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
    _user_data = Column("user_data", LargeBinary(), nullable=True)  # JSON data
 

	
 
    user_log = relationship('UserLog')
 
    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
 

	
 
    repositories = relationship('Repository')
 
    repo_groups = relationship('RepoGroup')
 
    user_groups = relationship('UserGroup')
 
    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
 
    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
 

	
 
    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
 
    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
 

	
 
    group_member = relationship('UserGroupMember', cascade='all')
 

	
 
    notifications = relationship('UserNotification', cascade='all')
 
    # notifications assigned to this user
 
    user_created_notifications = relationship('Notification', cascade='all')
 
    # comments created by this user
 
    user_comments = relationship('ChangesetComment', cascade='all')
 
    #extra emails for this user
 
    user_emails = relationship('UserEmailMap', cascade='all')
 
    #extra API keys
 
    user_api_keys = relationship('UserApiKeys', cascade='all')
 

	
 

	
 
    @hybrid_property
 
    def email(self):
 
        return self._email
 

	
 
    @email.setter
 
    def email(self, val):
 
        self._email = val.lower() if val else None
 

	
 
    @property
 
    def firstname(self):
 
        # alias for future
 
        return self.name
 

	
 
    @property
 
    def emails(self):
 
        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
 
        return [self.email] + [x.email for x in other]
 

	
 
    @property
 
    def api_keys(self):
 
        other = UserApiKeys.query().filter(UserApiKeys.user==self).all()
 
        return [self.api_key] + [x.api_key for x in other]
 

	
 
    @property
 
    def ip_addresses(self):
 
        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
 
        return [x.ip_addr for x in ret]
 

	
 
    @property
 
    def username_and_name(self):
 
        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
 

	
 
    @property
 
    def full_name(self):
 
        return '%s %s' % (self.firstname, self.lastname)
 

	
 
    @property
 
    def full_name_or_username(self):
 
        return ('%s %s' % (self.firstname, self.lastname)
 
                if (self.firstname and self.lastname) else self.username)
 

	
 
    @property
 
    def full_contact(self):
 
        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
 

	
 
    @property
 
    def short_contact(self):
 
        return '%s %s' % (self.firstname, self.lastname)
 

	
 
    @property
 
    def is_admin(self):
 
        return self.admin
 

	
 
    @property
 
    def AuthUser(self):
 
        """
 
        Returns instance of AuthUser for this user
 
        """
 
        from kallithea.lib.auth import AuthUser
 
        return AuthUser(user_id=self.user_id, api_key=self.api_key,
 
                        username=self.username)
 

	
 
    @hybrid_property
 
    def user_data(self):
 
        if not self._user_data:
 
            return {}
 

	
 
        try:
 
            return json.loads(self._user_data)
 
        except TypeError:
 
            return {}
 

	
 
    @user_data.setter
 
    def user_data(self, val):
 
        try:
 
            self._user_data = json.dumps(val)
 
        except Exception:
 
            log.error(traceback.format_exc())
 

	
 
    def __unicode__(self):
 
        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
 
                                      self.user_id, self.username)
 

	
 
    @classmethod
 
    def get_or_404(cls, id_, allow_default=True):
 
        '''
 
        Overridden version of BaseModel.get_or_404, with an extra check on
 
        the default user.
 
        '''
 
        user = super(User, cls).get_or_404(id_)
 
        if allow_default == False:
 
            if user.username == User.DEFAULT_USER:
 
                raise DefaultUserException
 
        return user
 

	
 
    @classmethod
 
    def get_by_username(cls, username, case_insensitive=False, cache=False):
 
        if case_insensitive:
 
            q = cls.query().filter(cls.username.ilike(username))
 
        else:
 
            q = cls.query().filter(cls.username == username)
 

	
 
        if cache:
 
            q = q.options(FromCache(
 
                            "sql_cache_short",
 
                            "get_user_%s" % _hash_key(username)
 
                          )
 
            )
 
        return q.scalar()
 

	
 
    @classmethod
 
    def get_by_api_key(cls, api_key, cache=False, fallback=True):
 
        if len(api_key) != 40 or not api_key.isalnum():
 
            return None
 

	
 
        q = cls.query().filter(cls.api_key == api_key)
 

	
 
        if cache:
 
            q = q.options(FromCache("sql_cache_short",
 
                                    "get_api_key_%s" % api_key))
 
        res = q.scalar()
 

	
 
        if fallback and not res:
 
            #fallback to additional keys
 
            _res = UserApiKeys.query()\
 
                .filter(UserApiKeys.api_key == api_key)\
 
                .filter(or_(UserApiKeys.expires == -1,
 
                            UserApiKeys.expires >= time.time()))\
 
                .first()
 
            if _res:
 
                res = _res.user
 
        return res
 

	
 
    @classmethod
 
    def get_by_email(cls, email, case_insensitive=False, cache=False):
 
        if case_insensitive:
 
            q = cls.query().filter(cls.email.ilike(email))
 
        else:
 
            q = cls.query().filter(cls.email == email)
 

	
 
        if cache:
 
            q = q.options(FromCache("sql_cache_short",
 
                                    "get_email_key_%s" % email))
 

	
 
        ret = q.scalar()
 
        if ret is None:
 
            q = UserEmailMap.query()
 
            # try fetching in alternate email map
 
            if case_insensitive:
 
                q = q.filter(UserEmailMap.email.ilike(email))
 
            else:
 
                q = q.filter(UserEmailMap.email == email)
 
            q = q.options(joinedload(UserEmailMap.user))
 
            if cache:
 
                q = q.options(FromCache("sql_cache_short",
 
                                        "get_email_map_key_%s" % email))
 
            ret = getattr(q.scalar(), 'user', None)
 

	
 
        return ret
 

	
 
    @classmethod
 
    def get_from_cs_author(cls, author):
 
        """
 
        Tries to get User objects out of commit author string
 

	
 
        :param author:
 
        """
 
        from kallithea.lib.helpers import email, author_name
 
        # Valid email in the attribute passed, see if they're in the system
 
        _email = email(author)
 
        if _email:
 
            user = cls.get_by_email(_email, case_insensitive=True)
 
            if user:
 
                return user
 
        # Maybe we can match by username?
 
        _author = author_name(author)
 
        user = cls.get_by_username(_author, case_insensitive=True)
 
        if user:
 
            return user
 

	
 
    def update_lastlogin(self):
 
        """Update user lastlogin"""
 
        self.last_login = datetime.datetime.now()
 
        Session().add(self)
 
        log.debug('updated user %s lastlogin' % self.username)
 

	
 
    @classmethod
 
    def get_first_admin(cls):
 
        user = User.query().filter(User.admin == True).first()
 
        if user is None:
 
            raise Exception('Missing administrative account!')
 
        return user
 

	
 
    @classmethod
 
    def get_default_user(cls, cache=False):
 
        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
 
        if user is None:
 
            raise Exception('Missing default account!')
 
        return user
 

	
 
    def get_api_data(self, details=False):
 
        """
 
        Common function for generating user related data for API
 
        """
 
        user = self
 
        data = dict(
 
            user_id=user.user_id,
 
            username=user.username,
 
            firstname=user.name,
 
            lastname=user.lastname,
 
            email=user.email,
 
            emails=user.emails,
 
            active=user.active,
 
            admin=user.admin,
 
        )
 
        if details:
 
            data.update(dict(
 
                extern_type=user.extern_type,
 
                extern_name=user.extern_name,
 
                api_key=user.api_key,
 
                api_keys=user.api_keys,
 
                last_login=user.last_login,
 
                ip_addresses=user.ip_addresses
 
                ))
 
        return data
 

	
 
    def __json__(self):
 
        data = dict(
 
            full_name=self.full_name,
 
            full_name_or_username=self.full_name_or_username,
 
            short_contact=self.short_contact,
 
            full_contact=self.full_contact
 
        )
 
        data.update(self.get_api_data())
 
        return data
 

	
 

	
 
class UserApiKeys(Base, BaseModel):
 
    __tablename__ = 'user_api_keys'
 
    __table_args__ = (
 
        Index('uak_api_key_idx', 'api_key'),
 
        Index('uak_api_key_expires_idx', 'api_key', 'expires'),
 
        UniqueConstraint('api_key'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
 
    )
 
    __mapper_args__ = {}
 

	
 
    user_api_key_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
 
    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
 
    api_key = Column(String(255, convert_unicode=False), nullable=False, unique=True)
 
    description = Column(UnicodeText(1024))
 
    expires = Column(Float(53), nullable=False)
 
    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 

	
 
    user = relationship('User')
 

	
 
    @property
 
    def expired(self):
 
        if self.expires == -1:
 
            return False
 
        return time.time() > self.expires
 

	
 

	
 
class UserEmailMap(Base, BaseModel):
 
    __tablename__ = 'user_email_map'
 
    __table_args__ = (
 
        Index('uem_email_idx', 'email'),
 
        UniqueConstraint('email'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
 
    )
 
    __mapper_args__ = {}
 

	
 
    email_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
 
    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
 
    _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
 
    user = relationship('User')
 

	
 
    @validates('_email')
 
    def validate_email(self, key, email):
 
        # check if this email is not main one
 
        main_email = Session().query(User).filter(User.email == email).scalar()
 
        if main_email is not None:
 
            raise AttributeError('email %s is present is user table' % email)
 
        return email
 

	
 
    @hybrid_property
 
    def email(self):
 
        return self._email
 

	
 
    @email.setter
 
    def email(self, val):
 
        self._email = val.lower() if val else None
 

	
 

	
 
class UserIpMap(Base, BaseModel):
 
    __tablename__ = 'user_ip_map'
 
    __table_args__ = (
 
        UniqueConstraint('user_id', 'ip_addr'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
 
    )
 
    __mapper_args__ = {}
 

	
 
    ip_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
 
    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
 
@@ -1473,420 +1476,420 @@ class RepoGroup(Base, BaseModel):
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
 
    )
 
    __mapper_args__ = {'order_by': 'group_name'}
 

	
 
    SEP = ' &raquo; '
 

	
 
    group_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
 
    group_name = Column(String(255, convert_unicode=False), nullable=False, unique=True, default=None)
 
    group_parent_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
 
    group_description = Column(String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
 
    enable_locking = Column(Boolean(), nullable=False, unique=None, default=False)
 
    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
 
    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 

	
 
    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
 
    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
 
    parent_group = relationship('RepoGroup', remote_side=group_id)
 
    user = relationship('User')
 

	
 
    def __init__(self, group_name='', parent_group=None):
 
        self.group_name = group_name
 
        self.parent_group = parent_group
 

	
 
    def __unicode__(self):
 
        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
 
                                      self.group_name)
 

	
 
    @classmethod
 
    def _generate_choice(cls, repo_group):
 
        from webhelpers.html import literal as _literal
 
        _name = lambda k: _literal(cls.SEP.join(k))
 
        return repo_group.group_id, _name(repo_group.full_path_splitted)
 

	
 
    @classmethod
 
    def groups_choices(cls, groups=None, show_empty_group=True):
 
        if not groups:
 
            groups = cls.query().all()
 

	
 
        repo_groups = []
 
        if show_empty_group:
 
            repo_groups = [('-1', u'-- %s --' % _('Top level'))]
 

	
 
        repo_groups.extend([cls._generate_choice(x) for x in groups])
 

	
 
        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(cls.SEP)[0])
 
        return repo_groups
 

	
 
    @classmethod
 
    def url_sep(cls):
 
        return URL_SEP
 

	
 
    @classmethod
 
    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
 
        if case_insensitive:
 
            gr = cls.query()\
 
                .filter(cls.group_name.ilike(group_name))
 
        else:
 
            gr = cls.query()\
 
                .filter(cls.group_name == group_name)
 
        if cache:
 
            gr = gr.options(FromCache(
 
                            "sql_cache_short",
 
                            "get_group_%s" % _hash_key(group_name)
 
                            )
 
            )
 
        return gr.scalar()
 

	
 
    @property
 
    def parents(self):
 
        parents_recursion_limit = 10
 
        groups = []
 
        if self.parent_group is None:
 
            return groups
 
        cur_gr = self.parent_group
 
        groups.insert(0, cur_gr)
 
        cnt = 0
 
        while 1:
 
            cnt += 1
 
            gr = getattr(cur_gr, 'parent_group', None)
 
            cur_gr = cur_gr.parent_group
 
            if gr is None:
 
                break
 
            if cnt == parents_recursion_limit:
 
                # this will prevent accidental infinite loops
 
                log.error(('more than %s parents found for group %s, stopping '
 
                           'recursive parent fetching' % (parents_recursion_limit, self)))
 
                break
 

	
 
            groups.insert(0, gr)
 
        return groups
 

	
 
    @property
 
    def children(self):
 
        return RepoGroup.query().filter(RepoGroup.parent_group == self)
 

	
 
    @property
 
    def name(self):
 
        return self.group_name.split(RepoGroup.url_sep())[-1]
 

	
 
    @property
 
    def full_path(self):
 
        return self.group_name
 

	
 
    @property
 
    def full_path_splitted(self):
 
        return self.group_name.split(RepoGroup.url_sep())
 

	
 
    @property
 
    def repositories(self):
 
        return Repository.query()\
 
                .filter(Repository.group == self)\
 
                .order_by(Repository.repo_name)
 

	
 
    @property
 
    def repositories_recursive_count(self):
 
        cnt = self.repositories.count()
 

	
 
        def children_count(group):
 
            cnt = 0
 
            for child in group.children:
 
                cnt += child.repositories.count()
 
                cnt += children_count(child)
 
            return cnt
 

	
 
        return cnt + children_count(self)
 

	
 
    def _recursive_objects(self, include_repos=True):
 
        all_ = []
 

	
 
        def _get_members(root_gr):
 
            if include_repos:
 
                for r in root_gr.repositories:
 
                    all_.append(r)
 
            childs = root_gr.children.all()
 
            if childs:
 
                for gr in childs:
 
                    all_.append(gr)
 
                    _get_members(gr)
 

	
 
        _get_members(self)
 
        return [self] + all_
 

	
 
    def recursive_groups_and_repos(self):
 
        """
 
        Recursive return all groups, with repositories in those groups
 
        """
 
        return self._recursive_objects()
 

	
 
    def recursive_groups(self):
 
        """
 
        Returns all children groups for this group including children of children
 
        """
 
        return self._recursive_objects(include_repos=False)
 

	
 
    def get_new_name(self, group_name):
 
        """
 
        returns new full group name based on parent and new name
 

	
 
        :param group_name:
 
        """
 
        path_prefix = (self.parent_group.full_path_splitted if
 
                       self.parent_group else [])
 
        return RepoGroup.url_sep().join(path_prefix + [group_name])
 

	
 
    def get_api_data(self):
 
        """
 
        Common function for generating api data
 

	
 
        """
 
        group = self
 
        data = dict(
 
            group_id=group.group_id,
 
            group_name=group.group_name,
 
            group_description=group.group_description,
 
            parent_group=group.parent_group.group_name if group.parent_group else None,
 
            repositories=[x.repo_name for x in group.repositories],
 
            owner=group.user.username
 
        )
 
        return data
 

	
 

	
 
class Permission(Base, BaseModel):
 
    __tablename__ = 'permissions'
 
    __table_args__ = (
 
        Index('p_perm_name_idx', 'permission_name'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
 
    )
 
    PERMS = [
 
        ('hg.admin', _('Kallithea Administrator')),
 

	
 
        ('repository.none', _('Repository no access')),
 
        ('repository.read', _('Repository read access')),
 
        ('repository.write', _('Repository write access')),
 
        ('repository.admin', _('Repository admin access')),
 

	
 
        ('group.none', _('Repository group no access')),
 
        ('group.read', _('Repository group read access')),
 
        ('group.write', _('Repository group write access')),
 
        ('group.admin', _('Repository group admin access')),
 

	
 
        ('usergroup.none', _('User group no access')),
 
        ('usergroup.read', _('User group read access')),
 
        ('usergroup.write', _('User group write access')),
 
        ('usergroup.admin', _('User group admin access')),
 

	
 
        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
 
        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
 

	
 
        ('hg.usergroup.create.false', _('User Group creation disabled')),
 
        ('hg.usergroup.create.true', _('User Group creation enabled')),
 

	
 
        ('hg.create.none', _('Repository creation disabled')),
 
        ('hg.create.repository', _('Repository creation enabled')),
 
        ('repository.none', _('Default user has no access to new Repositories')),
 
        ('repository.read', _('Default user has read access to new Repositories')),
 
        ('repository.write', _('Default user has write access to new Repositories')),
 
        ('repository.admin', _('Default user has admin access to new Repositories')),
 

	
 
        ('group.none', _('Default user has no access to new Repository Groups')),
 
        ('group.read', _('Default user has read access to new Repository Groups')),
 
        ('group.write', _('Default user has write access to new Repository Groups')),
 
        ('group.admin', _('Default user has admin access to new Repository Groups')),
 

	
 
        ('usergroup.none', _('Default user has no access to new User Groups')),
 
        ('usergroup.read', _('Default user has read access to new User Groups')),
 
        ('usergroup.write', _('Default user has write access to new User Groups')),
 
        ('usergroup.admin', _('Default user has admin access to new User Groups')),
 

	
 
        ('hg.repogroup.create.false', _('Only admins can create Repository Groups')),
 
        ('hg.repogroup.create.true', _('Non-admins can create Repository Groups')),
 

	
 
        ('hg.usergroup.create.false', _('Only admins can create User Groups')),
 
        ('hg.usergroup.create.true', _('Non-admins can create User Groups')),
 

	
 
        ('hg.create.none', _('Only admins can create top level Repositories')),
 
        ('hg.create.repository', _('Non-admins can create top level Repositories')),
 

	
 
        ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
 
        ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
 

	
 
        ('hg.fork.none', _('Repository forking disabled')),
 
        ('hg.fork.repository', _('Repository forking enabled')),
 
        ('hg.fork.none', _('Only admins can fork repositories')),
 
        ('hg.fork.repository', _('Non-admins can can fork repositories')),
 

	
 
        ('hg.register.none', _('Registration disabled')),
 
        ('hg.register.manual_activate', _('User Registration with manual account activation')),
 
        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
 

	
 
        ('hg.extern_activate.manual', _('Manual activation of external account')),
 
        ('hg.extern_activate.auto', _('Automatic activation of external account')),
 

	
 
    ]
 

	
 
    #definition of system default permissions for DEFAULT user
 
    DEFAULT_USER_PERMISSIONS = [
 
        'repository.read',
 
        'group.read',
 
        'usergroup.read',
 
        'hg.create.repository',
 
        'hg.create.write_on_repogroup.true',
 
        'hg.fork.repository',
 
        'hg.register.manual_activate',
 
        'hg.extern_activate.auto',
 
    ]
 

	
 
    # defines which permissions are more important higher the more important
 
    # Weight defines which permissions are more important.
 
    # The higher number the more important.
 
    PERM_WEIGHTS = {
 
        'repository.none': 0,
 
        'repository.read': 1,
 
        'repository.write': 3,
 
        'repository.admin': 4,
 

	
 
        'group.none': 0,
 
        'group.read': 1,
 
        'group.write': 3,
 
        'group.admin': 4,
 

	
 
        'usergroup.none': 0,
 
        'usergroup.read': 1,
 
        'usergroup.write': 3,
 
        'usergroup.admin': 4,
 
        'hg.repogroup.create.false': 0,
 
        'hg.repogroup.create.true': 1,
 

	
 
        'hg.usergroup.create.false': 0,
 
        'hg.usergroup.create.true': 1,
 

	
 
        'hg.fork.none': 0,
 
        'hg.fork.repository': 1,
 
        'hg.create.none': 0,
 
        'hg.create.repository': 1
 
    }
 

	
 
    permission_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
 
    permission_name = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 
    permission_longname = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
 

	
 
    def __unicode__(self):
 
        return u"<%s('%s:%s')>" % (
 
            self.__class__.__name__, self.permission_id, self.permission_name
 
        )
 

	
 
    @classmethod
 
    def get_by_key(cls, key):
 
        return cls.query().filter(cls.permission_name == key).scalar()
 

	
 
    @classmethod
 
    def get_default_perms(cls, default_user_id):
 
        q = Session().query(UserRepoToPerm, Repository, cls)\
 
         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
 
         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
 
         .filter(UserRepoToPerm.user_id == default_user_id)
 

	
 
        return q.all()
 

	
 
    @classmethod
 
    def get_default_group_perms(cls, default_user_id):
 
        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
 
         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
 
         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
 
         .filter(UserRepoGroupToPerm.user_id == default_user_id)
 

	
 
        return q.all()
 

	
 
    @classmethod
 
    def get_default_user_group_perms(cls, default_user_id):
 
        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
 
         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
 
         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
 
         .filter(UserUserGroupToPerm.user_id == default_user_id)
 

	
 
        return q.all()
 

	
 

	
 
class UserRepoToPerm(Base, BaseModel):
 
    __tablename__ = 'repo_to_perm'
 
    __table_args__ = (
 
        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
 
    )
 
    repo_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
 
    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 
    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
 

	
 
    user = relationship('User')
 
    repository = relationship('Repository')
 
    permission = relationship('Permission')
 

	
 
    @classmethod
 
    def create(cls, user, repository, permission):
 
        n = cls()
 
        n.user = user
 
        n.repository = repository
 
        n.permission = permission
 
        Session().add(n)
 
        return n
 

	
 
    def __unicode__(self):
 
        return u'<%s => %s >' % (self.user, self.repository)
 

	
 

	
 
class UserUserGroupToPerm(Base, BaseModel):
 
    __tablename__ = 'user_user_group_to_perm'
 
    __table_args__ = (
 
        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
 
    )
 
    user_user_group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
 
    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 
    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
    user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
 

	
 
    user = relationship('User')
 
    user_group = relationship('UserGroup')
 
    permission = relationship('Permission')
 

	
 
    @classmethod
 
    def create(cls, user, user_group, permission):
 
        n = cls()
 
        n.user = user
 
        n.user_group = user_group
 
        n.permission = permission
 
        Session().add(n)
 
        return n
 

	
 
    def __unicode__(self):
 
        return u'<%s => %s >' % (self.user, self.user_group)
 

	
 

	
 
class UserToPerm(Base, BaseModel):
 
    __tablename__ = 'user_to_perm'
 
    __table_args__ = (
 
        UniqueConstraint('user_id', 'permission_id'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
 
    )
 
    user_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
 
    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 
    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 

	
 
    user = relationship('User')
 
    permission = relationship('Permission')
 

	
 
    def __unicode__(self):
 
        return u'<%s => %s >' % (self.user, self.permission)
 

	
 

	
 
class UserGroupRepoToPerm(Base, BaseModel):
 
    __tablename__ = 'users_group_repo_to_perm'
 
    __table_args__ = (
 
        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
 
        {'extend_existing': True, 'mysql_engine': 'InnoDB',
 
         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
 
    )
 
    users_group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
 
    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
 
    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
 
    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
 

	
 
    users_group = relationship('UserGroup')
 
    permission = relationship('Permission')
 
    repository = relationship('Repository')
 

	
 
    @classmethod
 
    def create(cls, users_group, repository, permission):
 
        n = cls()
 
        n.users_group = users_group
 
        n.repository = repository
 
        n.permission = permission
 
        Session().add(n)
 
        return n
 

	
 
    def __unicode__(self):
 
        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
 

	
 

	
 
class UserGroupUserGroupToPerm(Base, BaseModel):
 
    __tablename__ = 'user_group_user_group_to_perm'
kallithea/model/user.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
"""
 
kallithea.model.user
 
~~~~~~~~~~~~~~~~~~~~
 

	
 
users model for Kallithea
 

	
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Apr 9, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 

	
 
import logging
 
import traceback
 
from pylons.i18n.translation import _
 

	
 
from sqlalchemy.exc import DatabaseError
 

	
 
from kallithea import EXTERN_TYPE_INTERNAL
 
from kallithea.lib.utils2 import safe_unicode, generate_api_key, get_current_authuser
 
from kallithea.lib.caching_query import FromCache
 
from kallithea.model import BaseModel
 
from kallithea.model.db import User, UserToPerm, Notification, \
 
    UserEmailMap, UserIpMap
 
from kallithea.lib.exceptions import DefaultUserException, \
 
    UserOwnsReposException
 
from kallithea.model.meta import Session
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class UserModel(BaseModel):
 
    cls = User
 

	
 
    def get(self, user_id, cache=False):
 
        user = self.sa.query(User)
 
        if cache:
 
            user = user.options(FromCache("sql_cache_short",
 
                                          "get_user_%s" % user_id))
 
        return user.get(user_id)
 

	
 
    def get_user(self, user):
 
        return self._get_user(user)
 

	
 
    def get_by_username(self, username, cache=False, case_insensitive=False):
 
        return User.get_by_username(username, case_insensitive, cache)
 

	
 
    def get_by_email(self, email, cache=False, case_insensitive=False):
 
        return User.get_by_email(email, case_insensitive, cache)
 

	
 
    def get_by_api_key(self, api_key, cache=False):
 
        return User.get_by_api_key(api_key, cache)
 

	
 
    def create(self, form_data, cur_user=None):
 
        if not cur_user:
 
            cur_user = getattr(get_current_authuser(), 'username', None)
 

	
 
        from kallithea.lib.hooks import log_create_user, \
 
            check_allowed_create_user
 
        _fd = form_data
 
        user_data = {
 
            'username': _fd['username'],
 
            'password': _fd['password'],
 
            'email': _fd['email'],
 
            'firstname': _fd['firstname'],
 
            'lastname': _fd['lastname'],
 
            'active': _fd['active'],
 
            'admin': False
 
        }
 
        # raises UserCreationError if it's not allowed
 
        check_allowed_create_user(user_data, cur_user)
 
        from kallithea.lib.auth import get_crypt_password
 

	
 
        new_user = User()
 
        for k, v in form_data.items():
 
            if k == 'password':
 
                v = get_crypt_password(v)
 
            if k == 'firstname':
 
                k = 'name'
 
            setattr(new_user, k, v)
 

	
 
        new_user.api_key = generate_api_key(form_data['username'])
 
        new_user.api_key = generate_api_key()
 
        self.sa.add(new_user)
 

	
 
        log_create_user(new_user.get_dict(), cur_user)
 
        return new_user
 

	
 
    def create_or_update(self, username, password, email, firstname='',
 
                         lastname='', active=True, admin=False,
 
                         extern_type=None, extern_name=None, cur_user=None):
 
        """
 
        Creates a new instance if not found, or updates current one
 

	
 
        :param username:
 
        :param password:
 
        :param email:
 
        :param active:
 
        :param firstname:
 
        :param lastname:
 
        :param active:
 
        :param admin:
 
        :param extern_name:
 
        :param extern_type:
 
        :param cur_user:
 
        """
 
        if not cur_user:
 
            cur_user = getattr(get_current_authuser(), 'username', None)
 

	
 
        from kallithea.lib.auth import get_crypt_password, check_password
 
        from kallithea.lib.hooks import log_create_user, \
 
            check_allowed_create_user
 
        user_data = {
 
            'username': username, 'password': password,
 
            'email': email, 'firstname': firstname, 'lastname': lastname,
 
            'active': active, 'admin': admin
 
        }
 
        # raises UserCreationError if it's not allowed
 
        check_allowed_create_user(user_data, cur_user)
 

	
 
        log.debug('Checking for %s account in Kallithea database' % username)
 
        user = User.get_by_username(username, case_insensitive=True)
 
        if user is None:
 
            log.debug('creating new user %s' % username)
 
            new_user = User()
 
            edit = False
 
        else:
 
            log.debug('updating user %s' % username)
 
            new_user = user
 
            edit = True
 

	
 
        try:
 
            new_user.username = username
 
            new_user.admin = admin
 
            new_user.email = email
 
            new_user.active = active
 
            new_user.extern_name = safe_unicode(extern_name) \
 
                if extern_name else None
 
            new_user.extern_type = safe_unicode(extern_type) \
 
                if extern_type else None
 
            new_user.name = firstname
 
            new_user.lastname = lastname
 

	
 
            if not edit:
 
                new_user.api_key = generate_api_key(username)
 
                new_user.api_key = generate_api_key()
 

	
 
            # set password only if creating an user or password is changed
 
            password_change = new_user.password and \
 
                not check_password(password, new_user.password)
 
            if not edit or password_change:
 
                reason = 'new password' if edit else 'new user'
 
                log.debug('Updating password reason=>%s' % (reason,))
 
                new_user.password = get_crypt_password(password) \
 
                    if password else None
 

	
 
            self.sa.add(new_user)
 

	
 
            if not edit:
 
                log_create_user(new_user.get_dict(), cur_user)
 
            return new_user
 
        except (DatabaseError,):
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def create_registration(self, form_data):
 
        from kallithea.model.notification import NotificationModel
 
        import kallithea.lib.helpers as h
 

	
 
        form_data['admin'] = False
 
        form_data['extern_name'] = EXTERN_TYPE_INTERNAL
 
        form_data['extern_type'] = EXTERN_TYPE_INTERNAL
 
        new_user = self.create(form_data)
 

	
 
        self.sa.add(new_user)
 
        self.sa.flush()
 

	
 
        # notification to admins
 
        subject = _('New user registration')
 
        body = (
 
            'New user registration\n'
 
            '---------------------\n'
 
            '- Username: {user.username}\n'
 
            '- Full Name: {user.full_name}\n'
 
            '- Email: {user.email}\n'
 
            ).format(user=new_user)
 
        edit_url = h.canonical_url('edit_user', id=new_user.user_id)
 
        email_kwargs = {
 
            'registered_user_url': edit_url,
 
            'new_username': new_user.username}
 
        NotificationModel().create(created_by=new_user, subject=subject,
 
                                   body=body, recipients=None,
 
                                   type_=Notification.TYPE_REGISTRATION,
 
                                   email_kwargs=email_kwargs)
 

	
 
    def update(self, user_id, form_data, skip_attrs=[]):
 
        from kallithea.lib.auth import get_crypt_password
 

	
 
        user = self.get(user_id, cache=False)
 
        if user.username == User.DEFAULT_USER:
 
            raise DefaultUserException(
 
                            _("You can't Edit this user since it's "
 
                              "crucial for entire application"))
 

	
 
        for k, v in form_data.items():
 
            if k in skip_attrs:
 
                continue
 
            if k == 'new_password' and v:
 
                user.password = get_crypt_password(v)
 
            else:
 
                # old legacy thing orm models store firstname as name,
 
                # need proper refactor to username
 
                if k == 'firstname':
 
                    k = 'name'
 
                setattr(user, k, v)
 
        self.sa.add(user)
 

	
 
    def update_user(self, user, **kwargs):
 
        from kallithea.lib.auth import get_crypt_password
 

	
 
        user = self._get_user(user)
 
        if user.username == User.DEFAULT_USER:
 
            raise DefaultUserException(
 
                _("You can't Edit this user since it's"
 
                  " crucial for entire application")
 
            )
 

	
 
        for k, v in kwargs.items():
 
            if k == 'password' and v:
 
                v = get_crypt_password(v)
 

	
 
            setattr(user, k, v)
 
        self.sa.add(user)
 
        return user
 

	
 
    def delete(self, user, cur_user=None):
 
        if not cur_user:
 
            cur_user = getattr(get_current_authuser(), 'username', None)
 
        user = self._get_user(user)
 

	
 
        if user.username == User.DEFAULT_USER:
 
            raise DefaultUserException(
 
                _(u"You can't remove this user since it's"
 
                  " crucial for entire application"))
 
        if user.repositories:
 
            repos = [x.repo_name for x in user.repositories]
 
            raise UserOwnsReposException(
 
                _(u'User "%s" still owns %s repositories and cannot be '
 
                  'removed. Switch owners or remove those repositories: %s')
 
                % (user.username, len(repos), ', '.join(repos)))
 
        if user.repo_groups:
 
            repogroups = [x.group_name for x in user.repo_groups]
 
            raise UserOwnsReposException(_(
 
                'User "%s" still owns %s repository groups and cannot be '
 
                'removed. Switch owners or remove those repository groups: %s')
 
                % (user.username, len(repogroups), ', '.join(repogroups)))
 
        if user.user_groups:
 
            usergroups = [x.users_group_name for x in user.user_groups]
 
            raise UserOwnsReposException(
 
                _('User "%s" still owns %s user groups and cannot be '
 
                  'removed. Switch owners or remove those user groups: %s')
 
                % (user.username, len(usergroups), ', '.join(usergroups)))
 
        self.sa.delete(user)
 

	
 
        from kallithea.lib.hooks import log_delete_user
 
        log_delete_user(user.get_dict(), cur_user)
 

	
 
    def reset_password_link(self, data):
 
        from kallithea.lib.celerylib import tasks, run_task
 
        from kallithea.model.notification import EmailNotificationModel
 
        import kallithea.lib.helpers as h
 

	
 
        user_email = data['email']
 
        user = User.get_by_email(user_email)
 
        if user:
 
            log.debug('password reset user found %s' % user)
 
            link = h.canonical_url('reset_password_confirmation',
 
                                   key=user.api_key)
 
            reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
 
            body = EmailNotificationModel().get_email_tmpl(
 
                reg_type, 'txt',
 
                user=user.short_contact,
 
                reset_url=link)
 
            html_body = EmailNotificationModel().get_email_tmpl(
 
                reg_type, 'html',
 
                user=user.short_contact,
 
                reset_url=link)
 
            log.debug('sending email')
 
            run_task(tasks.send_email, [user_email],
 
                     _("Password reset link"), body, html_body)
 
            log.info('send new password mail to %s' % user_email)
 
        else:
 
            log.debug("password reset email %s not found" % user_email)
 

	
 
        return True
 

	
 
    def reset_password(self, data):
 
        from kallithea.lib.celerylib import tasks, run_task
 
        from kallithea.lib import auth
 
        user_email = data['email']
 
        user = User.get_by_email(user_email)
 
        new_passwd = auth.PasswordGenerator().gen_password(
 
            8, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
 
        if user:
 
            user.password = auth.get_crypt_password(new_passwd)
 
            Session().add(user)
 
            Session().commit()
 
            log.info('change password for %s' % user_email)
 
        if new_passwd is None:
 
            raise Exception('unable to generate new password')
 

	
 
        run_task(tasks.send_email, [user_email],
 
                 _('Your new password'),
 
                 _('Your new Kallithea password:%s') % (new_passwd,))
 
        log.info('send new password mail to %s' % user_email)
 

	
 
        return True
 

	
 
    def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
 
        """
 
        Fetches auth_user by user_id,or api_key if present.
 
        Fills auth_user attributes with those taken from database.
 
        Additionally sets is_authenticated if lookup fails
 
        present in database
 

	
 
        :param auth_user: instance of user to set attributes
 
        :param user_id: user id to fetch by
 
        :param api_key: API key to fetch by
 
        :param username: username to fetch by
 
        """
 
        if user_id is None and api_key is None and username is None:
 
            raise Exception('You need to pass user_id, api_key or username')
 

	
 
        dbuser = None
 
        if user_id is not None:
 
            dbuser = self.get(user_id)
 
        elif api_key is not None:
 
            dbuser = self.get_by_api_key(api_key)
kallithea/model/user_group.py
Show inline comments
 
@@ -57,334 +57,334 @@ class UserGroupModel(BaseModel):
 
        user_group_to_perm = UserUserGroupToPerm()
 
        user_group_to_perm.permission = Permission.get_by_key(default_perm)
 

	
 
        user_group_to_perm.user_group = user_group
 
        user_group_to_perm.user_id = def_user.user_id
 
        return user_group_to_perm
 

	
 
    def _update_permissions(self, user_group, perms_new=None,
 
                            perms_updates=None):
 
        from kallithea.lib.auth import HasUserGroupPermissionAny
 
        if not perms_new:
 
            perms_new = []
 
        if not perms_updates:
 
            perms_updates = []
 

	
 
        # update permissions
 
        for member, perm, member_type in perms_updates:
 
            if member_type == 'user':
 
                # this updates existing one
 
                self.grant_user_permission(
 
                    user_group=user_group, user=member, perm=perm
 
                )
 
            else:
 
                #check if we have permissions to alter this usergroup
 
                if HasUserGroupPermissionAny('usergroup.read', 'usergroup.write',
 
                                             'usergroup.admin')(member):
 
                    self.grant_user_group_permission(
 
                        target_user_group=user_group, user_group=member, perm=perm
 
                    )
 
        # set new permissions
 
        for member, perm, member_type in perms_new:
 
            if member_type == 'user':
 
                self.grant_user_permission(
 
                    user_group=user_group, user=member, perm=perm
 
                )
 
            else:
 
                #check if we have permissions to alter this usergroup
 
                if HasUserGroupPermissionAny('usergroup.read', 'usergroup.write',
 
                                             'usergroup.admin')(member):
 
                    self.grant_user_group_permission(
 
                        target_user_group=user_group, user_group=member, perm=perm
 
                    )
 

	
 
    def get(self, user_group_id, cache=False):
 
        return UserGroup.get(user_group_id)
 

	
 
    def get_group(self, user_group):
 
        return self._get_user_group(user_group)
 

	
 
    def get_by_name(self, name, cache=False, case_insensitive=False):
 
        return UserGroup.get_by_group_name(name, cache, case_insensitive)
 

	
 
    def create(self, name, description, owner, active=True, group_data=None):
 
        try:
 
            new_user_group = UserGroup()
 
            new_user_group.user = self._get_user(owner)
 
            new_user_group.users_group_name = name
 
            new_user_group.user_group_description = description
 
            new_user_group.users_group_active = active
 
            if group_data:
 
                new_user_group.group_data = group_data
 
            self.sa.add(new_user_group)
 
            perm_obj = self._create_default_perms(new_user_group)
 
            self.sa.add(perm_obj)
 

	
 
            self.grant_user_permission(user_group=new_user_group,
 
                                       user=owner, perm='usergroup.admin')
 

	
 
            return new_user_group
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def update(self, user_group, form_data):
 

	
 
        try:
 
            user_group = self._get_user_group(user_group)
 

	
 
            for k, v in form_data.items():
 
                if k == 'users_group_members':
 
                    user_group.members = []
 
                    self.sa.flush()
 
                    members_list = []
 
                    if v:
 
                        v = [v] if isinstance(v, basestring) else v
 
                        for u_id in set(v):
 
                            member = UserGroupMember(user_group.users_group_id, u_id)
 
                            members_list.append(member)
 
                    setattr(user_group, 'members', members_list)
 
                setattr(user_group, k, v)
 

	
 
            self.sa.add(user_group)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def delete(self, user_group, force=False):
 
        """
 
        Deletes user group, unless force flag is used
 
        raises exception if there are members in that group, else deletes
 
        group and users
 

	
 
        :param user_group:
 
        :param force:
 
        """
 
        user_group = self._get_user_group(user_group)
 
        try:
 
            # check if this group is not assigned to repo
 
            assigned_groups = UserGroupRepoToPerm.query()\
 
                .filter(UserGroupRepoToPerm.users_group == user_group).all()
 
            assigned_groups = [x.repository.repo_name for x in assigned_groups]
 

	
 
            if assigned_groups and not force:
 
                raise UserGroupsAssignedException(
 
                    'User Group assigned to %s' % ", ".join(assigned_groups))
 
            self.sa.delete(user_group)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def add_user_to_group(self, user_group, user):
 
        user_group = self._get_user_group(user_group)
 
        user = self._get_user(user)
 

	
 
        for m in user_group.members:
 
            u = m.user
 
            if u.user_id == user.user_id:
 
                # user already in the group, skip
 
                return True
 

	
 
        try:
 
            user_group_member = UserGroupMember()
 
            user_group_member.user = user
 
            user_group_member.users_group = user_group
 

	
 
            user_group.members.append(user_group_member)
 
            user.group_member.append(user_group_member)
 

	
 
            self.sa.add(user_group_member)
 
            return user_group_member
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def remove_user_from_group(self, user_group, user):
 
        user_group = self._get_user_group(user_group)
 
        user = self._get_user(user)
 

	
 
        user_group_member = None
 
        for m in user_group.members:
 
            if m.user.user_id == user.user_id:
 
                # Found this user's membership row
 
                user_group_member = m
 
                break
 

	
 
        if user_group_member:
 
            try:
 
                self.sa.delete(user_group_member)
 
                return True
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                raise
 
        else:
 
            # User isn't in that group
 
            return False
 

	
 
    def has_perm(self, user_group, perm):
 
        user_group = self._get_user_group(user_group)
 
        perm = self._get_perm(perm)
 

	
 
        return UserGroupToPerm.query()\
 
            .filter(UserGroupToPerm.users_group == user_group)\
 
            .filter(UserGroupToPerm.permission == perm).scalar() is not None
 

	
 
    def grant_perm(self, user_group, perm):
 
        user_group = self._get_user_group(user_group)
 
        perm = self._get_perm(perm)
 

	
 
        # if this permission is already granted skip it
 
        _perm = UserGroupToPerm.query()\
 
            .filter(UserGroupToPerm.users_group == user_group)\
 
            .filter(UserGroupToPerm.permission == perm)\
 
            .scalar()
 
        if _perm:
 
            return
 

	
 
        new = UserGroupToPerm()
 
        new.users_group = user_group
 
        new.permission = perm
 
        self.sa.add(new)
 
        return new
 

	
 
    def revokehas_permrevoke_permgrant_perm_perm(self, user_group, perm):
 
    def revoke_perm(self, user_group, perm):
 
        user_group = self._get_user_group(user_group)
 
        perm = self._get_perm(perm)
 

	
 
        obj = UserGroupToPerm.query()\
 
            .filter(UserGroupToPerm.users_group == user_group)\
 
            .filter(UserGroupToPerm.permission == perm).scalar()
 
        if obj:
 
            self.sa.delete(obj)
 

	
 
    def grant_user_permission(self, user_group, user, perm):
 
        """
 
        Grant permission for user on given user group, or update
 
        existing one if found
 

	
 
        :param user_group: Instance of UserGroup, users_group_id,
 
            or users_group_name
 
        :param user: Instance of User, user_id or username
 
        :param perm: Instance of Permission, or permission_name
 
        """
 

	
 
        user_group = self._get_user_group(user_group)
 
        user = self._get_user(user)
 
        permission = self._get_perm(perm)
 

	
 
        # check if we have that permission already
 
        obj = self.sa.query(UserUserGroupToPerm)\
 
            .filter(UserUserGroupToPerm.user == user)\
 
            .filter(UserUserGroupToPerm.user_group == user_group)\
 
            .scalar()
 
        if obj is None:
 
            # create new !
 
            obj = UserUserGroupToPerm()
 
        obj.user_group = user_group
 
        obj.user = user
 
        obj.permission = permission
 
        self.sa.add(obj)
 
        log.debug('Granted perm %s to %s on %s' % (perm, user, user_group))
 
        return obj
 

	
 
    def revoke_user_permission(self, user_group, user):
 
        """
 
        Revoke permission for user on given repository group
 

	
 
        :param user_group: Instance of RepoGroup, repositories_group_id,
 
            or repositories_group name
 
        :param user: Instance of User, user_id or username
 
        """
 

	
 
        user_group = self._get_user_group(user_group)
 
        user = self._get_user(user)
 

	
 
        obj = self.sa.query(UserUserGroupToPerm)\
 
            .filter(UserUserGroupToPerm.user == user)\
 
            .filter(UserUserGroupToPerm.user_group == user_group)\
 
            .scalar()
 
        if obj:
 
            self.sa.delete(obj)
 
            log.debug('Revoked perm on %s on %s' % (user_group, user))
 

	
 
    def grant_user_group_permission(self, target_user_group, user_group, perm):
 
        """
 
        Grant user group permission for given target_user_group
 

	
 
        :param target_user_group:
 
        :param user_group:
 
        :param perm:
 
        """
 
        target_user_group = self._get_user_group(target_user_group)
 
        user_group = self._get_user_group(user_group)
 
        permission = self._get_perm(perm)
 
        # forbid assigning same user group to itself
 
        if target_user_group == user_group:
 
            raise RepoGroupAssignmentError('target repo:%s cannot be '
 
                                           'assigned to itself' % target_user_group)
 

	
 
        # check if we have that permission already
 
        obj = self.sa.query(UserGroupUserGroupToPerm)\
 
            .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
 
            .filter(UserGroupUserGroupToPerm.user_group == user_group)\
 
            .scalar()
 
        if obj is None:
 
            # create new !
 
            obj = UserGroupUserGroupToPerm()
 
        obj.user_group = user_group
 
        obj.target_user_group = target_user_group
 
        obj.permission = permission
 
        self.sa.add(obj)
 
        log.debug('Granted perm %s to %s on %s' % (perm, target_user_group, user_group))
 
        return obj
 

	
 
    def revoke_user_group_permission(self, target_user_group, user_group):
 
        """
 
        Revoke user group permission for given target_user_group
 

	
 
        :param target_user_group:
 
        :param user_group:
 
        """
 
        target_user_group = self._get_user_group(target_user_group)
 
        user_group = self._get_user_group(user_group)
 

	
 
        obj = self.sa.query(UserGroupUserGroupToPerm)\
 
            .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
 
            .filter(UserGroupUserGroupToPerm.user_group == user_group)\
 
            .scalar()
 
        if obj:
 
            self.sa.delete(obj)
 
            log.debug('Revoked perm on %s on %s' % (target_user_group, user_group))
 

	
 
    def enforce_groups(self, user, groups, extern_type=None):
 
        user = self._get_user(user)
 
        log.debug('Enforcing groups %s on user %s' % (user, groups))
 
        current_groups = user.group_member
 
        # find the external created groups
 
        externals = [x.users_group for x in current_groups
 
                     if 'extern_type' in x.users_group.group_data]
 

	
 
        # calculate from what groups user should be removed
 
        # externals that are not in groups
 
        for gr in externals:
 
            if gr.users_group_name not in groups:
 
                log.debug('Removing user %s from user group %s' % (user, gr))
 
                self.remove_user_from_group(gr, user)
 

	
 
        # now we calculate in which groups user should be == groups params
 
        owner = User.get_first_admin().username
 
        for gr in set(groups):
 
            existing_group = UserGroup.get_by_group_name(gr)
 
            if not existing_group:
 
                desc = 'Automatically created from plugin:%s' % extern_type
 
                # we use first admin account to set the owner of the group
 
                existing_group = UserGroupModel().create(gr, desc, owner,
 
                                        group_data={'extern_type': extern_type})
 

	
 
            # we can only add users to special groups created via plugins
 
            managed = 'extern_type' in existing_group.group_data
 
            if managed:
 
                log.debug('Adding user %s to user group %s' % (user, gr))
 
                UserGroupModel().add_user_to_group(existing_group, user)
 
            else:
 
                log.debug('Skipping addition to group %s since it is '
 
                          'not managed by auth plugins' % gr)
kallithea/public/js/base.js
Show inline comments
 
@@ -320,403 +320,409 @@ var YUE = YAHOO.util.Event;
 

	
 
/* Invoke all functions in callbacks */
 
var _run_callbacks = function(callbacks){
 
    if (callbacks !== undefined){
 
        var _l = callbacks.length;
 
        for (var i=0;i<_l;i++){
 
            var func = callbacks[i];
 
            if(typeof(func)=='function'){
 
                try{
 
                    func();
 
                }catch (err){};
 
            }
 
        }
 
    }
 
}
 

	
 
/**
 
 * turns objects into GET query string
 
 */
 
var _toQueryString = function(o) {
 
    if(typeof o !== 'object') {
 
        return false;
 
    }
 
    var _p, _qs = [];
 
    for(_p in o) {
 
        _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
 
    }
 
    return _qs.join('&');
 
};
 

	
 
/**
 
 * Load HTML into DOM using Ajax
 
 *
 
 * @param $target: load html async and place it (or an error message) here
 
 * @param success: success callback function
 
 * @param args: query parameters to pass to url
 
 */
 
function asynchtml(url, $target, success, args){
 
    if(args===undefined){
 
        args=null;
 
    }
 
    $target.html(_TM['Loading ...']).css('opacity','0.3');
 

	
 
    return $.ajax({url: url, data: args, headers: {'X-PARTIAL-XHR': '1'}, cache: false, dataType: 'html'})
 
        .done(function(html) {
 
                $target.html(html);
 
                $target.css('opacity','1.0');
 
                //execute the given original callback
 
                if (success !== undefined && success) {
 
                    success();
 
                }
 
            })
 
        .fail(function(jqXHR, textStatus, errorThrown) {
 
                if (textStatus == "abort")
 
                    return;
 
                $target.html('<span class="error_red">ERROR: {0}</span>'.format(textStatus));
 
                $target.css('opacity','1.0');
 
            })
 
        ;
 
};
 

	
 
var ajaxGET = function(url,success) {
 
    return $.ajax({url: url, headers: {'X-PARTIAL-XHR': '1'}, cache: false})
 
        .done(success)
 
        .fail(function(jqXHR, textStatus, errorThrown) {
 
                if (textStatus == "abort")
 
                    return;
 
                alert("Ajax GET error: " + textStatus);
 
        })
 
        ;
 
};
 

	
 
var ajaxPOST = function(url, postData, success, failure) {
 
    postData['_authentication_token'] = _authentication_token;
 
    var postData = _toQueryString(postData);
 
    if(failure === undefined) {
 
        failure = function(jqXHR, textStatus, errorThrown) {
 
                if (textStatus != "abort")
 
                    alert("Error posting to server: " + textStatus);
 
            };
 
    }
 
    return $.ajax({url: url, data: postData, type: 'POST', headers: {'X-PARTIAL-XHR': '1'}, cache: false})
 
        .done(success)
 
        .fail(failure);
 
};
 

	
 

	
 
/**
 
 * activate .show_more links
 
 * the .show_more must have an id that is the the id of an element to hide prefixed with _
 
 * the parentnode will be displayed
 
 */
 
var show_more_event = function(){
 
    $('.show_more').click(function(e){
 
        var el = e.currentTarget;
 
        $('#' + el.id.substring(1)).hide();
 
        $(el.parentNode).show();
 
    });
 
};
 

	
 
/**
 
 * activate .lazy-cs mouseover for showing changeset tooltip
 
 */
 
var show_changeset_tooltip = function(){
 
    $('.lazy-cs').mouseover(function(e){
 
        var $target = $(e.currentTarget);
 
        var rid = $target.attr('raw_id');
 
        var repo_name = $target.attr('repo_name');
 
        if(rid && !$target.hasClass('tooltip')){
 
            _show_tooltip(e, _TM['loading ...']);
 
            var url = pyroutes.url('changeset_info', {"repo_name": repo_name, "revision": rid});
 
            ajaxGET(url, function(json){
 
                    $target.addClass('tooltip')
 
                    _show_tooltip(e, json['message']);
 
                    _activate_tooltip($target);
 
                });
 
        }
 
    });
 
};
 

	
 
var _onSuccessFollow = function(target){
 
    var $target = $(target);
 
    var $f_cnt = $('#current_followers_count');
 
    if($target.hasClass('follow')){
 
        $target.attr('class', 'following');
 
        $target.attr('title', _TM['Stop following this repository']);
 
        if($f_cnt.html()){
 
            var cnt = Number($f_cnt.html())+1;
 
            $f_cnt.html(cnt);
 
        }
 
    }
 
    else{
 
        $target.attr('class', 'follow');
 
        $target.attr('title', _TM['Start following this repository']);
 
        if($f_cnt.html()){
 
            var cnt = Number($f_cnt.html())-1;
 
            $f_cnt.html(cnt);
 
        }
 
    }
 
}
 

	
 
var toggleFollowingRepo = function(target, follows_repo_id){
 
    var args = 'follows_repo_id=' + follows_repo_id;
 
    args += '&amp;_authentication_token=' + _authentication_token;
 
    $.post(TOGGLE_FOLLOW_URL, args, function(data){
 
            _onSuccessFollow(target);
 
        });
 
    return false;
 
};
 

	
 
var showRepoSize = function(target, repo_name){
 
    var args = '_authentication_token=' + _authentication_token;
 

	
 
    if(!$("#" + target).hasClass('loaded')){
 
        $("#" + target).html(_TM['Loading ...']);
 
        var url = pyroutes.url('repo_size', {"repo_name":repo_name});
 
        $.post(url, args, function(data) {
 
            $("#" + target).html(data);
 
            $("#" + target).addClass('loaded');
 
        });
 
    }
 
    return false;
 
};
 

	
 
/**
 
 * tooltips
 
 */
 

	
 
var tooltip_activate = function(){
 
    $(document).ready(_init_tooltip);
 
};
 

	
 
var _activate_tooltip = function($tt){
 
    $tt.mouseover(_show_tooltip);
 
    $tt.mousemove(_move_tooltip);
 
    $tt.mouseout(_close_tooltip);
 
};
 

	
 
var _init_tooltip = function(){
 
    var $tipBox = $('#tip-box');
 
    if(!$tipBox.length){
 
        $tipBox = $('<div id="tip-box"></div>')
 
        $(document.body).append($tipBox);
 
    }
 

	
 
    $tipBox.hide();
 
    $tipBox.css('position', 'absolute');
 
    $tipBox.css('max-width', '600px');
 

	
 
    _activate_tooltip($('.tooltip'));
 
};
 

	
 
var _show_tooltip = function(e, tipText){
 
var _show_tooltip = function(e, tipText, safe){
 
    e.stopImmediatePropagation();
 
    var el = e.currentTarget;
 
    var $el = $(el);
 
    if(tipText){
 
        // just use it
 
    } else if(el.tagName.toLowerCase() === 'img'){
 
        tipText = el.alt ? el.alt : '';
 
    } else {
 
        tipText = el.title ? el.title : '';
 
        safe = safe || $el.hasClass("safe-html-title");
 
    }
 

	
 
    if(tipText !== ''){
 
        // save org title
 
        $(el).attr('tt_title', tipText);
 
        $el.attr('tt_title', tipText);
 
        // reset title to not show org tooltips
 
        $(el).attr('title', '');
 
        $el.attr('title', '');
 

	
 
        var $tipBox = $('#tip-box');
 
        if (safe) {
 
        $tipBox.html(tipText);
 
        } else {
 
            $tipBox.text(tipText);
 
        }
 
        $tipBox.css('display', 'block');
 
    }
 
};
 

	
 
var _move_tooltip = function(e){
 
    e.stopImmediatePropagation();
 
    var $tipBox = $('#tip-box');
 
    $tipBox.css('top', (e.pageY + 15) + 'px');
 
    $tipBox.css('left', (e.pageX + 15) + 'px');
 
};
 

	
 
var _close_tooltip = function(e){
 
    e.stopImmediatePropagation();
 
    var $tipBox = $('#tip-box');
 
    $tipBox.hide();
 
    var el = e.currentTarget;
 
    $(el).attr('title', $(el).attr('tt_title'));
 
};
 

	
 
/**
 
 * Quick filter widget
 
 *
 
 * @param target: filter input target
 
 * @param nodes: list of nodes in html we want to filter.
 
 * @param display_element function that takes current node from nodes and
 
 *    does hide or show based on the node
 
 */
 
var q_filter = (function() {
 
    var _namespace = {};
 
    var namespace = function (target) {
 
        if (!(target in _namespace)) {
 
            _namespace[target] = {};
 
        }
 
        return _namespace[target];
 
    };
 
    return function (target, $nodes, display_element) {
 
        var $nodes = $nodes;
 
        var $q_filter_field = $('#' + target);
 
        var F = namespace(target);
 

	
 
        $q_filter_field.keyup(function (e) {
 
            clearTimeout(F.filterTimeout);
 
            F.filterTimeout = setTimeout(F.updateFilter, 600);
 
        });
 

	
 
        F.filterTimeout = null;
 

	
 
        F.updateFilter = function () {
 
            // Reset timeout
 
            F.filterTimeout = null;
 

	
 
            var obsolete = [];
 

	
 
            var req = $q_filter_field.val().toLowerCase();
 

	
 
            var showing = 0;
 
            $nodes.each(function () {
 
                var n = this;
 
                var target_element = display_element(n);
 
                if (req && n.innerHTML.toLowerCase().indexOf(req) == -1) {
 
                    $(target_element).hide();
 
                }
 
                else {
 
                    $(target_element).show();
 
                    showing += 1;
 
                }
 
            });
 

	
 
            $('#repo_count').html(showing);
 
            /* FIXME: don't hardcode */
 
        }
 
    }
 
})();
 

	
 
/* return jQuery expression with a tr with body in 3rd column and class cls and id named after the body */
 
var _table_tr = function(cls, body){
 
    // like: <div class="comment" id="comment-8" line="o92"><div class="comment-wrapp">...
 
    // except new inlines which are different ...
 
    var comment_id = ($(body).attr('id') || 'comment-new').split('comment-')[1];
 
    var tr_id = 'comment-tr-{0}'.format(comment_id);
 
    return $(('<tr id="{0}" class="{1}">'+
 
                  '<td class="lineno-inline new-inline"></td>'+
 
                  '<td class="lineno-inline old-inline"></td>'+
 
                  '<td>{2}</td>'+
 
                 '</tr>').format(tr_id, cls, body));
 
};
 

	
 
/** return jQuery expression with new inline form based on template **/
 
var _createInlineForm = function(parent_tr, f_path, line) {
 
    var $tmpl = $('#comment-inline-form-template').html().format(f_path, line);
 
    var $form = _table_tr('comment-form-inline', $tmpl)
 

	
 
    // create event for hide button
 
    $form.find('.hide-inline-form').click(function(e) {
 
        var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
 
        if($(newtr).next().hasClass('inline-comments-button')){
 
            $(newtr).next().show();
 
        }
 
        $(newtr).remove();
 
        $(parent_tr).removeClass('form-open');
 
        $(parent_tr).removeClass('hl-comment');
 
    });
 

	
 
    return $form
 
};
 

	
 
/**
 
 * Inject inline comment for an given TR. This tr should always be a .line .
 
 * The form will be inject after any comments.
 
 */
 
var injectInlineForm = function(tr){
 
    var $tr = $(tr);
 
    if(!$tr.hasClass('line')){
 
        return
 
    }
 
    var submit_url = AJAX_COMMENT_URL;
 
    var $td = $tr.find('.code');
 
    if($tr.hasClass('form-open') || $tr.hasClass('context') || $td.hasClass('no-comment')){
 
        return
 
    }
 
    $tr.addClass('form-open hl-comment');
 
    var $node = $tr.parent().parent().parent().find('.full_f_path');
 
    var f_path = $node.attr('path');
 
    var lineno = _getLineNo(tr);
 
    var $form = _createInlineForm(tr, f_path, lineno, submit_url);
 

	
 
    var $parent = $tr;
 
    while ($parent.next().hasClass('inline-comments')){
 
        var $parent = $parent.next();
 
    }
 
    $form.insertAfter($parent);
 
    var $overlay = $form.find('.submitting-overlay');
 
    var $inlineform = $form.find('.inline-form');
 

	
 
    $form.submit(function(e){
 
        e.preventDefault();
 

	
 
        if(lineno === undefined){
 
            alert('Error submitting, line ' + lineno + ' not found.');
 
            return;
 
        }
 
        if(f_path === undefined){
 
            alert('Error submitting, file path ' + f_path + ' not found.');
 
            return;
 
        }
 

	
 
        var text = $('#text_'+lineno).val();
 
        if(text == ""){
 
            return;
 
        }
 

	
 
        $overlay.show();
 

	
 
        var success = function(json_data){
 
            $tr.removeClass('form-open');
 
            $form.remove();
 
            _renderInlineComment(json_data);
 
            linkInlineComments($('.firstlink'), $('.comment'));
 
        };
 
        var postData = {
 
                'text': text,
 
                'f_path': f_path,
 
                'line': lineno
 
        };
 
        ajaxPOST(submit_url, postData, success);
 
    });
 

	
 
    $('#preview-btn_'+lineno).click(function(e){
 
        var text = $('#text_'+lineno).val();
 
        if(!text){
 
            return
 
        }
 
        $('#preview-box_'+lineno).addClass('unloaded');
 
        $('#preview-box_'+lineno).html(_TM['Loading ...']);
 
        $('#edit-container_'+lineno).hide();
 
        $('#edit-btn_'+lineno).show();
 
        $('#preview-container_'+lineno).show();
 
        $('#preview-btn_'+lineno).hide();
 

	
 
        var url = pyroutes.url('changeset_comment_preview', {'repo_name': REPO_NAME});
 
        var post_data = {'text': text};
 
        ajaxPOST(url, post_data, function(html){
 
            $('#preview-box_'+lineno).html(html);
 
            $('#preview-box_'+lineno).removeClass('unloaded');
 
        })
 
    })
 
    $('#edit-btn_'+lineno).click(function(e){
 
        $('#edit-container_'+lineno).show();
 
        $('#edit-btn_'+lineno).hide();
 
        $('#preview-container_'+lineno).hide();
 
        $('#preview-btn_'+lineno).show();
 
    })
kallithea/templates/admin/permissions/permissions_globals.html
Show inline comments
 
${h.form(url('admin_permissions'), method='post')}
 
    <div class="form">
 
        <!-- fields -->
 
        <div class="fields">
 
            <div class="field">
 
                <div class="label label-checkbox">
 
                    <label for="anonymous">${_('Anonymous access')}:</label>
 
                </div>
 
                <div class="checkboxes">
 
                    <div class="checkbox">
 
                        ${h.checkbox('anonymous',True)}
 
                    </div>
 
                     <span class="help-block">${h.literal(_('Allow access to Kallithea without needing to log in. Anonymous users use %s user permissions.' % (h.link_to('*default*',h.url('admin_permissions_perms')))))}</span>
 
                </div>
 
            </div>
 
            <div class="field">
 
                <div class="label">
 
                    <label for="default_repo_perm">${_('Repository')}:</label>
 
                </div>
 
                <div class="select">
 
                    ${h.select('default_repo_perm','',c.repo_perms_choices)}
 

	
 
                    ${h.checkbox('overwrite_default_repo','true')}
 
                    <label for="overwrite_default_repo">
 
                    <span class="tooltip"
 
                    title="${h.tooltip(_('All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost'))}">
 
                    ${_('Overwrite existing settings')}</span> </label>
 
                    ${_('Apply to all existing repositories')}</span> </label>
 
                    <span class="help-block">${_('Permissions for the Default user on new repositories.')}</span>
 
                </div>
 
            </div>
 
            <div class="field">
 
                <div class="label">
 
                    <label for="default_group_perm">${_('Repository group')}:</label>
 
                </div>
 
                <div class="select">
 
                    ${h.select('default_group_perm','',c.group_perms_choices)}
 
                    ${h.checkbox('overwrite_default_group','true')}
 
                    <label for="overwrite_default_group">
 
                    <span class="tooltip"
 
                    title="${h.tooltip(_('All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost'))}">
 
                    ${_('Overwrite existing settings')}</span> </label>
 

	
 
                    ${_('Apply to all existing repository groups')}</span> </label>
 
                    <span class="help-block">${_('Permissions for the Default user on new repository groups.')}</span>
 
                </div>
 
            </div>
 
            <div class="field">
 
                <div class="label">
 
                    <label for="default_group_perm">${_('User group')}:</label>
 
                </div>
 
                <div class="select">
 
                    ${h.select('default_user_group_perm','',c.user_group_perms_choices)}
 
                    ${h.checkbox('overwrite_default_user_group','true')}
 
                    <label for="overwrite_default_user_group">
 
                    <span class="tooltip"
 
                    title="${h.tooltip(_('All default permissions on each user group will be reset to chosen permission, note that all custom default permission on user groups will be lost'))}">
 
                    ${_('Overwrite existing settings')}</span> </label>
 

	
 
                    ${_('Apply to all existing user groups')}</span></label>
 
                    <span class="help-block">${_('Permissions for the Default user on new user groups.')}</span>
 
                </div>
 
            </div>
 
             <div class="field">
 
                <div class="label">
 
                    <label for="default_repo_create">${_('Repository creation')}:</label>
 
                    <label for="default_repo_create">${_('Top level repository creation')}:</label>
 
                </div>
 
                <div class="select">
 
                    ${h.select('default_repo_create','',c.repo_create_choices)}
 
                    <span class="help-block">${_('Enable this to allow non-admins to create repositories at the top level.')}</span>
 
                    <span class="help-block">${_('Note: This will also give all users API access to create repositories everywhere. That might change in future versions.')}</span>
 
                </div>
 
             </div>
 
            <div class="field">
 
                <div class="label label-checkbox">
 
                    <label for="create_on_write">${_('Repository creation with group write access')}:</label>
 
                </div>
 
                <div class="select">
 
                    ${h.select('create_on_write','',c.repo_create_on_write_choices)}
 
                    <span class="help-block">${_('Write permission to a repository group allows creating repositories inside that group.')}</span>
 
                    <span class="help-block">${_('With this, write permission to a repository group allows creating repositories inside that group. Without this, group write permissions mean nothing.')}</span>
 
                </div>
 
            </div>
 
             <div class="field">
 
                <div class="label">
 
                    <label for="default_user_group_create">${_('User group creation')}:</label>
 
                </div>
 
                <div class="select">
 
                    ${h.select('default_user_group_create','',c.user_group_create_choices)}
 
                    <span class="help-block">${_('Enable this to allow non-admins to create user groups.')}</span>
 
                </div>
 
             </div>
 
             <div class="field">
 
                <div class="label">
 
                    <label for="default_fork">${_('Repository forking')}:</label>
 
                </div>
 
                <div class="select">
 
                    ${h.select('default_fork','',c.fork_choices)}
 
                    <span class="help-block">${_('Enable this to allow non-admins to fork repositories.')}</span>
 
                </div>
 
             </div>
 
             <div class="field">
 
                <div class="label">
 
                    <label for="default_register">${_('Registration')}:</label>
 
                </div>
 
                <div class="select">
 
                    ${h.select('default_register','',c.register_choices)}
 
                </div>
 
             </div>
 
             <div class="field">
 
                <div class="label">
 
                    <label for="default_extern_activate">${_('External auth account activation')}:</label>
 
                </div>
 
                <div class="select">
 
                    ${h.select('default_extern_activate','',c.extern_activate_choices)}
 
                </div>
 
             </div>
 
            <div class="buttons">
 
              ${h.submit('save',_('Save'),class_="btn")}
 
              ${h.reset('reset',_('Reset'),class_="btn")}
 
            </div>
 
        </div>
 
    </div>
 
${h.end_form()}
kallithea/templates/data_table/_dt_elements.html
Show inline comments
 
## DATA TABLE RE USABLE ELEMENTS
 
## usage:
 
## <%namespace name="dt" file="/data_table/_dt_elements.html"/>
 

	
 
<%namespace name="base" file="/base/base.html"/>
 

	
 
<%def name="quick_menu(repo_name)">
 
  <ul class="menu_items hidden">
 
  ##<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu">
 

	
 
    <li style="border-top:1px solid #577632; margin-left: 21px; padding-left: -99px;"></li>
 
    <li>
 
       <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
 
       <span class="icon">
 
           <i class="icon-doc-text-inv"></i>
 
       </span>
 
       <span>${_('Summary')}</span>
 
       </a>
 
    </li>
 
    <li>
 
       <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
 
       <span class="icon">
 
           <i class="icon-clock"></i>
 
       </span>
 
       <span>${_('Changelog')}</span>
 
       </a>
 
    </li>
 
    <li>
 
       <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
 
       <span class="icon">
 
           <i class="icon-docs"></i>
 
       </span>
 
       <span>${_('Files')}</span>
 
       </a>
 
    </li>
 
    <li>
 
       <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
 
       <span class="icon">
 
           <i class="icon-fork"></i>
 
       </span>
 
       <span>${_('Fork')}</span>
 
       </a>
 
    </li>
 
    <li>
 
       <a title="${_('Settings')}" href="${h.url('edit_repo',repo_name=repo_name)}">
 
       <span class="icon">
 
           <i class="icon-gear"></i>
 
       </span>
 
       <span>${_('Settings')}</span>
 
       </a>
 
    </li>
 
  </ul>
 
</%def>
 

	
 
<%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
 
    <%
 
    def get_name(name,short_name=short_name):
 
      if short_name:
 
        return name.split('/')[-1]
 
      else:
 
        return name
 
    %>
 
  <div class="dt_repo ${'dt_repo_pending' if rstate == 'repo_state_pending' else ''}">
 
    ##NAME
 
    <a href="${h.url('edit_repo' if admin else 'summary_home', repo_name=name)}">
 

	
 
    ##TYPE OF REPO
 
    ${base.repotag(rtype)}
 

	
 
    ##PRIVATE/PUBLIC
 
    %if private and c.visual.show_private_icon:
 
      <i class="icon-keyhole-circled" title="${_('Private repository')}"></i>
 
    %elif not private and c.visual.show_public_icon:
 
      <i class="icon-globe" title="${_('Public repository')}"></i>
 
    %else:
 
      <span style="margin: 0px 8px 0px 8px"></span>
 
    %endif
 
    <span class="dt_repo_name">${get_name(name)}</span>
 
    </a>
 
    %if fork_of:
 
      <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-fork"></i></a>
 
    %endif
 
    %if rstate == 'repo_state_pending':
 
      <i class="icon-wrench" title="${_('Repository creation in progress...')}"></i>
 
    %endif
 
  </div>
 
</%def>
 

	
 
<%def name="last_change(last_change)">
 
  <span class="tooltip" date="${last_change}" title="${h.tooltip(h.fmt_date(last_change))}">${h.age(last_change)}</span>
 
</%def>
 

	
 
<%def name="revision(name,rev,tip,author,last_msg)">
 
  <div>
 
  %if rev >= 0:
 
      <a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip revision-link" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a>
 
      <a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip revision-link safe-html-title" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a>
 
  %else:
 
      ${_('No changesets yet')}
 
  %endif
 
  </div>
 
</%def>
 

	
 
<%def name="rss(name)">
 
  %if c.authuser.username != 'default':
 
    <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,api_key=c.authuser.api_key)}"><i class="icon-rss-squared"></i></a>
 
  %else:
 
    <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-squared"></i></a>
 
  %endif
 
</%def>
 

	
 
<%def name="atom(name)">
 
  %if c.authuser.username != 'default':
 
    <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,api_key=c.authuser.api_key)}"><i class="icon-rss-squared"></i></a>
 
  %else:
 
    <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-squared"></i></a>
 
  %endif
 
</%def>
 

	
 
<%def name="repo_actions(repo_name, super_user=True)">
 
  <div>
 
    <div style="float:left; margin-right:5px;" class="grid_edit">
 
      <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
 
        <i class="icon-pencil"></i> ${h.submit('edit_%s' % repo_name,_('Edit'),class_="action_button")}
 
      </a>
 
    </div>
 
    <div style="float:left" class="grid_delete">
 
      ${h.form(h.url('repo', repo_name=repo_name),method='delete')}
 
        <i class="icon-minus-circled" style="color:#FF4444"></i>
 
        ${h.submit('remove_%s' % repo_name,_('Delete'),class_="action_button",
 
        onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
 
      ${h.end_form()}
 
    </div>
 
  </div>
 
</%def>
 

	
 
<%def name="repo_state(repo_state)">
 
  <div>
 
    %if repo_state == 'repo_state_pending':
 
        <div class="btn btn-mini btn-info disabled">${_('Creating')}</div>
 
    %elif repo_state == 'repo_state_created':
 
        <div class="btn btn-mini btn-success disabled">${_('Created')}</div>
 
    %else:
 
        <div class="btn btn-mini btn-danger disabled" title="${repo_state}">invalid</div>
 
    %endif
 
  </div>
 
</%def>
 

	
 
<%def name="user_actions(user_id, username)">
 
 <div style="float:left" class="grid_edit">
 
   <a href="${h.url('edit_user',id=user_id)}" title="${_('Edit')}">
 
     <i class="icon-pencil"></i> ${h.submit('edit_%s' % username,_('Edit'),class_="action_button")}
 
   </a>
 
 </div>
 
 <div style="float:left" class="grid_delete">
 
  ${h.form(h.url('delete_user', id=user_id),method='delete')}
 
    <i class="icon-minus-circled" style="color:#FF4444"></i>
 
    ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="action_button",
 
    onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
 
  ${h.end_form()}
 
 </div>
 
</%def>
 

	
 
<%def name="user_group_actions(user_group_id, user_group_name)">
 
 <div style="float:left" class="grid_edit">
 
    <a href="${h.url('edit_users_group', id=user_group_id)}" title="${_('Edit')}">
 
    <i class="icon-pencil"></i>
 
     ${h.submit('edit_%s' % user_group_name,_('Edit'),class_="action_button", id_="submit_user_group_edit")}
 
    </a>
 
 </div>
 
 <div style="float:left" class="grid_delete">
 
    ${h.form(h.url('users_group', id=user_group_id),method='delete')}
 
      <i class="icon-minus-circled" style="color:#FF4444"></i>
 
      ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="action_button",
 
      onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
 
    ${h.end_form()}
 
 </div>
 
</%def>
 

	
 
<%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
 
 <div style="float:left" class="grid_edit">
 
    <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">
 
    <i class="icon-pencil"></i>
 
     ${h.submit('edit_%s' % repo_group_name, _('Edit'),class_="action_button")}
 
    </a>
 
 </div>
 
 <div style="float:left" class="grid_delete">
 
    ${h.form(h.url('repos_group', group_name=repo_group_name),method='delete')}
 
        <i class="icon-minus-circled" style="color:#FF4444"></i>
 
        ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="action_button",
 
        onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
 
    ${h.end_form()}
 
 </div>
 
</%def>
 

	
 
<%def name="user_name(user_id, username)">
 
    ${h.link_to(username,h.url('edit_user', id=user_id))}
 
</%def>
 

	
 
<%def name="repo_group_name(repo_group_name, children_groups)">
 
  <div style="white-space: nowrap">
 
  <a href="${h.url('repos_group_home',group_name=repo_group_name)}">
 
    <i class="icon-folder" title="${_('Repository group')}"></i> ${h.literal(' &raquo; '.join(children_groups))}</a>
 
  </div>
 
</%def>
 

	
 
<%def name="user_group_name(user_group_id, user_group_name)">
 
  <div style="white-space: nowrap">
 
  <a href="${h.url('edit_users_group', id=user_group_id)}">
 
    <i class="icon-users" title="${_('User group')}"></i> ${user_group_name}</a>
 
  </div>
 
</%def>
 

	
 
<%def name="toggle_follow(repo_id)">
 
  <span id="follow_toggle_${repo_id}" class="following" title="${_('Stop following this repository')}"
 
        onclick="javascript:toggleFollowingRepo(this, ${repo_id})">
 
  </span>
 
</%def>
kallithea/tests/api/api_base.py
Show inline comments
 
@@ -989,509 +989,589 @@ class _BaseTestApi(object):
 
        ret = {
 
            'msg': 'Created new repository `%s`' % repo_name,
 
            'success': True,
 
            'task': None,
 
        }
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 
        fixture.destroy_repo(repo_name)
 

	
 
    def test_api_create_repo_in_group(self):
 
        repo_name = 'my_gr/api-repo'
 
        id_, params = _build_data(self.apikey, 'create_repo',
 
                                  repo_name=repo_name,
 
                                  owner=TEST_USER_ADMIN_LOGIN,
 
                                  repo_type=self.REPO_TYPE,)
 
        response = api_call(self, params)
 
        print params
 
        repo = RepoModel().get_by_repo_name(repo_name)
 
        self.assertNotEqual(repo, None)
 
        ret = {
 
            'msg': 'Created new repository `%s`' % repo_name,
 
            'success': True,
 
            'task': None,
 
        }
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 
        fixture.destroy_repo(repo_name)
 
        fixture.destroy_repo_group('my_gr')
 

	
 
    def test_api_create_repo_unknown_owner(self):
 
        repo_name = 'api-repo'
 
        owner = 'i-dont-exist'
 
        id_, params = _build_data(self.apikey, 'create_repo',
 
                                  repo_name=repo_name,
 
                                  owner=owner,
 
                                  repo_type=self.REPO_TYPE,
 
        )
 
        response = api_call(self, params)
 
        expected = 'user `%s` does not exist' % owner
 
        self._compare_error(id_, expected, given=response.body)
 

	
 
    def test_api_create_repo_dont_specify_owner(self):
 
        repo_name = 'api-repo'
 
        owner = 'i-dont-exist'
 
        id_, params = _build_data(self.apikey, 'create_repo',
 
                                  repo_name=repo_name,
 
                                  repo_type=self.REPO_TYPE,
 
        )
 
        response = api_call(self, params)
 

	
 
        repo = RepoModel().get_by_repo_name(repo_name)
 
        self.assertNotEqual(repo, None)
 
        ret = {
 
            'msg': 'Created new repository `%s`' % repo_name,
 
            'success': True,
 
            'task': None,
 
        }
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 
        fixture.destroy_repo(repo_name)
 

	
 
    def test_api_create_repo_by_non_admin(self):
 
        repo_name = 'api-repo'
 
        owner = 'i-dont-exist'
 
        id_, params = _build_data(self.apikey_regular, 'create_repo',
 
                                  repo_name=repo_name,
 
                                  repo_type=self.REPO_TYPE,
 
        )
 
        response = api_call(self, params)
 

	
 
        repo = RepoModel().get_by_repo_name(repo_name)
 
        self.assertNotEqual(repo, None)
 
        ret = {
 
            'msg': 'Created new repository `%s`' % repo_name,
 
            'success': True,
 
            'task': None,
 
        }
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 
        fixture.destroy_repo(repo_name)
 

	
 
    def test_api_create_repo_by_non_admin_specify_owner(self):
 
        repo_name = 'api-repo'
 
        owner = 'i-dont-exist'
 
        id_, params = _build_data(self.apikey_regular, 'create_repo',
 
                                  repo_name=repo_name,
 
                                  repo_type=self.REPO_TYPE,
 
                                  owner=owner)
 
        response = api_call(self, params)
 

	
 
        expected = 'Only Kallithea admin can specify `owner` param'
 
        self._compare_error(id_, expected, given=response.body)
 
        fixture.destroy_repo(repo_name)
 

	
 
    def test_api_create_repo_exists(self):
 
        repo_name = self.REPO
 
        id_, params = _build_data(self.apikey, 'create_repo',
 
                                  repo_name=repo_name,
 
                                  owner=TEST_USER_ADMIN_LOGIN,
 
                                  repo_type=self.REPO_TYPE,)
 
        response = api_call(self, params)
 
        expected = "repo `%s` already exist" % repo_name
 
        self._compare_error(id_, expected, given=response.body)
 

	
 
    @mock.patch.object(RepoModel, 'create', crash)
 
    def test_api_create_repo_exception_occurred(self):
 
        repo_name = 'api-repo'
 
        id_, params = _build_data(self.apikey, 'create_repo',
 
                                  repo_name=repo_name,
 
                                  owner=TEST_USER_ADMIN_LOGIN,
 
                                  repo_type=self.REPO_TYPE,)
 
        response = api_call(self, params)
 
        expected = 'failed to create repository `%s`' % repo_name
 
        self._compare_error(id_, expected, given=response.body)
 

	
 
    @parameterized.expand([
 
        ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
 
        ('description', {'description': 'new description'}),
 
        ('active', {'active': True}),
 
        ('active', {'active': False}),
 
        ('clone_uri', {'clone_uri': 'http://foo.com/repo'}),
 
        ('clone_uri', {'clone_uri': None}),
 
        ('landing_rev', {'landing_rev': 'branch:master'}),
 
        ('enable_statistics', {'enable_statistics': True}),
 
        ('enable_locking', {'enable_locking': True}),
 
        ('enable_downloads', {'enable_downloads': True}),
 
        ('name', {'name': 'new_repo_name'}),
 
        ('repo_group', {'group': 'test_group_for_update'}),
 
    ])
 
    def test_api_update_repo(self, changing_attr, updates):
 
        repo_name = 'api_update_me'
 
        repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
 
        if changing_attr == 'repo_group':
 
            fixture.create_repo_group(updates['group'])
 

	
 
        id_, params = _build_data(self.apikey, 'update_repo',
 
                                  repoid=repo_name, **updates)
 
        response = api_call(self, params)
 
        if changing_attr == 'name':
 
            repo_name = updates['name']
 
        if changing_attr == 'repo_group':
 
            repo_name = '/'.join([updates['group'], repo_name])
 
        try:
 
            expected = {
 
                'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
 
                'repository': repo.get_api_data()
 
            }
 
            self._compare_ok(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_repo(repo_name)
 
            if changing_attr == 'repo_group':
 
                fixture.destroy_repo_group(updates['group'])
 

	
 
    def test_api_update_repo_repo_group_does_not_exist(self):
 
        repo_name = 'admin_owned'
 
        fixture.create_repo(repo_name)
 
        updates = {'group': 'test_group_for_update'}
 
        id_, params = _build_data(self.apikey, 'update_repo',
 
                                  repoid=repo_name, **updates)
 
        response = api_call(self, params)
 
        try:
 
            expected = 'repository group `%s` does not exist' % updates['group']
 
            self._compare_error(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_repo(repo_name)
 

	
 
    def test_api_update_repo_regular_user_not_allowed(self):
 
        repo_name = 'admin_owned'
 
        fixture.create_repo(repo_name)
 
        updates = {'active': False}
 
        id_, params = _build_data(self.apikey_regular, 'update_repo',
 
                                  repoid=repo_name, **updates)
 
        response = api_call(self, params)
 
        try:
 
            expected = 'repository `%s` does not exist' % repo_name
 
            self._compare_error(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_repo(repo_name)
 

	
 
    @mock.patch.object(RepoModel, 'update', crash)
 
    def test_api_update_repo_exception_occurred(self):
 
        repo_name = 'api_update_me'
 
        fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
 
        id_, params = _build_data(self.apikey, 'update_repo',
 
                                  repoid=repo_name, owner=TEST_USER_ADMIN_LOGIN,)
 
        response = api_call(self, params)
 
        try:
 
            expected = 'failed to update repo `%s`' % repo_name
 
            self._compare_error(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_repo(repo_name)
 

	
 
    def test_api_update_repo_regular_user_change_repo_name(self):
 
        repo_name = 'admin_owned'
 
        new_repo_name = 'new_repo_name'
 
        fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
 
        RepoModel().grant_user_permission(repo=repo_name,
 
                                          user=self.TEST_USER_LOGIN,
 
                                          perm='repository.admin')
 
        UserModel().revoke_perm('default', 'hg.create.repository')
 
        UserModel().grant_perm('default', 'hg.create.none')
 
        updates = {'name': new_repo_name}
 
        id_, params = _build_data(self.apikey_regular, 'update_repo',
 
                                  repoid=repo_name, **updates)
 
        response = api_call(self, params)
 
        try:
 
            expected = 'no permission to create (or move) repositories'
 
            self._compare_error(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_repo(repo_name)
 
            fixture.destroy_repo(new_repo_name)
 

	
 
    def test_api_update_repo_regular_user_change_repo_name_allowed(self):
 
        repo_name = 'admin_owned'
 
        new_repo_name = 'new_repo_name'
 
        repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
 
        RepoModel().grant_user_permission(repo=repo_name,
 
                                          user=self.TEST_USER_LOGIN,
 
                                          perm='repository.admin')
 
        UserModel().revoke_perm('default', 'hg.create.none')
 
        UserModel().grant_perm('default', 'hg.create.repository')
 
        updates = {'name': new_repo_name}
 
        id_, params = _build_data(self.apikey_regular, 'update_repo',
 
                                  repoid=repo_name, **updates)
 
        response = api_call(self, params)
 
        try:
 
            expected = {
 
                'msg': 'updated repo ID:%s %s' % (repo.repo_id, new_repo_name),
 
                'repository': repo.get_api_data()
 
            }
 
            self._compare_ok(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_repo(repo_name)
 
            fixture.destroy_repo(new_repo_name)
 

	
 
    def test_api_update_repo_regular_user_change_owner(self):
 
        repo_name = 'admin_owned'
 
        fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
 
        RepoModel().grant_user_permission(repo=repo_name,
 
                                          user=self.TEST_USER_LOGIN,
 
                                          perm='repository.admin')
 
        updates = {'owner': TEST_USER_ADMIN_LOGIN}
 
        id_, params = _build_data(self.apikey_regular, 'update_repo',
 
                                  repoid=repo_name, **updates)
 
        response = api_call(self, params)
 
        try:
 
            expected = 'Only Kallithea admin can specify `owner` param'
 
            self._compare_error(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_repo(repo_name)
 

	
 
    def test_api_delete_repo(self):
 
        repo_name = 'api_delete_me'
 
        fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
 

	
 
        id_, params = _build_data(self.apikey, 'delete_repo',
 
                                  repoid=repo_name, )
 
        response = api_call(self, params)
 

	
 
        ret = {
 
            'msg': 'Deleted repository `%s`' % repo_name,
 
            'success': True
 
        }
 
        try:
 
            expected = ret
 
            self._compare_ok(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_repo(repo_name)
 

	
 
    def test_api_delete_repo_by_non_admin(self):
 
        repo_name = 'api_delete_me'
 
        fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
 
                            cur_user=self.TEST_USER_LOGIN)
 
        id_, params = _build_data(self.apikey_regular, 'delete_repo',
 
                                  repoid=repo_name, )
 
        response = api_call(self, params)
 

	
 
        ret = {
 
            'msg': 'Deleted repository `%s`' % repo_name,
 
            'success': True
 
        }
 
        try:
 
            expected = ret
 
            self._compare_ok(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_repo(repo_name)
 

	
 
    def test_api_delete_repo_by_non_admin_no_permission(self):
 
        repo_name = 'api_delete_me'
 
        fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
 
        try:
 
            id_, params = _build_data(self.apikey_regular, 'delete_repo',
 
                                      repoid=repo_name, )
 
            response = api_call(self, params)
 
            expected = 'repository `%s` does not exist' % (repo_name)
 
            self._compare_error(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_repo(repo_name)
 

	
 
    def test_api_delete_repo_exception_occurred(self):
 
        repo_name = 'api_delete_me'
 
        fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
 
        try:
 
            with mock.patch.object(RepoModel, 'delete', crash):
 
                id_, params = _build_data(self.apikey, 'delete_repo',
 
                                          repoid=repo_name, )
 
                response = api_call(self, params)
 

	
 
                expected = 'failed to delete repository `%s`' % repo_name
 
                self._compare_error(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_repo(repo_name)
 

	
 
    def test_api_fork_repo(self):
 
        fork_name = 'api-repo-fork'
 
        id_, params = _build_data(self.apikey, 'fork_repo',
 
                                  repoid=self.REPO,
 
                                  fork_name=fork_name,
 
                                  owner=TEST_USER_ADMIN_LOGIN,
 
        )
 
        response = api_call(self, params)
 

	
 
        ret = {
 
            'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
 
                                                     fork_name),
 
            'success': True,
 
            'task': None,
 
        }
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 
        fixture.destroy_repo(fork_name)
 

	
 
    def test_api_fork_repo_non_admin(self):
 
        fork_name = 'api-repo-fork'
 
        id_, params = _build_data(self.apikey_regular, 'fork_repo',
 
                                  repoid=self.REPO,
 
                                  fork_name=fork_name,
 
        )
 
        response = api_call(self, params)
 

	
 
        ret = {
 
            'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
 
                                                     fork_name),
 
            'success': True,
 
            'task': None,
 
        }
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 
        fixture.destroy_repo(fork_name)
 

	
 
    def test_api_fork_repo_non_admin_specify_owner(self):
 
        fork_name = 'api-repo-fork'
 
        id_, params = _build_data(self.apikey_regular, 'fork_repo',
 
                                  repoid=self.REPO,
 
                                  fork_name=fork_name,
 
                                  owner=TEST_USER_ADMIN_LOGIN,
 
        )
 
        response = api_call(self, params)
 
        expected = 'Only Kallithea admin can specify `owner` param'
 
        self._compare_error(id_, expected, given=response.body)
 
        fixture.destroy_repo(fork_name)
 

	
 
    def test_api_fork_repo_non_admin_no_permission_to_fork(self):
 
        RepoModel().grant_user_permission(repo=self.REPO,
 
                                          user=self.TEST_USER_LOGIN,
 
                                          perm='repository.none')
 
        fork_name = 'api-repo-fork'
 
        id_, params = _build_data(self.apikey_regular, 'fork_repo',
 
                                  repoid=self.REPO,
 
                                  fork_name=fork_name,
 
        )
 
        response = api_call(self, params)
 
        expected = 'repository `%s` does not exist' % (self.REPO)
 
        self._compare_error(id_, expected, given=response.body)
 
        fixture.destroy_repo(fork_name)
 

	
 
    @parameterized.expand([('read', 'repository.read'),
 
                           ('write', 'repository.write'),
 
                           ('admin', 'repository.admin')])
 
    def test_api_fork_repo_non_admin_no_create_repo_permission(self, name, perm):
 
        fork_name = 'api-repo-fork'
 
        # regardless of base repository permission, forking is disallowed
 
        # when repository creation is disabled
 
        RepoModel().grant_user_permission(repo=self.REPO,
 
                                          user=self.TEST_USER_LOGIN,
 
                                          perm=perm)
 
        UserModel().revoke_perm('default', 'hg.create.repository')
 
        UserModel().grant_perm('default', 'hg.create.none')
 
        id_, params = _build_data(self.apikey_regular, 'fork_repo',
 
                                  repoid=self.REPO,
 
                                  fork_name=fork_name,
 
        )
 
        response = api_call(self, params)
 
        expected = 'no permission to create repositories'
 
        self._compare_error(id_, expected, given=response.body)
 
        fixture.destroy_repo(fork_name)
 

	
 
    def test_api_fork_repo_unknown_owner(self):
 
        fork_name = 'api-repo-fork'
 
        owner = 'i-dont-exist'
 
        id_, params = _build_data(self.apikey, 'fork_repo',
 
                                  repoid=self.REPO,
 
                                  fork_name=fork_name,
 
                                  owner=owner,
 
        )
 
        response = api_call(self, params)
 
        expected = 'user `%s` does not exist' % owner
 
        self._compare_error(id_, expected, given=response.body)
 

	
 
    def test_api_fork_repo_fork_exists(self):
 
        fork_name = 'api-repo-fork'
 
        fixture.create_fork(self.REPO, fork_name)
 

	
 
        try:
 
            fork_name = 'api-repo-fork'
 

	
 
            id_, params = _build_data(self.apikey, 'fork_repo',
 
                                      repoid=self.REPO,
 
                                      fork_name=fork_name,
 
                                      owner=TEST_USER_ADMIN_LOGIN,
 
            )
 
            response = api_call(self, params)
 

	
 
            expected = "fork `%s` already exist" % fork_name
 
            self._compare_error(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_repo(fork_name)
 

	
 
    def test_api_fork_repo_repo_exists(self):
 
        fork_name = self.REPO
 

	
 
        id_, params = _build_data(self.apikey, 'fork_repo',
 
                                  repoid=self.REPO,
 
                                  fork_name=fork_name,
 
                                  owner=TEST_USER_ADMIN_LOGIN,
 
        )
 
        response = api_call(self, params)
 

	
 
        expected = "repo `%s` already exist" % fork_name
 
        self._compare_error(id_, expected, given=response.body)
 

	
 
    @mock.patch.object(RepoModel, 'create_fork', crash)
 
    def test_api_fork_repo_exception_occurred(self):
 
        fork_name = 'api-repo-fork'
 
        id_, params = _build_data(self.apikey, 'fork_repo',
 
                                  repoid=self.REPO,
 
                                  fork_name=fork_name,
 
                                  owner=TEST_USER_ADMIN_LOGIN,
 
        )
 
        response = api_call(self, params)
 

	
 
        expected = 'failed to fork repository `%s` as `%s`' % (self.REPO,
 
                                                               fork_name)
 
        self._compare_error(id_, expected, given=response.body)
 

	
 
    def test_api_get_user_group(self):
 
        id_, params = _build_data(self.apikey, 'get_user_group',
 
                                  usergroupid=TEST_USER_GROUP)
 
        response = api_call(self, params)
 

	
 
        user_group = UserGroupModel().get_group(TEST_USER_GROUP)
 
        members = []
 
        for user in user_group.members:
 
            user = user.user
 
            members.append(user.get_api_data())
 

	
 
        ret = user_group.get_api_data()
 
        ret['members'] = members
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 

	
 
    def test_api_get_user_groups(self):
 
        gr_name = 'test_user_group2'
 
        make_user_group(gr_name)
 

	
 
        id_, params = _build_data(self.apikey, 'get_user_groups', )
 
        response = api_call(self, params)
 

	
 
        try:
 
            expected = []
 
            for gr_name in [TEST_USER_GROUP, 'test_user_group2']:
 
                user_group = UserGroupModel().get_group(gr_name)
 
                ret = user_group.get_api_data()
 
                expected.append(ret)
 
            self._compare_ok(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_user_group(gr_name)
 

	
 
    def test_api_create_user_group(self):
 
        group_name = 'some_new_group'
 
        id_, params = _build_data(self.apikey, 'create_user_group',
 
                                  group_name=group_name)
 
        response = api_call(self, params)
 

	
 
        ret = {
 
            'msg': 'created new user group `%s`' % group_name,
 
            'user_group': jsonify(UserGroupModel() \
 
                .get_by_name(group_name) \
 
                .get_api_data())
 
        }
 
        expected = ret
 
        self._compare_ok(id_, expected, given=response.body)
 

	
 
        fixture.destroy_user_group(group_name)
 

	
 
    def test_api_get_user_group_that_exist(self):
 
        id_, params = _build_data(self.apikey, 'create_user_group',
 
                                  group_name=TEST_USER_GROUP)
 
        response = api_call(self, params)
 

	
 
        expected = "user group `%s` already exist" % TEST_USER_GROUP
 
        self._compare_error(id_, expected, given=response.body)
 

	
 
    @mock.patch.object(UserGroupModel, 'create', crash)
 
    def test_api_get_user_group_exception_occurred(self):
 
        group_name = 'exception_happens'
 
        id_, params = _build_data(self.apikey, 'create_user_group',
 
                                  group_name=group_name)
 
        response = api_call(self, params)
 

	
 
        expected = 'failed to create group `%s`' % group_name
 
        self._compare_error(id_, expected, given=response.body)
 

	
 
    @parameterized.expand([('group_name', {'group_name': 'new_group_name'}),
 
                           ('group_name', {'group_name': 'test_group_for_update'}),
 
                           ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
 
                           ('active', {'active': False}),
 
                           ('active', {'active': True})])
 
    def test_api_update_user_group(self, changing_attr, updates):
 
        gr_name = 'test_group_for_update'
 
        user_group = fixture.create_user_group(gr_name)
 
        id_, params = _build_data(self.apikey, 'update_user_group',
 
                                  usergroupid=gr_name, **updates)
 
        response = api_call(self, params)
 
        try:
 
            expected = {
 
               'msg': 'updated user group ID:%s %s' % (user_group.users_group_id,
 
                                                     user_group.users_group_name),
 
               'user_group': user_group.get_api_data()
 
            }
 
            self._compare_ok(id_, expected, given=response.body)
 
        finally:
 
            if changing_attr == 'group_name':
 
                # switch to updated name for proper cleanup
 
                gr_name = updates['group_name']
 
            fixture.destroy_user_group(gr_name)
 

	
 
    @mock.patch.object(UserGroupModel, 'update', crash)
 
    def test_api_update_user_group_exception_occurred(self):
 
        gr_name = 'test_group'
 
        fixture.create_user_group(gr_name)
 
        id_, params = _build_data(self.apikey, 'update_user_group',
 
                                  usergroupid=gr_name)
 
        response = api_call(self, params)
 
        try:
 
            expected = 'failed to update user group `%s`' % gr_name
 
            self._compare_error(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_user_group(gr_name)
 

	
 
    def test_api_add_user_to_user_group(self):
 
        gr_name = 'test_group'
 
        fixture.create_user_group(gr_name)
 
        id_, params = _build_data(self.apikey, 'add_user_to_user_group',
 
                                  usergroupid=gr_name,
 
                                  userid=TEST_USER_ADMIN_LOGIN)
 
        response = api_call(self, params)
 
        try:
 
            expected = {
 
            'msg': 'added member `%s` to user group `%s`' % (
 
                    TEST_USER_ADMIN_LOGIN, gr_name),
 
            'success': True
 
            }
 
            self._compare_ok(id_, expected, given=response.body)
 
        finally:
 
            fixture.destroy_user_group(gr_name)
 

	
 
    def test_api_add_user_to_user_group_that_doesnt_exist(self):
 
        id_, params = _build_data(self.apikey, 'add_user_to_user_group',
 
                                  usergroupid='false-group',
 
                                  userid=TEST_USER_ADMIN_LOGIN)
 
        response = api_call(self, params)
 

	
 
        expected = 'user group `%s` does not exist' % 'false-group'
 
        self._compare_error(id_, expected, given=response.body)
 

	
 
    @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
 
    def test_api_add_user_to_user_group_exception_occurred(self):
 
        gr_name = 'test_group'
kallithea/tests/functional/test_login.py
Show inline comments
 
@@ -141,305 +141,307 @@ class TestLoginController(TestController
 
        ({'blue': u'blå'.encode('utf-8'), 'green':u'grøn'},
 
             ('blue=bl%C3%A5', 'green=gr%C3%B8n')),
 
    ])
 
    def test_redirection_after_successful_login_preserves_get_args(self, args, args_encoded):
 
        response = self.app.post(url(controller='login', action='index',
 
                                     came_from = '/_admin/users',
 
                                     **args),
 
                                 {'username': TEST_USER_ADMIN_LOGIN,
 
                                  'password': TEST_USER_ADMIN_PASS})
 
        self.assertEqual(response.status, '302 Found')
 
        for encoded in args_encoded:
 
            self.assertIn(encoded, response.location)
 

	
 
    @parameterized.expand([
 
        ({'foo':'one', 'bar':'two'}, ('foo=one', 'bar=two')),
 
        ({'blue': u'blå'.encode('utf-8'), 'green':u'grøn'},
 
             ('blue=bl%C3%A5', 'green=gr%C3%B8n')),
 
    ])
 
    def test_login_form_after_incorrect_login_preserves_get_args(self, args, args_encoded):
 
        response = self.app.post(url(controller='login', action='index',
 
                                     came_from = '/_admin/users',
 
                                     **args),
 
                                 {'username': 'error',
 
                                  'password': 'test12'})
 

	
 
        response.mustcontain('invalid user name')
 
        response.mustcontain('invalid password')
 
        for encoded in args_encoded:
 
            self.assertIn(encoded, response.form.action)
 

	
 
    #==========================================================================
 
    # REGISTRATIONS
 
    #==========================================================================
 
    def test_register(self):
 
        response = self.app.get(url(controller='login', action='register'))
 
        response.mustcontain('Sign Up')
 

	
 
    def test_register_err_same_username(self):
 
        uname = TEST_USER_ADMIN_LOGIN
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username': uname,
 
                                             'password': 'test12',
 
                                             'password_confirmation': 'test12',
 
                                             'email': 'goodmail@domain.com',
 
                                             'firstname': 'test',
 
                                             'lastname': 'test'})
 

	
 
        msg = validators.ValidUsername()._messages['username_exists']
 
        msg = h.html_escape(msg % {'username': uname})
 
        response.mustcontain(msg)
 

	
 
    def test_register_err_same_email(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username': 'test_admin_0',
 
                                             'password': 'test12',
 
                                             'password_confirmation': 'test12',
 
                                             'email': TEST_USER_ADMIN_EMAIL,
 
                                             'firstname': 'test',
 
                                             'lastname': 'test'})
 

	
 
        msg = validators.UniqSystemEmail()()._messages['email_taken']
 
        response.mustcontain(msg)
 

	
 
    def test_register_err_same_email_case_sensitive(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username': 'test_admin_1',
 
                                             'password': 'test12',
 
                                             'password_confirmation': 'test12',
 
                                             'email': TEST_USER_ADMIN_EMAIL.title(),
 
                                             'firstname': 'test',
 
                                             'lastname': 'test'})
 
        msg = validators.UniqSystemEmail()()._messages['email_taken']
 
        response.mustcontain(msg)
 

	
 
    def test_register_err_wrong_data(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username': 'xs',
 
                                             'password': 'test',
 
                                             'password_confirmation': 'test',
 
                                             'email': 'goodmailm',
 
                                             'firstname': 'test',
 
                                             'lastname': 'test'})
 
        self.assertEqual(response.status, '200 OK')
 
        response.mustcontain('An email address must contain a single @')
 
        response.mustcontain('Enter a value 6 characters long or more')
 

	
 
    def test_register_err_username(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username': 'error user',
 
                                             'password': 'test12',
 
                                             'password_confirmation': 'test12',
 
                                             'email': 'goodmailm',
 
                                             'firstname': 'test',
 
                                             'lastname': 'test'})
 

	
 
        response.mustcontain('An email address must contain a single @')
 
        response.mustcontain('Username may only contain '
 
                'alphanumeric characters underscores, '
 
                'periods or dashes and must begin with '
 
                'alphanumeric character')
 

	
 
    def test_register_err_case_sensitive(self):
 
        usr = TEST_USER_ADMIN_LOGIN.title()
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username': usr,
 
                                             'password': 'test12',
 
                                             'password_confirmation': 'test12',
 
                                             'email': 'goodmailm',
 
                                             'firstname': 'test',
 
                                             'lastname': 'test'})
 

	
 
        response.mustcontain('An email address must contain a single @')
 
        msg = validators.ValidUsername()._messages['username_exists']
 
        msg = h.html_escape(msg % {'username': usr})
 
        response.mustcontain(msg)
 

	
 
    def test_register_special_chars(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                        {'username': 'xxxaxn',
 
                                         'password': 'ąćźżąśśśś',
 
                                         'password_confirmation': 'ąćźżąśśśś',
 
                                         'email': 'goodmailm@test.plx',
 
                                         'firstname': 'test',
 
                                         'lastname': 'test'})
 

	
 
        msg = validators.ValidPassword()._messages['invalid_password']
 
        response.mustcontain(msg)
 

	
 
    def test_register_password_mismatch(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username': 'xs',
 
                                             'password': '123qwe',
 
                                             'password_confirmation': 'qwe123',
 
                                             'email': 'goodmailm@test.plxa',
 
                                             'firstname': 'test',
 
                                             'lastname': 'test'})
 
        msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
 
        response.mustcontain(msg)
 

	
 
    def test_register_ok(self):
 
        username = 'test_regular4'
 
        password = 'qweqwe'
 
        email = 'username@test.com'
 
        name = 'testname'
 
        lastname = 'testlastname'
 

	
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username': username,
 
                                             'password': password,
 
                                             'password_confirmation': password,
 
                                             'email': email,
 
                                             'firstname': name,
 
                                             'lastname': lastname,
 
                                             'admin': True})  # This should be overriden
 
        self.assertEqual(response.status, '302 Found')
 
        self.checkSessionFlash(response, 'You have successfully registered into Kallithea')
 

	
 
        ret = Session().query(User).filter(User.username == 'test_regular4').one()
 
        self.assertEqual(ret.username, username)
 
        self.assertEqual(check_password(password, ret.password), True)
 
        self.assertEqual(ret.email, email)
 
        self.assertEqual(ret.name, name)
 
        self.assertEqual(ret.lastname, lastname)
 
        self.assertNotEqual(ret.api_key, None)
 
        self.assertEqual(ret.admin, False)
 

	
 
    def test_forgot_password_wrong_mail(self):
 
        bad_email = 'username%wrongmail.org'
 
        response = self.app.post(
 
                        url(controller='login', action='password_reset'),
 
                            {'email': bad_email, }
 
        )
 

	
 
        response.mustcontain('An email address must contain a single @')
 

	
 
    def test_forgot_password(self):
 
        response = self.app.get(url(controller='login',
 
                                    action='password_reset'))
 
        self.assertEqual(response.status, '200 OK')
 

	
 
        username = 'test_password_reset_1'
 
        password = 'qweqwe'
 
        email = 'username@python-works.com'
 
        name = 'passwd'
 
        lastname = 'reset'
 

	
 
        new = User()
 
        new.username = username
 
        new.password = password
 
        new.email = email
 
        new.name = name
 
        new.lastname = lastname
 
        new.api_key = generate_api_key(username)
 
        new.api_key = generate_api_key()
 
        Session().add(new)
 
        Session().commit()
 

	
 
        response = self.app.post(url(controller='login',
 
                                     action='password_reset'),
 
                                 {'email': email, })
 

	
 
        self.checkSessionFlash(response, 'Your password reset link was sent')
 

	
 
        response = response.follow()
 

	
 
        # BAD KEY
 

	
 
        key = "bad"
 
        response = self.app.get(url(controller='login',
 
                                    action='password_reset_confirmation',
 
                                    key=key))
 
        self.assertEqual(response.status, '302 Found')
 
        self.assertTrue(response.location.endswith(url('reset_password')))
 

	
 
        # GOOD KEY
 

	
 
        key = User.get_by_username(username).api_key
 
        response = self.app.get(url(controller='login',
 
                                    action='password_reset_confirmation',
 
                                    key=key))
 
        self.assertEqual(response.status, '302 Found')
 
        self.assertTrue(response.location.endswith(url('login_home')))
 

	
 
        self.checkSessionFlash(response,
 
                               ('Your password reset was successful, '
 
                                'new password has been sent to your email'))
 

	
 
        response = response.follow()
 

	
 
    def _get_api_whitelist(self, values=None):
 
        config = {'api_access_controllers_whitelist': values or []}
 
        return config
 

	
 
    @parameterized.expand([
 
        ('none', None),
 
        ('empty_string', ''),
 
        ('fake_number', '123456'),
 
        ('proper_api_key', None)
 
    ])
 
    def test_access_not_whitelisted_page_via_api_key(self, test_name, api_key):
 
        whitelist = self._get_api_whitelist([])
 
        with mock.patch('kallithea.CONFIG', whitelist):
 
            self.assertEqual([],
 
                             whitelist['api_access_controllers_whitelist'])
 
            if test_name == 'proper_api_key':
 
                #use builtin if api_key is None
 
                api_key = User.get_first_admin().api_key
 

	
 
            with fixture.anon_access(False):
 
                self.app.get(url(controller='changeset',
 
                                 action='changeset_raw',
 
                                 repo_name=HG_REPO, revision='tip', api_key=api_key),
 
                             status=403)
 

	
 
    @parameterized.expand([
 
        ('none', None, 302),
 
        ('empty_string', '', 302),
 
        ('fake_number', '123456', 302),
 
        ('fake_not_alnum', 'a-z', 302),
 
        ('fake_api_key', '0123456789abcdef0123456789ABCDEF01234567', 302),
 
        ('proper_api_key', None, 200)
 
    ])
 
    def test_access_whitelisted_page_via_api_key(self, test_name, api_key, code):
 
        whitelist = self._get_api_whitelist(['ChangesetController:changeset_raw'])
 
        with mock.patch('kallithea.CONFIG', whitelist):
 
            self.assertEqual(['ChangesetController:changeset_raw'],
 
                             whitelist['api_access_controllers_whitelist'])
 
            if test_name == 'proper_api_key':
 
                api_key = User.get_first_admin().api_key
 

	
 
            with fixture.anon_access(False):
 
                self.app.get(url(controller='changeset',
 
                                 action='changeset_raw',
 
                                 repo_name=HG_REPO, revision='tip', api_key=api_key),
 
                             status=code)
 

	
 
    def test_access_page_via_extra_api_key(self):
 
        whitelist = self._get_api_whitelist(['ChangesetController:changeset_raw'])
 
        with mock.patch('kallithea.CONFIG', whitelist):
 
            self.assertEqual(['ChangesetController:changeset_raw'],
 
                             whitelist['api_access_controllers_whitelist'])
 

	
 
            new_api_key = ApiKeyModel().create(TEST_USER_ADMIN_LOGIN, u'test')
 
            Session().commit()
 
            with fixture.anon_access(False):
 
                self.app.get(url(controller='changeset',
 
                                 action='changeset_raw',
 
                                 repo_name=HG_REPO, revision='tip', api_key=new_api_key.api_key),
 
                             status=200)
 

	
 
    def test_access_page_via_expired_api_key(self):
 
        whitelist = self._get_api_whitelist(['ChangesetController:changeset_raw'])
 
        with mock.patch('kallithea.CONFIG', whitelist):
 
            self.assertEqual(['ChangesetController:changeset_raw'],
 
                             whitelist['api_access_controllers_whitelist'])
 

	
 
            new_api_key = ApiKeyModel().create(TEST_USER_ADMIN_LOGIN, u'test')
 
            Session().commit()
 
            #patch the API key and make it expired
 
            new_api_key.expires = 0
 
            Session().add(new_api_key)
 
            Session().commit()
 
            with fixture.anon_access(False):
 
                self.app.get(url(controller='changeset',
 
                                 action='changeset_raw',
 
                                 repo_name=HG_REPO, revision='tip',
 
                                 api_key=new_api_key.api_key),
 
                             status=302)
kallithea/tests/models/test_permissions.py
Show inline comments
 
from kallithea.tests import *
 
from kallithea.tests.fixture import Fixture
 
from kallithea.model.repo_group import RepoGroupModel
 
from kallithea.model.repo import RepoModel
 
from kallithea.model.db import RepoGroup, User, UserGroupRepoGroupToPerm,\
 
    Permission, UserToPerm
 
from kallithea.model.user import UserModel
 

	
 
from kallithea.model.meta import Session
 
from kallithea.model.user_group import UserGroupModel
 
from kallithea.lib.auth import AuthUser
 
from kallithea.model.permission import PermissionModel
 

	
 

	
 
fixture = Fixture()
 

	
 

	
 
class TestPermissions(BaseTestCase):
 
    def __init__(self, methodName='runTest'):
 
        super(TestPermissions, self).__init__(methodName=methodName)
 

	
 
    @classmethod
 
    def setUpClass(cls):
 
        #recreate default user to get a clean start
 
        PermissionModel().create_default_permissions(user=User.DEFAULT_USER,
 
                                                     force=True)
 
        Session().commit()
 

	
 
    def setUp(self):
 
        self.u1 = UserModel().create_or_update(
 
            username=u'u1', password=u'qweqwe',
 
            email=u'u1@example.com', firstname=u'u1', lastname=u'u1'
 
        )
 
        self.u2 = UserModel().create_or_update(
 
            username=u'u2', password=u'qweqwe',
 
            email=u'u2@example.com', firstname=u'u2', lastname=u'u2'
 
        )
 
        self.u3 = UserModel().create_or_update(
 
            username=u'u3', password=u'qweqwe',
 
            email=u'u3@example.com', firstname=u'u3', lastname=u'u3'
 
        )
 
        self.anon = User.get_default_user()
 
        self.a1 = UserModel().create_or_update(
 
            username=u'a1', password=u'qweqwe',
 
            email=u'a1@example.com', firstname=u'a1', lastname=u'a1', admin=True
 
        )
 
        Session().commit()
 

	
 
    def tearDown(self):
 
        if hasattr(self, 'test_repo'):
 
            RepoModel().delete(repo=self.test_repo)
 

	
 
        UserModel().delete(self.u1)
 
        UserModel().delete(self.u2)
 
        UserModel().delete(self.u3)
 
        UserModel().delete(self.a1)
 
        if hasattr(self, 'g1'):
 
            RepoGroupModel().delete(self.g1.group_id)
 
        if hasattr(self, 'g2'):
 
            RepoGroupModel().delete(self.g2.group_id)
 

	
 
        if hasattr(self, 'ug1'):
 
            UserGroupModel().delete(self.ug1, force=True)
 
        if hasattr(self, 'ug2'):
 
            UserGroupModel().delete(self.ug2, force=True)
 

	
 
        Session().commit()
 

	
 
    def test_default_perms_set(self):
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        perms = {
 
            'repositories_groups': {},
 
            'global': set([u'hg.create.repository', u'repository.read',
 
                           u'hg.register.manual_activate']),
 
            'repositories': {HG_REPO: u'repository.read'}
 
        }
 
        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
 
                         perms['repositories'][HG_REPO])
 
        new_perm = 'repository.write'
 
        RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
 
                                          perm=new_perm)
 
        Session().commit()
 

	
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
 
                         new_perm)
 

	
 
    def test_default_admin_perms_set(self):
 
        a1_auth = AuthUser(user_id=self.a1.user_id)
 
        perms = {
 
            'repositories_groups': {},
 
            'global': set([u'hg.admin', 'hg.create.write_on_repogroup.true']),
 
            'repositories': {HG_REPO: u'repository.admin'}
 
        }
 
        self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
 
                         perms['repositories'][HG_REPO])
 
        new_perm = 'repository.write'
 
        RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1,
 
                                          perm=new_perm)
 
        Session().commit()
 
        # cannot really downgrade admins permissions !? they still gets set as
 
        # admin !
 
        u1_auth = AuthUser(user_id=self.a1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
 
                         perms['repositories'][HG_REPO])
 

	
 
    def test_default_group_perms(self):
 
        self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
 
        self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        perms = {
 
            'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'},
 
            'global': set(Permission.DEFAULT_USER_PERMISSIONS),
 
            'repositories': {HG_REPO: u'repository.read'}
 
        }
 
        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
 
                         perms['repositories'][HG_REPO])
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                         perms['repositories_groups'])
 
        self.assertEqual(u1_auth.permissions['global'],
 
                         perms['global'])
 

	
 
    def test_default_admin_group_perms(self):
 
        self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
 
        self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
 
        a1_auth = AuthUser(user_id=self.a1.user_id)
 
        perms = {
 
            'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'},
 
            'global': set(['hg.admin', 'hg.create.write_on_repogroup.true']),
 
            'repositories': {HG_REPO: 'repository.admin'}
 
        }
 

	
 
        self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
 
                         perms['repositories'][HG_REPO])
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                         perms['repositories_groups'])
 

	
 
    def test_propagated_permission_from_users_group_by_explicit_perms_exist(self):
 
        # make group
 
        self.ug1 = fixture.create_user_group('G1')
 
        UserGroupModel().add_user_to_group(self.ug1, self.u1)
 

	
 
        # set permission to lower
 
        new_perm = 'repository.none'
 
        RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
 
                         new_perm)
 

	
 
        # grant perm for group this should not override permission from user
 
        # since it has explicitly set
 
        new_perm_gr = 'repository.write'
 
        RepoModel().grant_user_group_permission(repo=HG_REPO,
 
                                                 group_name=self.ug1,
 
                                                 perm=new_perm_gr)
 
        # check perms
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        perms = {
 
            'repositories_groups': {},
 
            'global': set([u'hg.create.repository', u'repository.read',
 
                           u'hg.register.manual_activate']),
 
            'repositories': {HG_REPO: u'repository.read'}
 
        }
 
        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
 
                         new_perm)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                         perms['repositories_groups'])
 

	
 
    def test_propagated_permission_from_users_group(self):
 
        # make group
 
        self.ug1 = fixture.create_user_group('G1')
 
        UserGroupModel().add_user_to_group(self.ug1, self.u3)
 

	
 
        # grant perm for group this should override default permission from user
 
        new_perm_gr = 'repository.write'
 
        RepoModel().grant_user_group_permission(repo=HG_REPO,
 
                                                 group_name=self.ug1,
 
                                                 perm=new_perm_gr)
 
        # check perms
 
        u3_auth = AuthUser(user_id=self.u3.user_id)
 
        perms = {
 
            'repositories_groups': {},
 
            'global': set([u'hg.create.repository', u'repository.read',
 
                           u'hg.register.manual_activate']),
 
            'repositories': {HG_REPO: u'repository.read'}
 
        }
 
        self.assertEqual(u3_auth.permissions['repositories'][HG_REPO],
 
                         new_perm_gr)
 
        self.assertEqual(u3_auth.permissions['repositories_groups'],
 
                         perms['repositories_groups'])
 

	
 
    def test_propagated_permission_from_users_group_lower_weight(self):
 
        # make group
 
        self.ug1 = fixture.create_user_group('G1')
 
        # add user to group
 
        UserGroupModel().add_user_to_group(self.ug1, self.u1)
 

	
 
        # set permission to lower
 
        new_perm_h = 'repository.write'
 
        RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
 
                                          perm=new_perm_h)
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
 
                         new_perm_h)
 

	
 
        # grant perm for group this should NOT override permission from user
 
        # since it's lower than granted
 
        new_perm_l = 'repository.read'
 
        RepoModel().grant_user_group_permission(repo=HG_REPO,
 
                                                 group_name=self.ug1,
 
                                                 perm=new_perm_l)
 
        # check perms
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        perms = {
 
            'repositories_groups': {},
 
            'global': set([u'hg.create.repository', u'repository.read',
 
                           u'hg.register.manual_activate']),
 
            'repositories': {HG_REPO: u'repository.write'}
 
        }
 
        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
 
                         new_perm_h)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                         perms['repositories_groups'])
 

	
 
    def test_repo_in_group_permissions(self):
 
        self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
 
        self.g2 = fixture.create_repo_group('group2', skip_if_exists=True)
 
        # both perms should be read !
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.read', u'group2': u'group.read'})
 

	
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.read', u'group2': u'group.read'})
 

	
 
        #Change perms to none for both groups
 
        RepoGroupModel().grant_user_permission(repo_group=self.g1,
 
                                               user=self.anon,
 
                                               perm='group.none')
 
        RepoGroupModel().grant_user_permission(repo_group=self.g2,
 
                                               user=self.anon,
 
                                               perm='group.none')
 

	
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.none', u'group2': u'group.none'})
 

	
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.none', u'group2': u'group.none'})
 

	
 
        # add repo to group
 
        name = RepoGroup.url_sep().join([self.g1.group_name, 'test_perm'])
 
        self.test_repo = fixture.create_repo(name=name,
 
                                             repo_type='hg',
 
                                             repo_group=self.g1,
 
                                             cur_user=self.u1,)
 

	
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.none', u'group2': u'group.none'})
 

	
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.none', u'group2': u'group.none'})
 

	
 
        #grant permission for u2 !
 
        RepoGroupModel().grant_user_permission(repo_group=self.g1, user=self.u2,
 
                                               perm='group.read')
 
        RepoGroupModel().grant_user_permission(repo_group=self.g2, user=self.u2,
 
                                               perm='group.read')
 
        Session().commit()
 
        self.assertNotEqual(self.u1, self.u2)
 
        #u1 and anon should have not change perms while u2 should !
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.none', u'group2': u'group.none'})
 

	
 
        u2_auth = AuthUser(user_id=self.u2.user_id)
 
        self.assertEqual(u2_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.read', u'group2': u'group.read'})
 

	
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.none', u'group2': u'group.none'})
 

	
 
    def test_repo_group_user_as_user_group_member(self):
 
        # create Group1
 
        self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 

	
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.read'})
 

	
 
        # set default permission to none
 
        RepoGroupModel().grant_user_permission(repo_group=self.g1,
 
                                               user=self.anon,
 
                                               perm='group.none')
 
        # make group
 
        self.ug1 = fixture.create_user_group('G1')
 
        # add user to group
 
        UserGroupModel().add_user_to_group(self.ug1, self.u1)
 
        Session().commit()
 

	
 
        # check if user is in the group
 
        membrs = [x.user_id for x in UserGroupModel().get(self.ug1.users_group_id).members]
 
        self.assertEqual(membrs, [self.u1.user_id])
 
        # add some user to that group
 

	
 
        # check his permissions
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.none'})
 

	
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.none'})
 

	
 
        # grant ug1 read permissions for
 
        RepoGroupModel().grant_user_group_permission(repo_group=self.g1,
 
                                                      group_name=self.ug1,
 
                                                      perm='group.read')
 
        Session().commit()
 
        # check if the
 
        obj = Session().query(UserGroupRepoGroupToPerm)\
 
            .filter(UserGroupRepoGroupToPerm.group == self.g1)\
 
            .filter(UserGroupRepoGroupToPerm.users_group == self.ug1)\
 
            .scalar()
 
        self.assertEqual(obj.permission.permission_name, 'group.read')
 

	
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 

	
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.none'})
 

	
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.read'})
 

	
 
    def test_inherited_permissions_from_default_on_user_enabled(self):
 
        user_model = UserModel()
 
        # enable fork and create on default user
 
        usr = 'default'
 
        user_model.revoke_perm(usr, 'hg.create.none')
 
        user_model.grant_perm(usr, 'hg.create.repository')
 
        user_model.revoke_perm(usr, 'hg.fork.none')
 
        user_model.grant_perm(usr, 'hg.fork.repository')
 
        # make sure inherit flag is turned on
 
        self.u1.inherit_default_permissions = True
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        # this user will have inherited permissions from default user
 
        self.assertEqual(u1_auth.permissions['global'],
 
                         set(['hg.create.repository', 'hg.fork.repository',
 
                              'hg.register.manual_activate',
 
                              'hg.extern_activate.auto',
 
                              'repository.read', 'group.read',
 
                              'usergroup.read', 'hg.create.write_on_repogroup.true']))
 

	
 
    def test_inherited_permissions_from_default_on_user_disabled(self):
 
        user_model = UserModel()
 
        # disable fork and create on default user
 
        usr = 'default'
 
        user_model.revoke_perm(usr, 'hg.create.repository')
 
        user_model.grant_perm(usr, 'hg.create.none')
 
        user_model.revoke_perm(usr, 'hg.fork.repository')
 
        user_model.grant_perm(usr, 'hg.fork.none')
 
        # make sure inherit flag is turned on
 
        self.u1.inherit_default_permissions = True
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        # this user will have inherited permissions from default user
 
        self.assertEqual(u1_auth.permissions['global'],
 
                         set(['hg.create.none', 'hg.fork.none',
 
                              'hg.register.manual_activate',
 
                              'hg.extern_activate.auto',
 
                              'repository.read', 'group.read',
 
                              'usergroup.read', 'hg.create.write_on_repogroup.true']))
 

	
 
    def test_non_inherited_permissions_from_default_on_user_enabled(self):
 
        user_model = UserModel()
 
        # enable fork and create on default user
 
        usr = 'default'
 
        user_model.revoke_perm(usr, 'hg.create.none')
 
        user_model.grant_perm(usr, 'hg.create.repository')
 
        user_model.revoke_perm(usr, 'hg.fork.none')
 
        user_model.grant_perm(usr, 'hg.fork.repository')
 

	
 
        #disable global perms on specific user
 
        user_model.revoke_perm(self.u1, 'hg.create.repository')
 
        user_model.grant_perm(self.u1, 'hg.create.none')
 
        user_model.revoke_perm(self.u1, 'hg.fork.repository')
 
        user_model.grant_perm(self.u1, 'hg.fork.none')
 

	
 
        # make sure inherit flag is turned off
 
        self.u1.inherit_default_permissions = False
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        # this user will have non inherited permissions from he's
 
        # explicitly set permissions
 
        self.assertEqual(u1_auth.permissions['global'],
 
                         set(['hg.create.none', 'hg.fork.none',
 
                              'hg.register.manual_activate',
 
                              'hg.extern_activate.auto',
 
                              'repository.read', 'group.read',
 
                              'usergroup.read', 'hg.create.write_on_repogroup.true']))
 

	
 
    def test_non_inherited_permissions_from_default_on_user_disabled(self):
 
        user_model = UserModel()
 
        # disable fork and create on default user
 
        usr = 'default'
 
        user_model.revoke_perm(usr, 'hg.create.repository')
 
        user_model.grant_perm(usr, 'hg.create.none')
 
        user_model.revoke_perm(usr, 'hg.fork.repository')
 
        user_model.grant_perm(usr, 'hg.fork.none')
 

	
 
        #enable global perms on specific user
 
        user_model.revoke_perm(self.u1, 'hg.create.none')
 
        user_model.grant_perm(self.u1, 'hg.create.repository')
 
        user_model.revoke_perm(self.u1, 'hg.fork.none')
 
        user_model.grant_perm(self.u1, 'hg.fork.repository')
 

	
 
        # make sure inherit flag is turned off
 
        self.u1.inherit_default_permissions = False
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        # this user will have non inherited permissions from he's
 
        # explicitly set permissions
 
        self.assertEqual(u1_auth.permissions['global'],
 
                         set(['hg.create.repository', 'hg.fork.repository',
 
                              'hg.register.manual_activate',
 
                              'hg.extern_activate.auto',
 
                              'repository.read', 'group.read',
 
                              'usergroup.read', 'hg.create.write_on_repogroup.true']))
 

	
 
    def test_inactive_user_group_does_not_affect_global_permissions(self):
 
        # Issue #138: Inactive User Groups affecting permissions
 
        # Add user to inactive user group, set specific permissions on user
 
        # group and disable inherit-from-default. User permissions should still
 
        # inherit from default.
 
        self.ug1 = fixture.create_user_group('G1')
 
        self.ug1.inherit_default_permissions = False
 
        user_group_model = UserGroupModel()
 
        user_group_model.add_user_to_group(self.ug1, self.u1)
 
        user_group_model.update(self.ug1, {'users_group_active': False})
 

	
 
        # enable fork and create on user group
 
        user_group_model.revoke_perm(self.ug1, perm='hg.create.none')
 
        user_group_model.grant_perm(self.ug1, perm='hg.create.repository')
 
        user_group_model.revoke_perm(self.ug1, perm='hg.fork.none')
 
        user_group_model.grant_perm(self.ug1, perm='hg.fork.repository')
 

	
 
        user_model = UserModel()
 
        # disable fork and create on default user
 
        usr = 'default'
 
        user_model.revoke_perm(usr, 'hg.create.repository')
 
        user_model.grant_perm(usr, 'hg.create.none')
 
        user_model.revoke_perm(usr, 'hg.fork.repository')
 
        user_model.grant_perm(usr, 'hg.fork.none')
 

	
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 

	
 
        self.assertEqual(u1_auth.permissions['global'],
 
                         set(['hg.create.none', 'hg.fork.none',
 
                              'hg.register.manual_activate',
 
                              'hg.extern_activate.auto',
 
                              'repository.read', 'group.read',
 
                              'usergroup.read',
 
                              'hg.create.write_on_repogroup.true']))
 

	
 
    def test_inactive_user_group_does_not_affect_global_permissions_inverse(self):
 
        # Issue #138: Inactive User Groups affecting permissions
 
        # Add user to inactive user group, set specific permissions on user
 
        # group and disable inherit-from-default. User permissions should still
 
        # inherit from default.
 
        self.ug1 = fixture.create_user_group('G1')
 
        self.ug1.inherit_default_permissions = False
 
        user_group_model = UserGroupModel()
 
        user_group_model.add_user_to_group(self.ug1, self.u1)
 
        user_group_model.update(self.ug1, {'users_group_active': False})
 

	
 
        # disable fork and create on user group
 
        user_group_model.revoke_perm(self.ug1, perm='hg.create.repository')
 
        user_group_model.grant_perm(self.ug1, perm='hg.create.none')
 
        user_group_model.revoke_perm(self.ug1, perm='hg.fork.repository')
 
        user_group_model.grant_perm(self.ug1, perm='hg.fork.none')
 

	
 
        user_model = UserModel()
 
        # enable fork and create on default user
 
        usr = 'default'
 
        user_model.revoke_perm(usr, 'hg.create.none')
 
        user_model.grant_perm(usr, 'hg.create.repository')
 
        user_model.revoke_perm(usr, 'hg.fork.none')
 
        user_model.grant_perm(usr, 'hg.fork.repository')
 

	
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 

	
 
        self.assertEqual(u1_auth.permissions['global'],
 
                         set(['hg.create.repository', 'hg.fork.repository',
 
                              'hg.register.manual_activate',
 
                              'hg.extern_activate.auto',
 
                              'repository.read', 'group.read',
 
                              'usergroup.read',
 
                              'hg.create.write_on_repogroup.true']))
 

	
 
    def test_inactive_user_group_does_not_affect_repo_permissions(self):
 
        self.ug1 = fixture.create_user_group('G1')
 
        self.ug1.inherit_default_permissions = False
 
        user_group_model = UserGroupModel()
 
        user_group_model.add_user_to_group(self.ug1, self.u1)
 
        user_group_model.update(self.ug1, {'users_group_active': False})
 

	
 
        # note: make u2 repo owner rather than u1, because the owner always has
 
        # admin permissions
 
        self.test_repo = fixture.create_repo(name='myownrepo',
 
                                             repo_type='hg',
 
                                             cur_user=self.u2)
 

	
 
        # enable admin access for user group on repo
 
        RepoModel().grant_user_group_permission(self.test_repo,
 
                                                group_name=self.ug1,
 
                                                perm='repository.admin')
 
        # enable only write access for default user on repo
 
        RepoModel().grant_user_permission(self.test_repo,
 
                                          user='default',
 
                                          perm='repository.write')
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
 
                         'repository.write')
 

	
 
    def test_inactive_user_group_does_not_affect_repo_permissions_inverse(self):
 
        self.ug1 = fixture.create_user_group('G1')
 
        self.ug1.inherit_default_permissions = False
 
        user_group_model = UserGroupModel()
 
        user_group_model.add_user_to_group(self.ug1, self.u1)
 
        user_group_model.update(self.ug1, {'users_group_active': False})
 

	
 
        # note: make u2 repo owner rather than u1, because the owner always has
 
        # admin permissions
 
        self.test_repo = fixture.create_repo(name='myownrepo',
 
                                             repo_type='hg',
 
                                             cur_user=self.u2)
 

	
 
        # enable only write access for user group on repo
 
        RepoModel().grant_user_group_permission(self.test_repo,
 
                                                group_name=self.ug1,
 
                                                perm='repository.write')
 
        # enable admin access for default user on repo
 
        RepoModel().grant_user_permission(self.test_repo,
 
                                          user='default',
 
                                          perm='repository.admin')
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
 
                         'repository.admin')
 

	
 
    def test_inactive_user_group_does_not_affect_repo_group_permissions(self):
 
        self.ug1 = fixture.create_user_group('G1')
 
        self.ug1.inherit_default_permissions = False
 
        user_group_model = UserGroupModel()
 
        user_group_model.add_user_to_group(self.ug1, self.u1)
 
        user_group_model.update(self.ug1, {'users_group_active': False})
 

	
 
        self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
 

	
 
        # enable admin access for user group on repo group
 
        RepoGroupModel().grant_user_group_permission(self.g1,
 
                                                     group_name=self.ug1,
 
                                                     perm='group.admin')
 
        # enable only write access for default user on repo group
 
        RepoGroupModel().grant_user_permission(self.g1,
 
                                               user='default',
 
                                               perm='group.write')
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.write'})
 

	
 
    def test_inactive_user_group_does_not_affect_repo_group_permissions_inverse(self):
 
        self.ug1 = fixture.create_user_group('G1')
 
        self.ug1.inherit_default_permissions = False
 
        user_group_model = UserGroupModel()
 
        user_group_model.add_user_to_group(self.ug1, self.u1)
 
        user_group_model.update(self.ug1, {'users_group_active': False})
 

	
 
        self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
 

	
 
        # enable only write access for user group on repo group
 
        RepoGroupModel().grant_user_group_permission(self.g1,
 
                                                     group_name=self.ug1,
 
                                                     perm='group.write')
 
        # enable admin access for default user on repo group
 
        RepoGroupModel().grant_user_permission(self.g1,
 
                                               user='default',
 
                                               perm='group.admin')
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.admin'})
 

	
 
    def test_inactive_user_group_does_not_affect_user_group_permissions(self):
 
        self.ug1 = fixture.create_user_group('G1')
 
        self.ug1.inherit_default_permissions = False
 
        user_group_model = UserGroupModel()
 
        user_group_model.add_user_to_group(self.ug1, self.u1)
 
        user_group_model.update(self.ug1, {'users_group_active': False})
 

	
 
        self.ug2 = fixture.create_user_group('G2')
 

	
 
        # enable admin access for user group on user group
 
        UserGroupModel().grant_user_group_permission(self.ug2,
 
                                                     user_group=self.ug1,
 
                                                     perm='usergroup.admin')
 
        # enable only write access for default user on user group
 
        UserGroupModel().grant_user_permission(self.ug2,
 
                                               user='default',
 
                                               perm='usergroup.write')
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['user_groups'][u'G1'], u'usergroup.read')
 
        self.assertEqual(u1_auth.permissions['user_groups'][u'G2'], u'usergroup.write')
 

	
 
    def test_inactive_user_group_does_not_affect_user_group_permissions_inverse(self):
 
        self.ug1 = fixture.create_user_group('G1')
 
        self.ug1.inherit_default_permissions = False
 
        user_group_model = UserGroupModel()
 
        user_group_model.add_user_to_group(self.ug1, self.u1)
 
        user_group_model.update(self.ug1, {'users_group_active': False})
 

	
 
        self.ug2 = fixture.create_user_group('G2')
 

	
 
        # enable only write access for user group on user group
 
        UserGroupModel().grant_user_group_permission(self.ug2,
 
                                                     user_group=self.ug1,
 
                                                     perm='usergroup.write')
 
        # enable admin access for default user on user group
 
        UserGroupModel().grant_user_permission(self.ug2,
 
                                               user='default',
 
                                               perm='usergroup.admin')
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['user_groups'][u'G1'], u'usergroup.read')
 
        self.assertEqual(u1_auth.permissions['user_groups'][u'G2'], u'usergroup.admin')
 

	
 
    def test_owner_permissions_doesnot_get_overwritten_by_group(self):
 
        #create repo as USER,
 
        self.test_repo = fixture.create_repo(name='myownrepo',
 
                                             repo_type='hg',
 
                                             cur_user=self.u1)
 

	
 
        #he has permissions of admin as owner
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
 
                         'repository.admin')
 
        #set his permission as user group, he should still be admin
 
        self.ug1 = fixture.create_user_group('G1')
 
        UserGroupModel().add_user_to_group(self.ug1, self.u1)
 
        RepoModel().grant_user_group_permission(self.test_repo,
 
                                                 group_name=self.ug1,
 
                                                 perm='repository.none')
 

	
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
 
                         'repository.admin')
 

	
 
    def test_owner_permissions_doesnot_get_overwritten_by_others(self):
 
        #create repo as USER,
 
        self.test_repo = fixture.create_repo(name='myownrepo',
 
                                             repo_type='hg',
 
                                             cur_user=self.u1)
 

	
 
        #he has permissions of admin as owner
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
 
                         'repository.admin')
 
        #set his permission as user, he should still be admin
 
        RepoModel().grant_user_permission(self.test_repo, user=self.u1,
 
                                          perm='repository.none')
 
        Session().commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
 
                         'repository.admin')
 

	
 
    def _test_def_perm_equal(self, user, change_factor=0):
 
        perms = UserToPerm.query()\
 
                .filter(UserToPerm.user == user)\
 
                .all()
 
        self.assertEqual(len(perms),
 
                         len(Permission.DEFAULT_USER_PERMISSIONS,)+change_factor,
 
                         msg=perms)
 

	
 
    def test_set_default_permissions(self):
 
        PermissionModel().create_default_permissions(user=self.u1)
 
        self._test_def_perm_equal(user=self.u1)
 

	
 
    def test_set_default_permissions_after_one_is_missing(self):
 
        PermissionModel().create_default_permissions(user=self.u1)
 
        self._test_def_perm_equal(user=self.u1)
 
        #now we delete one, it should be re-created after another call
 
        perms = UserToPerm.query()\
 
                .filter(UserToPerm.user == self.u1)\
 
                .all()
 
        Session().delete(perms[0])
 
        Session().commit()
 

	
 
        self._test_def_perm_equal(user=self.u1, change_factor=-1)
 

	
 
        #create missing one !
 
        PermissionModel().create_default_permissions(user=self.u1)
 
        self._test_def_perm_equal(user=self.u1)
 

	
 
    @parameterized.expand([
 
        ('repository.read', 'repository.none'),
 
        ('group.read', 'group.none'),
 
        ('usergroup.read', 'usergroup.none'),
 
        ('hg.create.repository', 'hg.create.none'),
 
        ('hg.fork.repository', 'hg.fork.none'),
 
        ('hg.register.manual_activate', 'hg.register.auto_activate',)
 
    ])
 
    def test_set_default_permissions_after_modification(self, perm, modify_to):
 
        PermissionModel().create_default_permissions(user=self.u1)
 
        self._test_def_perm_equal(user=self.u1)
 

	
 
        old = Permission.get_by_key(perm)
 
        new = Permission.get_by_key(modify_to)
 
        self.assertNotEqual(old, None)
 
        self.assertNotEqual(new, None)
 

	
 
        #now modify permissions
 
        p = UserToPerm.query()\
 
                .filter(UserToPerm.user == self.u1)\
 
                .filter(UserToPerm.permission == old)\
 
                .one()
 
        p.permission = new
 
        Session().add(p)
 
        Session().commit()
 

	
 
        PermissionModel().create_default_permissions(user=self.u1)
 
        self._test_def_perm_equal(user=self.u1)
0 comments (0 inline, 0 general)