aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMilo Casagrande <milo@ubuntu.com>2013-02-13 18:35:00 +0100
committerMilo Casagrande <milo@ubuntu.com>2013-02-13 18:35:00 +0100
commitf6e9d7be791b269967311ba281b5705361112669 (patch)
tree3da66a5f98dbb2975d00085dc2a3a781ec73d808
parentd6b2ec12ceddb987c6f9df411f1c02fe32b1fe0a (diff)
parent637fcc9fc3f7f177097ed2c590d98a077cbeee26 (diff)
Merge branch 'master' into linaro
Fixed conflicts: setup.py
-rw-r--r--.hgtags1
-rw-r--r--CONTRIBUTORS1
-rw-r--r--docs/api/api.rst280
-rwxr-xr-xdocs/changelog.rst32
-rw-r--r--docs/installation.rst4
-rw-r--r--rhodecode/__init__.py4
-rw-r--r--rhodecode/config/routing.py6
-rw-r--r--rhodecode/controllers/admin/notifications.py12
-rw-r--r--rhodecode/controllers/admin/permissions.py81
-rw-r--r--rhodecode/controllers/admin/repos.py38
-rw-r--r--rhodecode/controllers/admin/repos_groups.py48
-rw-r--r--rhodecode/controllers/admin/settings.py61
-rw-r--r--rhodecode/controllers/admin/users.py44
-rw-r--r--rhodecode/controllers/api/__init__.py24
-rw-r--r--rhodecode/controllers/api/api.py170
-rw-r--r--rhodecode/controllers/changeset.py25
-rw-r--r--rhodecode/controllers/compare.py7
-rw-r--r--rhodecode/controllers/home.py54
-rw-r--r--rhodecode/controllers/journal.py81
-rw-r--r--rhodecode/controllers/login.py7
-rw-r--r--rhodecode/controllers/pullrequests.py7
-rw-r--r--rhodecode/i18n/ja/LC_MESSAGES/rhodecode.mobin61119 -> 65250 bytes
-rw-r--r--rhodecode/i18n/ja/LC_MESSAGES/rhodecode.po265
-rw-r--r--rhodecode/i18n/pl/LC_MESSAGES/rhodecode.po517
-rw-r--r--rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.mobin51776 -> 57171 bytes
-rw-r--r--rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.po248
-rw-r--r--rhodecode/lib/auth.py190
-rw-r--r--rhodecode/lib/base.py29
-rw-r--r--rhodecode/lib/celerylib/tasks.py4
-rw-r--r--rhodecode/lib/db_manage.py7
-rwxr-xr-xrhodecode/lib/dbmigrate/schema/db_1_2_0.py2
-rw-r--r--rhodecode/lib/dbmigrate/schema/db_1_3_0.py2
-rw-r--r--rhodecode/lib/dbmigrate/schema/db_1_5_0.py1814
-rw-r--r--rhodecode/lib/dbmigrate/schema/db_1_5_2.py28
-rw-r--r--rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py15
-rw-r--r--rhodecode/lib/dbmigrate/versions/008_version_1_5_0.py2
-rw-r--r--rhodecode/lib/dbmigrate/versions/010_version_1_5_2.py50
-rw-r--r--rhodecode/lib/dbmigrate/versions/__init__.py20
-rw-r--r--rhodecode/lib/diffs.py2
-rw-r--r--rhodecode/lib/helpers.py8
-rw-r--r--rhodecode/lib/ipaddr.py1901
-rw-r--r--rhodecode/lib/markup_renderer.py2
-rw-r--r--rhodecode/lib/middleware/simplegit.py13
-rw-r--r--rhodecode/lib/middleware/simplehg.py13
-rw-r--r--rhodecode/lib/update_repoinfo.py6
-rw-r--r--rhodecode/lib/utils.py31
-rw-r--r--rhodecode/lib/vcs/backends/base.py1
-rw-r--r--rhodecode/lib/vcs/backends/git/repository.py11
-rw-r--r--rhodecode/lib/vcs/nodes.py5
-rw-r--r--rhodecode/model/changeset_status.py33
-rwxr-xr-xrhodecode/model/db.py126
-rw-r--r--rhodecode/model/forms.py12
-rw-r--r--rhodecode/model/notification.py5
-rw-r--r--rhodecode/model/pull_request.py16
-rw-r--r--rhodecode/model/repo.py110
-rw-r--r--rhodecode/model/repos_group.py2
-rw-r--r--rhodecode/model/scm.py5
-rw-r--r--rhodecode/model/user.py57
-rw-r--r--rhodecode/model/validators.py43
-rw-r--r--rhodecode/public/css/style.css25
-rw-r--r--rhodecode/public/js/rhodecode.js7
-rw-r--r--rhodecode/templates/admin/admin.html1
-rw-r--r--rhodecode/templates/admin/admin_log.html2
-rw-r--r--rhodecode/templates/admin/permissions/permissions.html121
-rw-r--r--rhodecode/templates/admin/repos/repos.html13
-rw-r--r--rhodecode/templates/admin/users/user_edit.html56
-rw-r--r--rhodecode/templates/admin/users/user_edit_my_account.html227
-rw-r--r--rhodecode/templates/admin/users/user_edit_my_account_form.html6
-rw-r--r--rhodecode/templates/admin/users/user_edit_my_account_repos.html46
-rw-r--r--rhodecode/templates/base/root.html5
-rw-r--r--rhodecode/templates/changelog/changelog.html3
-rw-r--r--rhodecode/templates/changeset/changeset.html10
-rw-r--r--rhodecode/templates/changeset/changeset_file_comment.html7
-rw-r--r--rhodecode/templates/compare/compare_diff.html4
-rw-r--r--rhodecode/templates/data_table/_dt_elements.html29
-rw-r--r--rhodecode/templates/email_templates/pull_request.html5
-rw-r--r--rhodecode/templates/files/files_source.html9
-rw-r--r--rhodecode/templates/index_base.html24
-rw-r--r--rhodecode/templates/journal/journal.html387
-rw-r--r--rhodecode/templates/journal/journal_page_repos.html47
-rw-r--r--rhodecode/templates/pullrequests/pullrequest_show.html20
-rw-r--r--rhodecode/templates/settings/repo_settings.html2
-rw-r--r--rhodecode/tests/api/api_base.py292
-rw-r--r--rhodecode/tests/functional/test_admin_notifications.py1
-rw-r--r--rhodecode/tests/functional/test_compare.py12
-rw-r--r--rhodecode/tests/functional/test_compare_local.py12
-rw-r--r--rhodecode/tests/functional/test_home.py46
-rw-r--r--rhodecode/tests/functional/test_journal.py5
-rw-r--r--rhodecode/tests/models/test_user_permissions_on_repos.py2
-rwxr-xr-xrhodecode/tests/scripts/test_vcs_operations.py50
-rw-r--r--rhodecode/tests/test_libs.py5
-rw-r--r--setup.py4
-rw-r--r--test.ini12
93 files changed, 6592 insertions, 1487 deletions
diff --git a/.hgtags b/.hgtags
index 6c51bfea..6cffccc8 100644
--- a/.hgtags
+++ b/.hgtags
@@ -60,3 +60,4 @@ d998cc84cf726798486a438763053f0e1dc1b646 v1.4.2
3148c08cf86f1849917e2d50f7ab7766c1550b0a v1.4.4
a5f0bc867edc88be23eb808693e5393a97d4c54a v1.5.0
3259dc7caea48687eab018ee646ae6ad7e7ef377 v1.5.1
+efe23d6c178c11d575a0214181276a3452776e48 v1.5.2
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 8e0bd927..bb64bbfa 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -32,3 +32,4 @@ List of contributors to RhodeCode project:
Raoul Thill <raoul.thill@gmail.com>
Philip Jameson <philip.j@hostdime.com>
Mads Kiilerich <madski@unity3d.com>
+ Dan Sheridan <djs@adelard.com>
diff --git a/docs/api/api.rst b/docs/api/api.rst
index 82bfa22d..3d1b8152 100644
--- a/docs/api/api.rst
+++ b/docs/api/api.rst
@@ -155,9 +155,10 @@ OUTPUT::
lock
----
-Set locking state on given repository by given user.
+Set locking state on given repository by given user. If userid param is skipped
+, then it is set to id of user whos calling this method.
This command can be executed only using api_key belonging to user with admin
-rights.
+rights or regular user that have admin or write access to repository.
INPUT::
@@ -166,9 +167,8 @@ INPUT::
method : "lock"
args : {
"repoid" : "<reponame or repo_id>"
- "userid" : "<user_id or username>",
+ "userid" : "<user_id or username = Optional(=apiuser)>",
"locked" : "<bool true|false>"
-
}
OUTPUT::
@@ -178,12 +178,47 @@ OUTPUT::
error : null
+show_ip
+-------
+
+Shows IP address as seen from RhodeCode server, together with all
+defined IP addresses for given user.
+This command can be executed only using api_key belonging to user with admin
+rights.
+
+INPUT::
+
+ id : <id_for_response>
+ api_key : "<api_key>"
+ method : "show_ip"
+ args : {
+ "userid" : "<user_id or username>",
+ }
+
+OUTPUT::
+
+ id : <id_given_in_input>
+ result : {
+ "ip_addr_server": <ip_from_clien>",
+ "user_ips": [
+ {
+ "ip_addr": "<ip_with_mask>",
+ "ip_range": ["<start_ip>", "<end_ip>"],
+ },
+ ...
+ ]
+ }
+
+ error : null
+
+
get_user
--------
Get's an user by username or user_id, Returns empty result if user is not found.
+If userid param is skipped it is set to id of user who is calling this method.
This command can be executed only using api_key belonging to user with admin
-rights.
+rights, or regular users that cannot specify different userid than theirs
INPUT::
@@ -192,7 +227,7 @@ INPUT::
api_key : "<api_key>"
method : "get_user"
args : {
- "userid" : "<username or user_id>"
+ "userid" : "<username or user_id Optional(=apiuser)>"
}
OUTPUT::
@@ -200,16 +235,17 @@ OUTPUT::
id : <id_given_in_input>
result: None if user does not exist or
{
- "user_id" : "<user_id>",
- "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>",
+ "user_id" : "<user_id>",
+ "username" : "<username>",
+ "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>",
"permissions": {
"global": ["hg.create.repository",
"repository.read",
@@ -241,16 +277,17 @@ OUTPUT::
id : <id_given_in_input>
result: [
{
- "user_id" : "<user_id>",
- "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>",
+ "user_id" : "<user_id>",
+ "username" : "<username>",
+ "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>",
},
]
@@ -315,14 +352,14 @@ INPUT::
method : "update_user"
args : {
"userid" : "<user_id or username>",
- "username" : "<username> = Optional",
- "email" : "<useremail> = Optional",
- "password" : "<password> = Optional",
- "firstname" : "<firstname> = Optional",
- "lastname" : "<lastname> = Optional",
- "active" : "<bool> = Optional",
- "admin" : "<bool> = Optional",
- "ldap_dn" : "<ldap_dn> = Optional"
+ "username" : "<username> = Optional(None)",
+ "email" : "<useremail> = Optional(None)",
+ "password" : "<password> = Optional(None)",
+ "firstname" : "<firstname> = Optional(None)",
+ "lastname" : "<lastname> = Optional(None)",
+ "active" : "<bool> = Optional(None)",
+ "admin" : "<bool> = Optional(None)",
+ "ldap_dn" : "<ldap_dn> = Optional(None)"
}
OUTPUT::
@@ -537,8 +574,9 @@ get_repo
--------
Gets an existing repository by it's name or repository_id. Members will return
-either users_group or user associated to that repository. This command can
-be executed only using api_key belonging to user with admin rights.
+either users_group or user associated to that repository. This command can be
+executed only using api_key belonging to user with admin
+rights or regular user that have at least read access to repository.
INPUT::
@@ -555,29 +593,40 @@ OUTPUT::
id : <id_given_in_input>
result: None if repository does not exist or
{
- "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>",
+ "repo_id" : "<repo_id>",
+ "repo_name" : "<reponame>"
+ "repo_type" : "<repo_type>",
+ "clone_uri" : "<clone_uri>",
+ "enable_downloads": "<bool>",
+ "enable_locking": "<bool>",
+ "enable_statistics": "<bool>",
+ "private": "<bool>",
+ "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>",
- "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>",
+ "user_id" : "<user_id>",
+ "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)"
},
@@ -597,8 +646,9 @@ OUTPUT::
get_repos
---------
-Lists all existing repositories. This command can be executed only using api_key
-belonging to user with admin rights
+Lists all existing repositories. This command can be executed only using
+api_key belonging to user with admin rights or regular user that have
+admin, write or read access to repository.
INPUT::
@@ -613,16 +663,19 @@ 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>",
+ "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>",
},
]
@@ -666,11 +719,12 @@ OUTPUT::
create_repo
-----------
-Creates a repository. This command can be executed only using api_key
-belonging to user with admin rights.
-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.
+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
INPUT::
@@ -679,13 +733,16 @@ INPUT::
api_key : "<api_key>"
method : "create_repo"
args: {
- "repo_name" : "<reponame>",
- "owner" : "<onwer_name_or_id>",
- "repo_type" : "<repo_type>",
- "description" : "<description> = Optional('')",
- "private" : "<bool> = Optional(False)",
- "clone_uri" : "<clone_uri> = Optional(None)",
- "landing_rev" : "<landing_rev> = Optional('tip')",
+ "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::
@@ -694,26 +751,65 @@ OUTPUT::
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": "<repo_owner>",
- "fork_of": "<name_of_fork_parent>",
+ "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
+---------
+
+Creates a fork of given repo. In case of using celery this will
+immidiatelly 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.
+
+
+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
-----------
-Deletes a repository. This command can be executed only using api_key
-belonging to user with admin rights.
+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.
INPUT::
diff --git a/docs/changelog.rst b/docs/changelog.rst
index c122c064..0e0345f0 100755
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,6 +4,38 @@
Changelog
=========
+1.5.2 (**2013-01-14**)
+----------------------
+
+news
+++++
+
+- IP restrictions for users. Each user can get a set of whitelist IP+mask for
+ extra protection. Useful for buildbots etc.
+- added full last changeset info to lightweight dashboard. lightweight dashboard
+ is now fully functional replacement of original dashboard.
+- implemented certain API calls for non-admin users.
+- enabled all Markdown Extra plugins
+- implemented #725 Pull Request View - Show origin repo URL
+- show comments from pull requests into associated changesets
+
+fixes
++++++
+
+- update repoinfo script is more failsafe
+- fixed #687 Lazy loaded tooltip bug with simultaneous ajax requests
+- fixed #691: Notifications for pull requests: move link to top for better
+ readability
+- fixed #699: fix missing fork docs for API
+- fixed #693 Opening changeset from pull request fails
+- fixed #710 File view stripping empty lines from beginning and end of file
+- fixed issues with getting repos by path on windows, caused GIT hooks to fail
+- fixed issues with groups paginator on main dashboard
+- improved fetch/pull command for git repos, now pulling all refs
+- fixed issue #719 Journal revision ID tooltip AJAX query path is incorrect
+ when running in a subdir
+- fixed issue #702 API methods without arguments fail when "args":null
+- set the status of changesets initially on pull request. Fixes issues #690 and #587
1.5.1 (**2012-12-13**)
----------------------
diff --git a/docs/installation.rst b/docs/installation.rst
index 203bc264..b634ffef 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -43,6 +43,10 @@ For installing RhodeCode i highly recommend using separate virtualenv_. This
way many required by RhodeCode libraries will remain sandboxed from your main
python and making things less problematic when doing system python updates.
+Alternative very detailed installation instructions for Ubuntu Server with
+celery, indexer and daemon scripts: https://gist.github.com/4546398
+
+
- Assuming you have installed virtualenv_ create a new virtual environment
using virtualenv command::
diff --git a/rhodecode/__init__.py b/rhodecode/__init__.py
index 83a936af..03d8d5df 100644
--- a/rhodecode/__init__.py
+++ b/rhodecode/__init__.py
@@ -26,7 +26,7 @@
import sys
import platform
-VERSION = (1, 5, 1)
+VERSION = (1, 5, 2)
try:
from rhodecode.lib import get_current_revision
@@ -38,7 +38,7 @@ except ImportError:
__version__ = ('.'.join((str(each) for each in VERSION[:3])) +
'.'.join(VERSION[3:]))
-__dbversion__ = 9 # defines current db version for migrations
+__dbversion__ = 10 # defines current db version for migrations
__platform__ = platform.system()
__license__ = 'GPLv3'
__py_version__ = sys.version_info
diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py
index 1018e35b..365bcdb0 100644
--- a/rhodecode/config/routing.py
+++ b/rhodecode/config/routing.py
@@ -222,6 +222,10 @@ def make_map(config):
action="add_email", conditions=dict(method=["PUT"]))
m.connect("user_emails_delete", "/users_emails/{id}",
action="delete_email", conditions=dict(method=["DELETE"]))
+ m.connect("user_ips", "/users_ips/{id}",
+ action="add_ip", conditions=dict(method=["PUT"]))
+ m.connect("user_ips_delete", "/users_ips/{id}",
+ action="delete_ip", conditions=dict(method=["DELETE"]))
#ADMIN USERS GROUPS REST ROUTES
with rmap.submapper(path_prefix=ADMIN_PREFIX,
@@ -355,8 +359,6 @@ def make_map(config):
m.connect('api', '/api')
#USER JOURNAL
- rmap.connect('journal_my_repos', '%s/journal_my_repos' % ADMIN_PREFIX,
- controller='journal', action='index_my_repos')
rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
controller='journal', action='index')
rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
diff --git a/rhodecode/controllers/admin/notifications.py b/rhodecode/controllers/admin/notifications.py
index 90cfd69b..221aa00b 100644
--- a/rhodecode/controllers/admin/notifications.py
+++ b/rhodecode/controllers/admin/notifications.py
@@ -110,8 +110,8 @@ class NotificationsController(BaseController):
# url('notification', notification_id=ID)
try:
no = Notification.get(notification_id)
- owner = lambda: (no.notifications_to_users.user.user_id
- == c.rhodecode_user.user_id)
+ owner = all(un.user.user_id == c.rhodecode_user.user_id
+ for un in no.notifications_to_users)
if h.HasPermissionAny('hg.admin')() or owner:
NotificationModel().mark_read(c.rhodecode_user.user_id, no)
Session().commit()
@@ -132,8 +132,8 @@ class NotificationsController(BaseController):
try:
no = Notification.get(notification_id)
- owner = lambda: (no.notifications_to_users.user.user_id
- == c.rhodecode_user.user_id)
+ owner = all(un.user.user_id == c.rhodecode_user.user_id
+ for un in no.notifications_to_users)
if h.HasPermissionAny('hg.admin')() or owner:
NotificationModel().delete(c.rhodecode_user.user_id, no)
Session().commit()
@@ -149,8 +149,8 @@ class NotificationsController(BaseController):
c.user = self.rhodecode_user
no = Notification.get(notification_id)
- owner = lambda: (no.notifications_to_users.user.user_id
- == c.user.user_id)
+ owner = all(un.user.user_id == c.rhodecode_user.user_id
+ for un in no.notifications_to_users)
if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
unotification = NotificationModel()\
.get_user_notification(c.user.user_id, no)
diff --git a/rhodecode/controllers/admin/permissions.py b/rhodecode/controllers/admin/permissions.py
index bdbaeddd..8acee302 100644
--- a/rhodecode/controllers/admin/permissions.py
+++ b/rhodecode/controllers/admin/permissions.py
@@ -33,11 +33,12 @@ from pylons.controllers.util import abort, redirect
from pylons.i18n.translation import _
from rhodecode.lib import helpers as h
-from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
+from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator,\
+ AuthUser
from rhodecode.lib.base import BaseController, render
from rhodecode.model.forms import DefaultPermissionsForm
from rhodecode.model.permission import PermissionModel
-from rhodecode.model.db import User
+from rhodecode.model.db import User, UserIpMap
from rhodecode.model.meta import Session
log = logging.getLogger(__name__)
@@ -105,36 +106,41 @@ class PermissionsController(BaseController):
# h.form(url('permission', id=ID),
# method='put')
# url('permission', id=ID)
-
- permission_model = PermissionModel()
-
- _form = DefaultPermissionsForm([x[0] for x in self.repo_perms_choices],
- [x[0] for x in self.group_perms_choices],
- [x[0] for x in self.register_choices],
- [x[0] for x in self.create_choices],
- [x[0] for x in self.fork_choices])()
-
- try:
- form_result = _form.to_python(dict(request.POST))
- form_result.update({'perm_user_name': id})
- permission_model.update(form_result)
- Session().commit()
- h.flash(_('Default permissions updated successfully'),
- category='success')
-
- except formencode.Invalid, errors:
- defaults = errors.value
-
- return htmlfill.render(
- render('admin/permissions/permissions.html'),
- defaults=defaults,
- errors=errors.error_dict or {},
- prefix_error=False,
- encoding="UTF-8")
- except Exception:
- log.error(traceback.format_exc())
- h.flash(_('error occurred during update of permissions'),
- category='error')
+ if id == 'default':
+ c.user = default_user = User.get_by_username('default')
+ c.perm_user = AuthUser(user_id=default_user.user_id)
+ c.user_ip_map = UserIpMap.query()\
+ .filter(UserIpMap.user == default_user).all()
+ permission_model = PermissionModel()
+
+ _form = DefaultPermissionsForm(
+ [x[0] for x in self.repo_perms_choices],
+ [x[0] for x in self.group_perms_choices],
+ [x[0] for x in self.register_choices],
+ [x[0] for x in self.create_choices],
+ [x[0] for x in self.fork_choices])()
+
+ try:
+ form_result = _form.to_python(dict(request.POST))
+ form_result.update({'perm_user_name': id})
+ permission_model.update(form_result)
+ Session().commit()
+ h.flash(_('Default permissions updated successfully'),
+ category='success')
+
+ except formencode.Invalid, errors:
+ defaults = errors.value
+
+ return htmlfill.render(
+ render('admin/permissions/permissions.html'),
+ defaults=defaults,
+ errors=errors.error_dict or {},
+ prefix_error=False,
+ encoding="UTF-8")
+ except Exception:
+ log.error(traceback.format_exc())
+ h.flash(_('error occurred during update of permissions'),
+ category='error')
return redirect(url('edit_permission', id=id))
@@ -157,10 +163,11 @@ class PermissionsController(BaseController):
#this form can only edit default user permissions
if id == 'default':
- default_user = User.get_by_username('default')
- defaults = {'_method': 'put',
- 'anonymous': default_user.active}
-
+ c.user = default_user = User.get_by_username('default')
+ defaults = {'anonymous': default_user.active}
+ c.perm_user = AuthUser(user_id=default_user.user_id)
+ c.user_ip_map = UserIpMap.query()\
+ .filter(UserIpMap.user == default_user).all()
for p in default_user.user_perms:
if p.permission.permission_name.startswith('repository.'):
defaults['default_repo_perm'] = p.permission.permission_name
@@ -181,7 +188,7 @@ class PermissionsController(BaseController):
render('admin/permissions/permissions.html'),
defaults=defaults,
encoding="UTF-8",
- force_defaults=True,
+ force_defaults=False
)
else:
return redirect(url('admin_home'))
diff --git a/rhodecode/controllers/admin/repos.py b/rhodecode/controllers/admin/repos.py
index ff9efd1a..ac9372e0 100644
--- a/rhodecode/controllers/admin/repos.py
+++ b/rhodecode/controllers/admin/repos.py
@@ -135,40 +135,10 @@ class ReposController(BaseController):
.order_by(func.lower(Repository.repo_name))\
.all()
- repos_data = []
- total_records = len(c.repos_list)
-
- _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
- template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
-
- quick_menu = lambda repo_name: (template.get_def("quick_menu")
- .render(repo_name, _=_, h=h, c=c))
- repo_lnk = lambda name, rtype, private, fork_of: (
- template.get_def("repo_name")
- .render(name, rtype, private, fork_of, short_name=False,
- admin=True, _=_, h=h, c=c))
-
- repo_actions = lambda repo_name: (template.get_def("repo_actions")
- .render(repo_name, _=_, h=h, c=c))
-
- for repo in c.repos_list:
- repos_data.append({
- "menu": quick_menu(repo.repo_name),
- "raw_name": repo.repo_name.lower(),
- "name": repo_lnk(repo.repo_name, repo.repo_type,
- repo.private, repo.fork),
- "desc": repo.description,
- "owner": repo.user.username,
- "action": repo_actions(repo.repo_name),
- })
-
- c.data = json.dumps({
- "totalRecords": total_records,
- "startIndex": 0,
- "sort": "name",
- "dir": "asc",
- "records": repos_data
- })
+ repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
+ admin=True)
+ #json used to render the grid
+ c.data = json.dumps(repos_data)
return render('admin/repos/repos.html')
diff --git a/rhodecode/controllers/admin/repos_groups.py b/rhodecode/controllers/admin/repos_groups.py
index 6f62ee8e..97ded63d 100644
--- a/rhodecode/controllers/admin/repos_groups.py
+++ b/rhodecode/controllers/admin/repos_groups.py
@@ -295,54 +295,18 @@ class ReposGroupsController(BaseController):
c.groups = self.scm_model.get_repos_groups(groups)
if c.visual.lightweight_dashboard is False:
- c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
-
- c.repos_list = c.cached_repo_list
+ c.repos_list = self.scm_model.get_repos(all_repos=gr_filter)
## lightweight version of dashboard
else:
c.repos_list = Repository.query()\
.filter(Repository.group_id == id)\
.order_by(func.lower(Repository.repo_name))\
.all()
- repos_data = []
- total_records = len(c.repos_list)
-
- _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
- template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
-
- quick_menu = lambda repo_name: (template.get_def("quick_menu")
- .render(repo_name, _=_, h=h, c=c))
- repo_lnk = lambda name, rtype, private, fork_of: (
- template.get_def("repo_name")
- .render(name, rtype, private, fork_of, short_name=False,
- admin=False, _=_, h=h, c=c))
- last_change = lambda last_change: (template.get_def("last_change")
- .render(last_change, _=_, h=h, c=c))
- rss_lnk = lambda repo_name: (template.get_def("rss")
- .render(repo_name, _=_, h=h, c=c))
- atom_lnk = lambda repo_name: (template.get_def("atom")
- .render(repo_name, _=_, h=h, c=c))
-
- for repo in c.repos_list:
- repos_data.append({
- "menu": quick_menu(repo.repo_name),
- "raw_name": repo.repo_name.lower(),
- "name": repo_lnk(repo.repo_name, repo.repo_type,
- repo.private, repo.fork),
- "last_change": last_change(repo.last_db_change),
- "desc": repo.description,
- "owner": h.person(repo.user.username),
- "rss": rss_lnk(repo.repo_name),
- "atom": atom_lnk(repo.repo_name),
- })
-
- c.data = json.dumps({
- "totalRecords": total_records,
- "startIndex": 0,
- "sort": "name",
- "dir": "asc",
- "records": repos_data
- })
+
+ repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
+ admin=False)
+ #json used to render the grid
+ c.data = json.dumps(repos_data)
return render('admin/repos_groups/repos_groups.html')
diff --git a/rhodecode/controllers/admin/settings.py b/rhodecode/controllers/admin/settings.py
index 32ba99a2..30181d62 100644
--- a/rhodecode/controllers/admin/settings.py
+++ b/rhodecode/controllers/admin/settings.py
@@ -48,11 +48,12 @@ from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
ApplicationUiSettingsForm, ApplicationVisualisationForm
from rhodecode.model.scm import ScmModel
from rhodecode.model.user import UserModel
+from rhodecode.model.repo import RepoModel
from rhodecode.model.db import User
from rhodecode.model.notification import EmailNotificationModel
from rhodecode.model.meta import Session
-from rhodecode.lib.utils2 import str2bool
-
+from rhodecode.lib.utils2 import str2bool, safe_unicode
+from rhodecode.lib.compat import json
log = logging.getLogger(__name__)
@@ -119,10 +120,11 @@ class SettingsController(BaseController):
invalidate_cache('get_repo_cached_%s' % repo_name)
added, removed = repo2db_mapper(initial, rm_obsolete)
-
- h.flash(_('Repositories successfully'
- ' rescanned added: %s,removed: %s') % (added, removed),
- category='success')
+ _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
+ h.flash(_('Repositories successfully '
+ 'rescanned added: %s ; removed: %s') %
+ (_repr(added), _repr(removed)),
+ category='success')
if setting_id == 'whoosh':
repo_location = self._get_hg_ui_settings()['paths_root_path']
@@ -336,7 +338,7 @@ class SettingsController(BaseController):
.get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
body=test_email_body)
- recipients = [test_email] if [test_email] else None
+ recipients = [test_email] if test_email else None
run_task(tasks.send_email, recipients, test_email_subj,
test_email_body, test_email_html_body)
@@ -381,6 +383,17 @@ class SettingsController(BaseController):
force_defaults=False
)
+ def _load_my_repos_data(self):
+ repos_list = Session().query(Repository)\
+ .filter(Repository.user_id ==
+ self.rhodecode_user.user_id)\
+ .order_by(func.lower(Repository.repo_name)).all()
+
+ repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
+ admin=True)
+ #json used to render the grid
+ return json.dumps(repos_data)
+
@NotAnonymous()
def my_account(self):
"""
@@ -389,17 +402,16 @@ class SettingsController(BaseController):
# url('admin_settings_my_account')
c.user = User.get(self.rhodecode_user.user_id)
- all_repos = Session().query(Repository)\
- .filter(Repository.user_id == c.user.user_id)\
- .order_by(func.lower(Repository.repo_name)).all()
-
- c.user_repos = ScmModel().get_repos(all_repos)
+ c.ldap_dn = c.user.ldap_dn
if c.user.username == 'default':
h.flash(_("You can't edit this user since it's"
" crucial for entire application"), category='warning')
return redirect(url('users'))
+ #json used to render the grid
+ c.data = self._load_my_repos_data()
+
defaults = c.user.get_dict()
c.form = htmlfill.render(
@@ -420,19 +432,25 @@ class SettingsController(BaseController):
# method='put')
# url('admin_settings_my_account_update', id=ID)
uid = self.rhodecode_user.user_id
+ c.user = User.get(self.rhodecode_user.user_id)
+ c.ldap_dn = c.user.ldap_dn
email = self.rhodecode_user.email
_form = UserForm(edit=True,
old_data={'user_id': uid, 'email': email})()
form_result = {}
try:
form_result = _form.to_python(dict(request.POST))
- UserModel().update_my_account(uid, form_result)
+ skip_attrs = ['admin', 'active'] # skip attr for my account
+ if c.ldap_dn:
+ #forbid updating username for ldap accounts
+ skip_attrs.append('username')
+ UserModel().update(uid, form_result, skip_attrs=skip_attrs)
h.flash(_('Your account was updated successfully'),
category='success')
Session().commit()
except formencode.Invalid, errors:
- c.user = User.get(self.rhodecode_user.user_id)
-
+ #json used to render the grid
+ c.data = self._load_my_repos_data()
c.form = htmlfill.render(
render('admin/users/user_edit_my_account_form.html'),
defaults=errors.value,
@@ -448,23 +466,14 @@ class SettingsController(BaseController):
return redirect(url('my_account'))
@NotAnonymous()
- def my_account_my_repos(self):
- all_repos = Session().query(Repository)\
- .filter(Repository.user_id == self.rhodecode_user.user_id)\
- .order_by(func.lower(Repository.repo_name))\
- .all()
- c.user_repos = ScmModel().get_repos(all_repos)
- return render('admin/users/user_edit_my_account_repos.html')
-
- @NotAnonymous()
def my_account_my_pullrequests(self):
c.my_pull_requests = PullRequest.query()\
- .filter(PullRequest.user_id==
+ .filter(PullRequest.user_id ==
self.rhodecode_user.user_id)\
.all()
c.participate_in_pull_requests = \
[x.pull_request for x in PullRequestReviewers.query()\
- .filter(PullRequestReviewers.user_id==
+ .filter(PullRequestReviewers.user_id ==
self.rhodecode_user.user_id)\
.all()]
return render('admin/users/user_edit_my_account_pullrequests.html')
diff --git a/rhodecode/controllers/admin/users.py b/rhodecode/controllers/admin/users.py
index e8d222ef..6b815bf4 100644
--- a/rhodecode/controllers/admin/users.py
+++ b/rhodecode/controllers/admin/users.py
@@ -41,7 +41,7 @@ from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
AuthUser
from rhodecode.lib.base import BaseController, render
-from rhodecode.model.db import User, UserEmailMap
+from rhodecode.model.db import User, UserEmailMap, UserIpMap
from rhodecode.model.forms import UserForm
from rhodecode.model.user import UserModel
from rhodecode.model.meta import Session
@@ -159,7 +159,7 @@ class UsersController(BaseController):
user_model = UserModel()
c.user = user_model.get(id)
c.ldap_dn = c.user.ldap_dn
- c.perm_user = AuthUser(user_id=id)
+ c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
_form = UserForm(edit=True, old_data={'user_id': id,
'email': c.user.email})()
form_result = {}
@@ -178,6 +178,8 @@ class UsersController(BaseController):
except formencode.Invalid, errors:
c.user_email_map = UserEmailMap.query()\
.filter(UserEmailMap.user == c.user).all()
+ c.user_ip_map = UserIpMap.query()\
+ .filter(UserIpMap.user == c.user).all()
defaults = errors.value
e = errors.error_dict or {}
defaults.update({
@@ -231,12 +233,14 @@ class UsersController(BaseController):
h.flash(_("You can't edit this user"), category='warning')
return redirect(url('users'))
- c.perm_user = AuthUser(user_id=id)
+ c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
c.user.permissions = {}
c.granted_permissions = UserModel().fill_perms(c.user)\
.permissions['global']
c.user_email_map = UserEmailMap.query()\
.filter(UserEmailMap.user == c.user).all()
+ c.user_ip_map = UserIpMap.query()\
+ .filter(UserIpMap.user == c.user).all()
user_model = UserModel()
c.ldap_dn = c.user.ldap_dn
defaults = c.user.get_dict()
@@ -299,7 +303,6 @@ class UsersController(BaseController):
"""POST /user_emails:Add an existing item"""
# url('user_emails', id=ID, method='put')
- #TODO: validation and form !!!
email = request.POST.get('new_email')
user_model = UserModel()
@@ -324,3 +327,36 @@ class UsersController(BaseController):
Session().commit()
h.flash(_("Removed email from user"), category='success')
return redirect(url('edit_user', id=id))
+
+ 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 %s to user") % 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('edit_permission', id='default'))
+ return redirect(url('edit_user', id=id))
+
+ def delete_ip(self, id):
+ """DELETE /user_ips_delete/id: Delete an existing item"""
+ # url('user_ips_delete', id=ID, method='delete')
+ user_model = UserModel()
+ user_model.delete_extra_ip(id, request.POST.get('del_ip'))
+ Session().commit()
+ h.flash(_("Removed ip from user"), category='success')
+ if 'default_user' in request.POST:
+ return redirect(url('edit_permission', id='default'))
+ return redirect(url('edit_user', id=id))
diff --git a/rhodecode/controllers/api/__init__.py b/rhodecode/controllers/api/__init__.py
index 13fd7033..2f4e1416 100644
--- a/rhodecode/controllers/api/__init__.py
+++ b/rhodecode/controllers/api/__init__.py
@@ -32,17 +32,15 @@ import urllib
import traceback
import time
-from rhodecode.lib.compat import izip_longest, json
-
from paste.response import replace_header
-
from pylons.controllers import WSGIController
-
from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
HTTPBadRequest, HTTPError
from rhodecode.model.db import User
+from rhodecode.model import meta
+from rhodecode.lib.compat import izip_longest, json
from rhodecode.lib.auth import AuthUser
from rhodecode.lib.base import _get_ip_addr, _get_access_path
from rhodecode.lib.utils2 import safe_unicode
@@ -86,6 +84,9 @@ class JSONRPCController(WSGIController):
"""
+ def _get_ip_addr(self, environ):
+ return _get_ip_addr(environ)
+
def _get_method_args(self):
"""
Return `self._rpc_args` to dispatched controller method
@@ -99,6 +100,7 @@ class JSONRPCController(WSGIController):
controller and if it exists, dispatch to it.
"""
start = time.time()
+ ip_addr = self.ip_addr = self._get_ip_addr(environ)
self._req_id = None
if 'CONTENT_LENGTH' not in environ:
log.debug("No Content-Length")
@@ -130,6 +132,9 @@ class JSONRPCController(WSGIController):
self._req_id = json_body['id']
self._req_method = json_body['method']
self._request_params = json_body['args']
+ if not isinstance(self._request_params, dict):
+ self._request_params = {}
+
log.debug(
'method: %s, params: %s' % (self._req_method,
self._request_params)
@@ -144,7 +149,15 @@ class JSONRPCController(WSGIController):
if u is None:
return jsonrpc_error(retid=self._req_id,
message='Invalid API KEY')
- auth_u = AuthUser(u.user_id, self._req_api_key)
+
+ #check if we are allowed to use this IP
+ auth_u = AuthUser(u.user_id, self._req_api_key, ip_addr=ip_addr)
+ if not auth_u.ip_allowed:
+ return jsonrpc_error(retid=self._req_id,
+ message='request from IP:%s not allowed' % (ip_addr))
+ else:
+ log.info('Access for IP:%s allowed' % (ip_addr))
+
except Exception, e:
return jsonrpc_error(retid=self._req_id,
message='Invalid API KEY')
@@ -202,6 +215,7 @@ class JSONRPCController(WSGIController):
)
self._rpc_args = {USER_SESSION_ATTR: u}
+
self._rpc_args.update(self._request_params)
self._rpc_args['action'] = self._req_method
diff --git a/rhodecode/controllers/api/api.py b/rhodecode/controllers/api/api.py
index 93bec581..fc185df0 100644
--- a/rhodecode/controllers/api/api.py
+++ b/rhodecode/controllers/api/api.py
@@ -27,10 +27,12 @@
import traceback
import logging
+from pylons.controllers.util import abort
from rhodecode.controllers.api import JSONRPCController, JSONRPCError
-from rhodecode.lib.auth import HasPermissionAllDecorator, \
- HasPermissionAnyDecorator, PasswordGenerator, AuthUser
+from rhodecode.lib.auth import PasswordGenerator, AuthUser, \
+ HasPermissionAllDecorator, HasPermissionAnyDecorator, \
+ HasPermissionAnyApi, HasRepoPermissionAnyApi
from rhodecode.lib.utils import map_groups, repo2db_mapper
from rhodecode.model.meta import Session
from rhodecode.model.scm import ScmModel
@@ -38,11 +40,27 @@ from rhodecode.model.repo import RepoModel
from rhodecode.model.user import UserModel
from rhodecode.model.users_group import UsersGroupModel
from rhodecode.model.permission import PermissionModel
-from rhodecode.model.db import Repository
+from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap
log = logging.getLogger(__name__)
+class OptionalAttr(object):
+ """
+ Special Optional Option that defines other attribute
+ """
+ def __init__(self, attr_name):
+ self.attr_name = attr_name
+
+ def __repr__(self):
+ return '<OptionalAttr:%s>' % self.attr_name
+
+ def __call__(self):
+ return self
+#alias
+OAttr = OptionalAttr
+
+
class Optional(object):
"""
Defines an optional parameter::
@@ -184,10 +202,11 @@ class ApiController(JSONRPCController):
'Error occurred during rescan repositories action'
)
- @HasPermissionAllDecorator('hg.admin')
- def lock(self, apiuser, repoid, userid, locked):
+ def lock(self, apiuser, repoid, locked, userid=Optional(OAttr('apiuser'))):
"""
- Set locking state on particular repository by given user
+ Set locking state on particular repository by given user, if
+ this command is runned by non-admin account userid is set to user
+ who is calling this method
:param apiuser:
:param repoid:
@@ -195,6 +214,22 @@ class ApiController(JSONRPCController):
:param locked:
"""
repo = get_repo_or_error(repoid)
+ if HasPermissionAnyApi('hg.admin')(user=apiuser):
+ pass
+ elif HasRepoPermissionAnyApi('repository.admin',
+ 'repository.write')(user=apiuser,
+ repo_name=repo.repo_name):
+ #make sure normal user does not pass someone else userid,
+ #he is not allowed to do that
+ if not isinstance(userid, Optional) and userid != apiuser.user_id:
+ raise JSONRPCError(
+ 'userid is not the same as your user'
+ )
+ else:
+ raise JSONRPCError('repository `%s` does not exist' % (repoid))
+
+ if isinstance(userid, Optional):
+ userid = apiuser.user_id
user = get_user_or_error(userid)
locked = bool(locked)
try:
@@ -212,13 +247,38 @@ class ApiController(JSONRPCController):
)
@HasPermissionAllDecorator('hg.admin')
- def get_user(self, apiuser, userid):
+ def show_ip(self, apiuser, userid):
+ """
+ Shows IP address as seen from RhodeCode server, together with all
+ defined IP addresses for given user
+
+ :param apiuser:
+ :param userid:
+ """
+ user = get_user_or_error(userid)
+ ips = UserIpMap.query().filter(UserIpMap.user == user).all()
+ return dict(
+ ip_addr_server=self.ip_addr,
+ user_ips=ips
+ )
+
+ def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))):
""""
- Get a user by username
+ Get a user by username, or userid, if userid is given
:param apiuser:
:param userid:
"""
+ if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
+ #make sure normal user does not pass someone else userid,
+ #he is not allowed to do that
+ if not isinstance(userid, Optional) and userid != apiuser.user_id:
+ raise JSONRPCError(
+ 'userid is not the same as your user'
+ )
+
+ if isinstance(userid, Optional):
+ userid = apiuser.user_id
user = get_user_or_error(userid)
data = user.get_api_data()
@@ -479,7 +539,6 @@ class ApiController(JSONRPCController):
)
)
- @HasPermissionAnyDecorator('hg.admin')
def get_repo(self, apiuser, repoid):
""""
Get repository by name
@@ -489,6 +548,12 @@ class ApiController(JSONRPCController):
"""
repo = get_repo_or_error(repoid)
+ if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
+ # check if we have admin permission for this repo !
+ if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
+ repo_name=repo.repo_name) is False:
+ raise JSONRPCError('repository `%s` does not exist' % (repoid))
+
members = []
for user in repo.repo_to_perm:
perm = user.permission.permission_name
@@ -510,20 +575,23 @@ class ApiController(JSONRPCController):
data['members'] = members
return data
- @HasPermissionAnyDecorator('hg.admin')
def get_repos(self, apiuser):
""""
Get all repositories
:param apiuser:
"""
-
result = []
- for repo in RepoModel().get_all():
+ if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
+ repos = RepoModel().get_all_user_repos(user=apiuser)
+ else:
+ repos = RepoModel().get_all()
+
+ for repo in repos:
result.append(repo.get_api_data())
return result
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def get_repo_nodes(self, apiuser, repoid, revision, root_path,
ret_type='all'):
"""
@@ -556,12 +624,16 @@ class ApiController(JSONRPCController):
)
@HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
- def create_repo(self, apiuser, repo_name, owner, repo_type,
+ 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('tip')):
+ clone_uri=Optional(None), landing_rev=Optional('tip'),
+ enable_statistics=Optional(False),
+ enable_locking=Optional(False),
+ enable_downloads=Optional(False)):
"""
Create repository, if clone_url is given it makes a remote clone
- if repo_name is withina group name the groups will be created
+ if repo_name is within a group name the groups will be created
automatically if they aren't present
:param apiuser:
@@ -573,12 +645,32 @@ class ApiController(JSONRPCController):
:param clone_uri:
:param landing_rev:
"""
+ if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
+ if not isinstance(owner, Optional):
+ #forbid setting owner for non-admins
+ raise JSONRPCError(
+ 'Only RhodeCode 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)
- private = Optional.extract(private)
+ defs = RhodeCodeSetting.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)
@@ -596,32 +688,51 @@ class ApiController(JSONRPCController):
clone_uri=clone_uri,
repos_group=group,
landing_rev=landing_rev,
+ enable_statistics=enable_statistics,
+ enable_downloads=enable_downloads,
+ enable_locking=enable_locking
)
Session().commit()
-
return dict(
msg="Created new repository `%s`" % (repo.repo_name),
repo=repo.get_api_data()
)
-
except Exception:
log.error(traceback.format_exc())
raise JSONRPCError('failed to create repository `%s`' % repo_name)
- @HasPermissionAnyDecorator('hg.admin')
- def fork_repo(self, apiuser, repoid, fork_name, owner,
+ @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('tip')):
repo = get_repo_or_error(repoid)
repo_name = repo.repo_name
- owner = get_user_or_error(owner)
_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 RhodeCode admin can specify `owner` param'
+ )
+ 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)
@@ -652,7 +763,6 @@ class ApiController(JSONRPCController):
fork_name)
)
- @HasPermissionAnyDecorator('hg.admin')
def delete_repo(self, apiuser, repoid):
"""
Deletes a given repository
@@ -662,6 +772,12 @@ class ApiController(JSONRPCController):
"""
repo = get_repo_or_error(repoid)
+ if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
+ # check if we have admin permission for this repo !
+ if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
+ repo_name=repo.repo_name) is False:
+ raise JSONRPCError('repository `%s` does not exist' % (repoid))
+
try:
RepoModel().delete(repo)
Session().commit()
@@ -675,7 +791,7 @@ class ApiController(JSONRPCController):
'failed to delete repository `%s`' % repo.repo_name
)
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def grant_user_permission(self, apiuser, repoid, userid, perm):
"""
Grant permission for user on given repository, or update existing one
@@ -708,7 +824,7 @@ class ApiController(JSONRPCController):
)
)
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def revoke_user_permission(self, apiuser, repoid, userid):
"""
Revoke permission for user on given repository
@@ -739,7 +855,7 @@ class ApiController(JSONRPCController):
)
)
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def grant_users_group_permission(self, apiuser, repoid, usersgroupid,
perm):
"""
@@ -778,7 +894,7 @@ class ApiController(JSONRPCController):
)
)
- @HasPermissionAnyDecorator('hg.admin')
+ @HasPermissionAllDecorator('hg.admin')
def revoke_users_group_permission(self, apiuser, repoid, usersgroupid):
"""
Revoke permission for users group on given repository
diff --git a/rhodecode/controllers/changeset.py b/rhodecode/controllers/changeset.py
index 68a21d37..f982f04f 100644
--- a/rhodecode/controllers/changeset.py
+++ b/rhodecode/controllers/changeset.py
@@ -221,13 +221,25 @@ class ChangesetController(BaseRepoController):
for changeset in c.cs_ranges:
inlines = []
if method == 'show':
- c.statuses.extend([ChangesetStatusModel()\
- .get_status(c.rhodecode_db_repo.repo_id,
- changeset.raw_id)])
+ c.statuses.extend([ChangesetStatusModel().get_status(
+ c.rhodecode_db_repo.repo_id, changeset.raw_id)])
c.comments.extend(ChangesetCommentsModel()\
.get_comments(c.rhodecode_db_repo.repo_id,
revision=changeset.raw_id))
+
+ #comments from PR
+ st = ChangesetStatusModel().get_statuses(
+ c.rhodecode_db_repo.repo_id, changeset.raw_id,
+ with_revisions=True)
+ # from associated statuses, check the pull requests, and
+ # show comments from them
+
+ prs = set([x.pull_request for x in
+ filter(lambda x: x.pull_request != None, st)])
+
+ for pr in prs:
+ c.comments.extend(pr.comments)
inlines = ChangesetCommentsModel()\
.get_inline_comments(c.rhodecode_db_repo.repo_id,
revision=changeset.raw_id)
@@ -269,6 +281,9 @@ class ChangesetController(BaseRepoController):
cs_changes[''] = [None, None, None, None, diff, None]
c.changes[changeset.raw_id] = cs_changes
+ #sort comments by how they were generated
+ c.comments = sorted(c.comments, key=lambda x: x.comment_id)
+
# count inline comments
for __, lines in c.inline_comments:
for comments in lines.values():
@@ -342,7 +357,7 @@ class ChangesetController(BaseRepoController):
)
except StatusChangeOnClosedPullRequestError:
log.error(traceback.format_exc())
- msg = _('Changing status on a changeset associated with'
+ msg = _('Changing status on a changeset associated with '
'a closed pull request is not allowed')
h.flash(msg, category='warning')
return redirect(h.url('changeset_home', repo_name=repo_name,
@@ -371,7 +386,7 @@ class ChangesetController(BaseRepoController):
@jsonify
def delete_comment(self, repo_name, comment_id):
co = ChangesetComment.get(comment_id)
- owner = lambda: co.author.user_id == c.rhodecode_user.user_id
+ owner = co.author.user_id == c.rhodecode_user.user_id
if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
ChangesetCommentsModel().delete(comment=co)
Session().commit()
diff --git a/rhodecode/controllers/compare.py b/rhodecode/controllers/compare.py
index 9479c641..2f05a35e 100644
--- a/rhodecode/controllers/compare.py
+++ b/rhodecode/controllers/compare.py
@@ -103,8 +103,11 @@ class CompareController(BaseRepoController):
c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
- if c.org_repo is None or c.other_repo is None:
- log.error('Could not found repo %s or %s' % (org_repo, other_repo))
+ if c.org_repo is None:
+ log.error('Could not find org repo %s' % org_repo)
+ raise HTTPNotFound
+ if c.other_repo is None:
+ log.error('Could not find other repo %s' % other_repo)
raise HTTPNotFound
if c.org_repo != c.other_repo and h.is_git(c.rhodecode_repo):
diff --git a/rhodecode/controllers/home.py b/rhodecode/controllers/home.py
index bc94da7f..3515cb08 100644
--- a/rhodecode/controllers/home.py
+++ b/rhodecode/controllers/home.py
@@ -28,6 +28,7 @@ import logging
from pylons import tmpl_context as c, request
from pylons.i18n.translation import _
from webob.exc import HTTPBadRequest
+from sqlalchemy.sql.expression import func
import rhodecode
from rhodecode.lib import helpers as h
@@ -35,7 +36,8 @@ from rhodecode.lib.ext_json import json
from rhodecode.lib.auth import LoginRequired
from rhodecode.lib.base import BaseController, render
from rhodecode.model.db import Repository
-from sqlalchemy.sql.expression import func
+from rhodecode.model.repo import RepoModel
+
log = logging.getLogger(__name__)
@@ -58,51 +60,11 @@ class HomeController(BaseController):
.filter(Repository.group_id == None)\
.order_by(func.lower(Repository.repo_name))\
.all()
- repos_data = []
- total_records = len(c.repos_list)
-
- _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
- template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
-
- quick_menu = lambda repo_name: (template.get_def("quick_menu")
- .render(repo_name, _=_, h=h, c=c))
- repo_lnk = lambda name, rtype, private, fork_of: (
- template.get_def("repo_name")
- .render(name, rtype, private, fork_of, short_name=False,
- admin=False, _=_, h=h, c=c))
- last_change = lambda last_change: (template.get_def("last_change")
- .render(last_change, _=_, h=h, c=c))
- rss_lnk = lambda repo_name: (template.get_def("rss")
- .render(repo_name, _=_, h=h, c=c))
- atom_lnk = lambda repo_name: (template.get_def("atom")
- .render(repo_name, _=_, h=h, c=c))
-
- def desc(desc):
- if c.visual.stylify_metatags:
- return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
- else:
- return h.urlify_text(h.truncate(desc, 60))
-
- for repo in c.repos_list:
- repos_data.append({
- "menu": quick_menu(repo.repo_name),
- "raw_name": repo.repo_name.lower(),
- "name": repo_lnk(repo.repo_name, repo.repo_type,
- repo.private, repo.fork),
- "last_change": last_change(repo.last_db_change),
- "desc": desc(repo.description),
- "owner": h.person(repo.user.username),
- "rss": rss_lnk(repo.repo_name),
- "atom": atom_lnk(repo.repo_name),
- })
-
- c.data = json.dumps({
- "totalRecords": total_records,
- "startIndex": 0,
- "sort": "name",
- "dir": "asc",
- "records": repos_data
- })
+
+ repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
+ admin=False)
+ #json used to render the grid
+ c.data = json.dumps(repos_data)
return render('/index.html')
diff --git a/rhodecode/controllers/journal.py b/rhodecode/controllers/journal.py
index 9d499d19..5894cf73 100644
--- a/rhodecode/controllers/journal.py
+++ b/rhodecode/controllers/journal.py
@@ -27,6 +27,8 @@ from itertools import groupby
from sqlalchemy import or_
from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import func
+
from webhelpers.paginate import Page
from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
@@ -39,10 +41,10 @@ from rhodecode.lib.auth import LoginRequired, NotAnonymous
from rhodecode.lib.base import BaseController, render
from rhodecode.model.db import UserLog, UserFollowing, Repository, User
from rhodecode.model.meta import Session
-from sqlalchemy.sql.expression import func
-from rhodecode.model.scm import ScmModel
from rhodecode.lib.utils2 import safe_int, AttributeDict
from rhodecode.controllers.admin.admin import _journal_filter
+from rhodecode.model.repo import RepoModel
+from rhodecode.lib.compat import json
log = logging.getLogger(__name__)
@@ -78,18 +80,73 @@ class JournalController(BaseController):
c.journal_data = render('journal/journal_data.html')
if request.environ.get('HTTP_X_PARTIAL_XHR'):
return c.journal_data
- return render('journal/journal.html')
- @LoginRequired()
- @NotAnonymous()
- def index_my_repos(self):
- c.user = User.get(self.rhodecode_user.user_id)
- if request.environ.get('HTTP_X_PARTIAL_XHR'):
- all_repos = self.sa.query(Repository)\
- .filter(Repository.user_id == c.user.user_id)\
+ repos_list = Session().query(Repository)\
+ .filter(Repository.user_id ==
+ self.rhodecode_user.user_id)\
.order_by(func.lower(Repository.repo_name)).all()
- c.user_repos = ScmModel().get_repos(all_repos)
- return render('journal/journal_page_repos.html')
+
+ repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
+ admin=True)
+ #json used to render the grid
+ c.data = json.dumps(repos_data)
+
+ watched_repos_data = []
+
+ ## watched repos
+ _render = RepoModel._render_datatable
+
+ def quick_menu(repo_name):
+ return _render('quick_menu', repo_name)
+
+ def repo_lnk(name, rtype, private, fork_of):
+ return _render('repo_name', name, rtype, private, fork_of,
+ short_name=False, admin=False)
+
+ def last_rev(repo_name, cs_cache):
+ return _render('revision', repo_name, cs_cache.get('revision'),
+ cs_cache.get('raw_id'), cs_cache.get('author'),
+ cs_cache.get('message'))
+
+ def desc(desc):
+ from pylons import tmpl_context as c
+ if c.visual.stylify_metatags:
+ return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
+ else:
+ return h.urlify_text(h.truncate(desc, 60))
+
+ def repo_actions(repo_name):
+ return _render('repo_actions', repo_name)
+
+ def owner_actions(user_id, username):
+ return _render('user_name', user_id, username)
+
+ def toogle_follow(repo_id):
+ return _render('toggle_follow', repo_id)
+
+ for entry in c.following:
+ repo = entry.follows_repository
+ cs_cache = repo.changeset_cache
+ row = {
+ "menu": quick_menu(repo.repo_name),
+ "raw_name": repo.repo_name.lower(),
+ "name": repo_lnk(repo.repo_name, repo.repo_type,
+ repo.private, repo.fork),
+ "last_changeset": last_rev(repo.repo_name, cs_cache),
+ "raw_tip": cs_cache.get('revision'),
+ "action": toogle_follow(repo.repo_id)
+ }
+
+ watched_repos_data.append(row)
+
+ c.watched_data = json.dumps({
+ "totalRecords": len(c.following),
+ "startIndex": 0,
+ "sort": "name",
+ "dir": "asc",
+ "records": watched_repos_data
+ })
+ return render('journal/journal.html')
@LoginRequired(api_access=True)
@NotAnonymous()
diff --git a/rhodecode/controllers/login.py b/rhodecode/controllers/login.py
index 1e75bb49..da9c07f8 100644
--- a/rhodecode/controllers/login.py
+++ b/rhodecode/controllers/login.py
@@ -54,10 +54,9 @@ class LoginController(BaseController):
def index(self):
# redirect if already logged in
c.came_from = request.GET.get('came_from')
-
- if self.rhodecode_user.is_authenticated \
- and self.rhodecode_user.username != 'default':
-
+ not_default = self.rhodecode_user.username != 'default'
+ ip_allowed = self.rhodecode_user.ip_allowed
+ if self.rhodecode_user.is_authenticated and not_default and ip_allowed:
return redirect(url('home'))
if request.POST:
diff --git a/rhodecode/controllers/pullrequests.py b/rhodecode/controllers/pullrequests.py
index 1ed6192d..fa08b632 100644
--- a/rhodecode/controllers/pullrequests.py
+++ b/rhodecode/controllers/pullrequests.py
@@ -97,7 +97,7 @@ class PullrequestsController(BaseRepoController):
return repo.branches.keys()[0]
def _get_is_allowed_change_status(self, pull_request):
- owner = self.rhodecode_user.user_id == pull_request.user_id
+ owner = self.rhodecode_user.user_id == pull_request.user_id
reviewer = self.rhodecode_user.user_id in [x.user_id for x in
pull_request.reviewers]
return (self.rhodecode_user.admin or owner or reviewer)
@@ -299,7 +299,7 @@ class PullrequestsController(BaseRepoController):
else EmptyChangeset(), 'raw_id'))
c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
- c.target_repo = c.repo_name
+ c.target_repo = other_repo.repo_name
# defines that we need hidden inputs with changesets
c.as_form = request.GET.get('as_form', False)
@@ -339,7 +339,6 @@ class PullrequestsController(BaseRepoController):
c.users_array = repo_model.get_users_js()
c.users_groups_array = repo_model.get_users_groups_js()
c.pull_request = PullRequest.get_or_404(pull_request_id)
- c.target_repo = c.pull_request.org_repo.repo_name
c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
cc_model = ChangesetCommentsModel()
cs_model = ChangesetStatusModel()
@@ -478,7 +477,7 @@ class PullrequestsController(BaseRepoController):
#don't allow deleting comments on closed pull request
raise HTTPForbidden()
- owner = lambda: co.author.user_id == c.rhodecode_user.user_id
+ owner = co.author.user_id == c.rhodecode_user.user_id
if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
ChangesetCommentsModel().delete(comment=co)
Session().commit()
diff --git a/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.mo b/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.mo
index 2f94cd23..5372cec0 100644
--- a/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.mo
+++ b/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.mo
Binary files differ
diff --git a/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.po b/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.po
index 76d70978..312a7927 100644
--- a/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.po
+++ b/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.po
@@ -13,7 +13,7 @@ msgstr ""
"Project-Id-Version: RhodeCode 1.2.0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2012-12-14 04:19+0100\n"
-"PO-Revision-Date: 2012-10-27 15:06+0900\n"
+"PO-Revision-Date: 2013-01-02 01:39+0900\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: ja <LL@li.org>\n"
"Plural-Forms: nplurals=1; plural=0\n"
@@ -41,9 +41,9 @@ msgstr ""
#: rhodecode/controllers/changeset.py:314
#: rhodecode/controllers/pullrequests.py:417
-#, fuzzy, python-format
+#, python-format
msgid "Status change -> %s"
-msgstr ""
+msgstr "ステータス変更 -> %s"
#: rhodecode/controllers/changeset.py:345
msgid ""
@@ -71,7 +71,7 @@ msgstr "リソースにアクセスする権限がありません"
#: rhodecode/controllers/error.py:103
msgid "You don't have permission to view this page"
-msgstr "このページを見る権限がありません"
+msgstr "このページを閲覧する権限がありません"
#: rhodecode/controllers/error.py:105
msgid "The resource could not be found"
@@ -285,19 +285,17 @@ msgid "An error occurred during deletion of %s"
msgstr "リポジトリ %s の削除中にエラーが発生しました"
#: rhodecode/controllers/settings.py:185
-#, fuzzy
msgid "unlocked"
-msgstr "変更可能にする"
+msgstr "アンロック"
#: rhodecode/controllers/settings.py:188
-#, fuzzy
msgid "locked"
-msgstr "変更可能にする"
+msgstr "ロック"
#: rhodecode/controllers/settings.py:190
-#, fuzzy, python-format
+#, python-format
msgid "Repository has been %s"
-msgstr ""
+msgstr "リポジトリは %s されています"
#: rhodecode/controllers/settings.py:194
#: rhodecode/controllers/admin/repos.py:423
@@ -314,14 +312,12 @@ msgid "Statistics are disabled for this repository"
msgstr "このリポジトリの統計は無効化されています"
#: rhodecode/controllers/admin/defaults.py:96
-#, fuzzy
msgid "Default settings updated successfully"
-msgstr "LDAP設定を更新しました"
+msgstr "デフォルト設定を更新しました"
#: rhodecode/controllers/admin/defaults.py:110
-#, fuzzy
msgid "error occurred during update of defaults"
-msgstr "ユーザー %s の更新中にエラーが発生しました"
+msgstr "デフォルト設定の更新中にエラーが発生しました"
#: rhodecode/controllers/admin/ldap_settings.py:50
msgid "BASE"
@@ -473,7 +469,7 @@ msgstr "リポジトリ %s を作成中にエラーが発生しました"
#: rhodecode/controllers/admin/repos.py:320
#, python-format
msgid "Cannot delete %s it still contains attached forks"
-msgstr ""
+msgstr "フォークしたリポジトリが存在するため、 %s は削除できません"
#: rhodecode/controllers/admin/repos.py:349
msgid "An error occurred during deletion of repository user"
@@ -493,11 +489,11 @@ msgstr "キャッシュの無効化時にエラーが発生しました"
#: rhodecode/controllers/admin/repos.py:443
msgid "Updated repository visibility in public journal"
-msgstr ""
+msgstr "公開ジャーナルでのリポジトリの可視性を更新しました"
#: rhodecode/controllers/admin/repos.py:447
msgid "An error occurred during setting this repository in public journal"
-msgstr ""
+msgstr "このリポジトリの公開ジャーナルの設定中にエラーが発生しました"
#: rhodecode/controllers/admin/repos.py:452 rhodecode/model/validators.py:300
msgid "Token mismatch"
@@ -750,7 +746,7 @@ msgstr "バイナリファイル"
#: rhodecode/lib/diffs.py:90
msgid "Changeset was too big and was cut off, use diff menu to display this diff"
-msgstr ""
+msgstr "チェンジセットが大きすぎるため省略しました。差分を表示する場合は差分メニューを使用してください"
#: rhodecode/lib/diffs.py:100
msgid "No changes detected"
@@ -770,14 +766,14 @@ msgid "False"
msgstr "False"
#: rhodecode/lib/helpers.py:530
-#, fuzzy, python-format
+#, python-format
msgid "Deleted branch: %s"
-msgstr "リポジトリ %s を削除しました"
+msgstr "削除されたブランチ: %s"
#: rhodecode/lib/helpers.py:533
-#, fuzzy, python-format
+#, python-format
msgid "Created tag: %s"
-msgstr "ユーザー %s を作成しました"
+msgstr "作成したタグ: %s"
#: rhodecode/lib/helpers.py:546
msgid "Changeset not found"
@@ -794,21 +790,21 @@ msgstr "比較の表示"
#: rhodecode/lib/helpers.py:615
msgid "and"
-msgstr ""
+msgstr "と"
#: rhodecode/lib/helpers.py:616
#, python-format
msgid "%s more"
-msgstr ""
+msgstr "%s 以上"
#: rhodecode/lib/helpers.py:617 rhodecode/templates/changelog/changelog.html:51
msgid "revisions"
msgstr "リビジョン"
#: rhodecode/lib/helpers.py:641
-#, fuzzy, python-format
+#, python-format
msgid "fork name %s"
-msgstr ""
+msgstr "フォーク名 %s"
#: rhodecode/lib/helpers.py:658
#: rhodecode/templates/pullrequests/pullrequest_show.html:4
@@ -959,9 +955,9 @@ msgid "%s ago"
msgstr "%s 前"
#: rhodecode/lib/utils2.py:428
-#, fuzzy, python-format
+#, python-format
msgid "in %s and %s"
-msgstr "%s と %s 前"
+msgstr ""
#: rhodecode/lib/utils2.py:431
#, python-format
@@ -1084,39 +1080,39 @@ msgid "Enter %(min)i characters or more"
msgstr "%(min)i 文字以上必要です"
#: rhodecode/model/notification.py:220
-#, fuzzy, python-format
+#, python-format
msgid "commented on commit at %(when)s"
-msgstr ""
+msgstr "コミットにコメント %(when)s"
#: rhodecode/model/notification.py:221
#, python-format
msgid "sent message at %(when)s"
-msgstr ""
+msgstr "メッセージを送信 %(when)s"
#: rhodecode/model/notification.py:222
#, python-format
msgid "mentioned you at %(when)s"
-msgstr ""
+msgstr "Mention %(when)s"
#: rhodecode/model/notification.py:223
#, python-format
msgid "registered in RhodeCode at %(when)s"
-msgstr ""
+msgstr "RhodeCodeに登録 %(when)s"
#: rhodecode/model/notification.py:224
-#, fuzzy, python-format
+#, python-format
msgid "opened new pull request at %(when)s"
-msgstr ""
+msgstr "新しいプルリクエストを作成 %(when)s"
#: rhodecode/model/notification.py:225
-#, fuzzy, python-format
+#, python-format
msgid "commented on pull request at %(when)s"
-msgstr ""
+msgstr "プルリクエストにコメント %(when)s"
#: rhodecode/model/pull_request.py:90
#, python-format
msgid "%(user)s wants you to review pull request #%(pr_id)s"
-msgstr ""
+msgstr "%(user)s がプリリクエスト #%(pr_id)s のレビューを求めています"
#: rhodecode/model/scm.py:542
msgid "latest tip"
@@ -1129,11 +1125,11 @@ msgstr "新規ユーザー登録"
#: rhodecode/model/user.py:257 rhodecode/model/user.py:281
#: rhodecode/model/user.py:303
msgid "You can't Edit this user since it's crucial for entire application"
-msgstr ""
+msgstr "アプリケーション全体にとって重要なユーザなため、編集出来ません"
#: rhodecode/model/user.py:327
msgid "You can't remove this user since it's crucial for entire application"
-msgstr ""
+msgstr "アプリケーション全体にとって重要なユーザなため、削除できません"
#: rhodecode/model/user.py:333
#, python-format
@@ -1144,7 +1140,7 @@ msgstr ""
#: rhodecode/model/validators.py:36 rhodecode/model/validators.py:37
msgid "Value cannot be an empty list"
-msgstr ""
+msgstr "空のリストには出来ません"
#: rhodecode/model/validators.py:83
#, python-format
@@ -1244,16 +1240,15 @@ msgstr "無効なクローンURIです"
#: rhodecode/model/validators.py:433
msgid "Invalid clone url, provide a valid clone http(s)/svn+http(s) url"
-msgstr ""
+msgstr "無効なクローンURIです。有効な http(s)/svn+http(s) のURIを指定してください"
#: rhodecode/model/validators.py:458
msgid "Fork have to be the same type as parent"
msgstr "フォークは親と同じタイプの必要があります"
#: rhodecode/model/validators.py:473
-#, fuzzy
msgid "You don't have permissions to create repository in this group"
-msgstr "このページを見る権限がありません"
+msgstr "このグループでリポジトリを作成する権限がありません"
#: rhodecode/model/validators.py:498
msgid "This username or users group name is not valid"
@@ -1617,22 +1612,20 @@ msgid "Admin journal"
msgstr "管理者ジャーナル"
#: rhodecode/templates/admin/admin.html:10
-#, fuzzy
msgid "journal filter..."
-msgstr "クイックフィルタ..."
+msgstr "ジャーナルフィルタ..."
#: rhodecode/templates/admin/admin.html:12
#: rhodecode/templates/journal/journal.html:11
-#, fuzzy
msgid "filter"
-msgstr "ファイル"
+msgstr "フィルタ"
#: rhodecode/templates/admin/admin.html:13
#: rhodecode/templates/journal/journal.html:12
#, python-format
msgid "%s entry"
msgid_plural "%s entries"
-msgstr[0] ""
+msgstr[0] "%s エントリ"
#: rhodecode/templates/admin/admin_log.html:6
#: rhodecode/templates/admin/repos/repos.html:74
@@ -1668,14 +1661,12 @@ msgstr "まだアクションがありません"
#: rhodecode/templates/admin/defaults/defaults.html:5
#: rhodecode/templates/admin/defaults/defaults.html:25
-#, fuzzy
msgid "Repositories defaults"
-msgstr "リポジトリグループ"
+msgstr "リポジトリのデフォルト設定"
#: rhodecode/templates/admin/defaults/defaults.html:11
-#, fuzzy
msgid "Defaults"
-msgstr "default"
+msgstr "デフォルト設定"
#: rhodecode/templates/admin/defaults/defaults.html:35
#: rhodecode/templates/admin/repos/repo_add_base.html:38
@@ -1722,7 +1713,7 @@ msgstr "ロックを有効にする"
#: rhodecode/templates/admin/defaults/defaults.html:79
#: rhodecode/templates/admin/repos/repo_edit.html:116
msgid "Enable lock-by-pulling on repository."
-msgstr ""
+msgstr "リポジトリのpullのロックを有効にします"
#: rhodecode/templates/admin/defaults/defaults.html:84
#: rhodecode/templates/admin/ldap/ldap.html:89
@@ -2076,20 +2067,19 @@ msgstr "リポジトリのキャッシュを無効化してもよろしいです
msgid ""
"Manually invalidate cache for this repository. On first access repository"
" will be cached again"
-msgstr ""
+msgstr "このリポジトリのキャッシュを手動で無効化します。リポジトリへの初回アクセス時に再びキャッシュされます。"
#: rhodecode/templates/admin/repos/repo_edit.html:198
msgid "List of cached values"
-msgstr ""
+msgstr "キャッシュしている値の一覧"
#: rhodecode/templates/admin/repos/repo_edit.html:201
msgid "Prefix"
-msgstr ""
+msgstr "プレフィックス"
#: rhodecode/templates/admin/repos/repo_edit.html:202
-#, fuzzy
msgid "Key"
-msgstr "APIキー"
+msgstr "キー"
#: rhodecode/templates/admin/repos/repo_edit.html:203
#: rhodecode/templates/admin/users/user_add.html:86
@@ -2146,7 +2136,7 @@ msgstr "リポジトリはロックされていません"
#: rhodecode/templates/admin/repos/repo_edit.html:252
msgid "Force locking on repository. Works only when anonymous access is disabled"
-msgstr ""
+msgstr "リポジトリを強制ロックします。匿名アクセスが無効になっている場合のみ動作します。"
#: rhodecode/templates/admin/repos/repo_edit.html:259
msgid "Set as fork of"
@@ -2173,14 +2163,13 @@ msgstr "このリポジトリを削除しますか?"
#: rhodecode/templates/admin/repos/repo_edit.html:282
#: rhodecode/templates/settings/repo_settings.html:119
-#, fuzzy
msgid ""
"This repository will be renamed in a special way in order to be "
"unaccesible for RhodeCode and VCS systems. If you need fully delete it "
"from file system please do it manually"
msgstr ""
-"このリポジトリはRhodeCodeとVCSシステムからアクセスされないような名前に、特別な方法で変更されます。\n"
-"もし、ファイルシステムから完全に削除したい場合、手動で行ってください"
+"このリポジトリはRhodeCodeとVCSシステムからアクセス出来ないようにするために特別な方法でリネームされます。\n"
+"完全な削除が必要な場合はファイルシステムから手動で削除してください"
#: rhodecode/templates/admin/repos/repo_edit_perms.html:3
#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3
@@ -2250,7 +2239,7 @@ msgstr "リポジトリ管理"
#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:73
msgid "apply to children"
-msgstr ""
+msgstr "子リポジトリにも適用"
#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:74
msgid ""
@@ -2356,10 +2345,10 @@ msgid "delete"
msgstr "削除"
#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:55
-#, fuzzy, python-format
+#, 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[0] "このグループを削除してもよろしいですか?: %s %s リポジトリ"
#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:63
msgid "There are no repositories groups yet"
@@ -2394,11 +2383,11 @@ msgstr "フックの削除に失敗しました"
#: rhodecode/templates/admin/settings/settings.html:24
msgid "Remap and rescan repositories"
-msgstr ""
+msgstr "リポジトリの再マッピングと再スキャン"
#: rhodecode/templates/admin/settings/settings.html:32
msgid "rescan option"
-msgstr ""
+msgstr "再スキャンオプション"
#: rhodecode/templates/admin/settings/settings.html:38
msgid ""
@@ -2464,13 +2453,12 @@ msgid "Visualisation settings"
msgstr "表示の設定"
#: rhodecode/templates/admin/settings/settings.html:127
-#, fuzzy
msgid "General"
-msgstr "有効にする"
+msgstr "一般"
#: rhodecode/templates/admin/settings/settings.html:132
msgid "Use lightweight dashboard"
-msgstr ""
+msgstr "軽量ダッシュボードを使用"
#: rhodecode/templates/admin/settings/settings.html:139
msgid "Icons"
@@ -2508,7 +2496,7 @@ msgstr "VCSの操作にSSLを必須とする"
msgid ""
"RhodeCode will require SSL for pushing or pulling. If SSL is missing it "
"will return HTTP Error 406: Not Acceptable"
-msgstr ""
+msgstr "RhodeCodeはPushとPullにSSLを要求します。もしSSLでない場合、HTTP Error 406: Not Acceptalbeを返します"
#: rhodecode/templates/admin/settings/settings.html:209
msgid "Hooks"
@@ -2561,12 +2549,15 @@ msgid ""
"This a crucial application setting. If you are really sure you need to "
"change this, you must restart application in order to make this setting "
"take effect. Click this label to unlock."
-msgstr "これはアプリケーションの重要な設定です。本当に変更が必要でしょうか。もし、変更した場合、変更を反映さ競るためにアプリケーションを再起動する必要があります。変更可能にするにはこのラベルをクリックして下さい"
+msgstr ""
+"これはアプリケーションの重要な設定です。本当に変更が必要でしょうか。"
+"もし、変更した場合、変更を反映さ競るためにアプリケーションを再起動する必要があります。"
+"アンロックにするにはこのラベルをクリックして下さい"
#: rhodecode/templates/admin/settings/settings.html:262
#: rhodecode/templates/base/base.html:221
msgid "unlock"
-msgstr "変更可能にする"
+msgstr "アンロック"
#: rhodecode/templates/admin/settings/settings.html:263
msgid ""
@@ -2869,19 +2860,16 @@ msgid "Group members"
msgstr "グループメンバー"
#: rhodecode/templates/admin/users_groups/users_group_edit.html:163
-#, fuzzy
msgid "No members yet"
-msgstr "メンバー"
+msgstr "まだメンバーがいません"
#: rhodecode/templates/admin/users_groups/users_group_edit.html:171
-#, fuzzy
msgid "Permissions defined for this group"
-msgstr "権限管理"
+msgstr "このリポジトリの権限設定"
#: rhodecode/templates/admin/users_groups/users_group_edit.html:178
-#, fuzzy
msgid "No permissions set yet"
-msgstr "権限のコピー"
+msgstr "まだ権限設定がありません"
#: rhodecode/templates/admin/users_groups/users_groups.html:5
msgid "Users groups administration"
@@ -2992,9 +2980,8 @@ msgstr "オプション"
#: rhodecode/templates/base/base.html:204
#: rhodecode/templates/base/base.html:206
-#, fuzzy
msgid "repository settings"
-msgstr "リポジトリ作成"
+msgstr "リポジトリ設定"
#: rhodecode/templates/base/base.html:210
#: rhodecode/templates/data_table/_dt_elements.html:80
@@ -3017,9 +3004,8 @@ msgid "search"
msgstr "検索"
#: rhodecode/templates/base/base.html:223
-#, fuzzy
msgid "lock"
-msgstr "変更可能にする"
+msgstr "ロック"
#: rhodecode/templates/base/base.html:234
msgid "repositories groups"
@@ -3034,9 +3020,8 @@ msgid "permissions"
msgstr "権限"
#: rhodecode/templates/base/base.html:239
-#, fuzzy
msgid "defaults"
-msgstr "default"
+msgstr "デフォルト設定"
#: rhodecode/templates/base/base.html:240
msgid "settings"
@@ -3087,13 +3072,12 @@ msgid "no matching files"
msgstr "マッチするファイルはありません"
#: rhodecode/templates/base/root.html:51
-#, fuzzy
msgid "Open new pull request for selected changesets"
-msgstr "新しいプルリクエストを作成"
+msgstr "選択したチェンジセットから新しいプルリクエストを作成"
#: rhodecode/templates/base/root.html:52
msgid "Show selected changes __S -> __E"
-msgstr ""
+msgstr "選択した変更 __S -> __E を表示"
#: rhodecode/templates/base/root.html:53
msgid "Selection link"
@@ -3143,9 +3127,8 @@ msgid_plural "showing %d out of %d revisions"
msgstr[0] ""
#: rhodecode/templates/changelog/changelog.html:37
-#, fuzzy
msgid "Clear selection"
-msgstr "検索設定"
+msgstr "選択を解除"
#: rhodecode/templates/changelog/changelog.html:40
#: rhodecode/templates/forks/forks_data.html:19
@@ -3154,9 +3137,8 @@ msgid "compare fork with %s"
msgstr "%s とフォークを比較"
#: rhodecode/templates/changelog/changelog.html:40
-#, fuzzy
msgid "Compare fork with parent"
-msgstr "%s とフォークを比較"
+msgstr "フォークを比較"
#: rhodecode/templates/changelog/changelog.html:49
msgid "Show"
@@ -3259,7 +3241,7 @@ msgstr "チェンジセット"
#: rhodecode/templates/changeset/changeset.html:52
msgid "No children"
-msgstr ""
+msgstr "子リビジョンはありません"
#: rhodecode/templates/changeset/changeset.html:70
#: rhodecode/templates/changeset/diff_block.html:20
@@ -3267,9 +3249,8 @@ msgid "raw diff"
msgstr "差分を表示"
#: rhodecode/templates/changeset/changeset.html:71
-#, fuzzy
msgid "patch diff"
-msgstr "差分を表示"
+msgstr "パッチとして差分を表示"
#: rhodecode/templates/changeset/changeset.html:72
#: rhodecode/templates/changeset/diff_block.html:21
@@ -3293,18 +3274,18 @@ msgstr[0] "(%d インライン)"
#: rhodecode/templates/changeset/changeset.html:122
#: rhodecode/templates/compare/compare_diff.html:44
#: rhodecode/templates/pullrequests/pullrequest_show.html:76
-#, fuzzy, python-format
+#, python-format
msgid "%s file changed"
msgid_plural "%s files changed"
-msgstr[0] ""
+msgstr[0] "%s ファイルに影響"
#: rhodecode/templates/changeset/changeset.html:124
#: rhodecode/templates/compare/compare_diff.html:46
#: rhodecode/templates/pullrequests/pullrequest_show.html:78
-#, fuzzy, python-format
+#, 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] "%s ファイルに影響。 %s 個の追加と %s 個の削除:"
+msgstr[0] "%s ファイルに影響。 %s 個の追加と %s 個の削除"
#: rhodecode/templates/changeset/changeset_file_comment.html:42
msgid "Submitting..."
@@ -3349,7 +3330,7 @@ msgstr "コメントを残す"
#: rhodecode/templates/changeset/changeset_file_comment.html:125
msgid "Check this to change current status of code-review for this changeset"
-msgstr ""
+msgstr "チェックするとチェンジセットの現在のコードレビューステータスを変更出来ます"
#: rhodecode/templates/changeset/changeset_file_comment.html:125
msgid "change status"
@@ -3370,9 +3351,8 @@ msgid "Compare View"
msgstr "比較ビュー"
#: rhodecode/templates/changeset/changeset_range.html:29
-#, fuzzy
msgid "Show combined compare"
-msgstr "インラインコメントを表示"
+msgstr "結合した比較ビューを表示"
#: rhodecode/templates/changeset/changeset_range.html:54
msgid "Files affected"
@@ -3380,7 +3360,7 @@ msgstr "影響のあるファイル"
#: rhodecode/templates/changeset/diff_block.html:19
msgid "show full diff for this file"
-msgstr ""
+msgstr "このファイルの全差分を表示"
#: rhodecode/templates/changeset/diff_block.html:27
msgid "show inline comments"
@@ -3392,16 +3372,15 @@ msgstr "チェンジセットはありません"
#: rhodecode/templates/compare/compare_diff.html:37
#: rhodecode/templates/pullrequests/pullrequest_show.html:69
-#, fuzzy, python-format
+#, python-format
msgid "Showing %s commit"
msgid_plural "Showing %s commits"
-msgstr[0] ""
+msgstr[0] "%s コミットを表示"
#: rhodecode/templates/compare/compare_diff.html:52
#: rhodecode/templates/pullrequests/pullrequest_show.html:84
-#, fuzzy
msgid "No files"
-msgstr "ファイル"
+msgstr "ファイルはありません"
#: rhodecode/templates/data_table/_dt_elements.html:39
#: rhodecode/templates/data_table/_dt_elements.html:41
@@ -3455,40 +3434,37 @@ msgid "Confirm to delete this user: %s"
msgstr "このユーザーを本当に削除してよろしいですか?: %s"
#: rhodecode/templates/email_templates/changeset_comment.html:10
-#, fuzzy
msgid "New status$"
-msgstr "ステータスを変更する"
+msgstr "新しいステータス$"
#: rhodecode/templates/email_templates/main.html:8
-#, fuzzy
msgid "This is a notification from RhodeCode."
-msgstr "RhodeCodeからの通知があります"
+msgstr "RhodeCodeからの通知です"
#: rhodecode/templates/email_templates/password_reset.html:4
msgid "Hello"
-msgstr ""
+msgstr "こんにちは"
#: rhodecode/templates/email_templates/password_reset.html:6
msgid "We received a request to create a new password for your account."
-msgstr ""
+msgstr "あなたのアカウントの新しいパスワードの生成リクエストを受け取りました。"
#: rhodecode/templates/email_templates/password_reset.html:8
msgid "You can generate it by clicking following URL"
-msgstr ""
+msgstr "下のURLをクリックすることで再生成が行えます。"
#: rhodecode/templates/email_templates/password_reset.html:12
msgid "If you didn't request new password please ignore this email."
-msgstr ""
+msgstr "新しいパスワードのリクエストをしていない場合は、このメールを無視して下さい。"
#: rhodecode/templates/email_templates/pull_request.html:4
#, python-format
msgid ""
"User %s opened pull request for repository %s and wants you to review "
"changes."
-msgstr ""
+msgstr "ユーザ %s がリポジトリ %s で新しいプルリクエストを作成しました。変更をレビューしてください。"
#: rhodecode/templates/email_templates/pull_request.html:5
-#, fuzzy
msgid "title"
msgstr "タイトル"
@@ -3499,35 +3475,32 @@ msgstr "説明"
#: rhodecode/templates/email_templates/pull_request.html:11
msgid "revisions for reviewing"
-msgstr ""
+msgstr "レビュー対象のリビジョン"
#: rhodecode/templates/email_templates/pull_request.html:18
-#, fuzzy
msgid "View this pull request here"
-msgstr "このプルリクエストにレビュアーを追加"
+msgstr "このプルリクエストを閲覧する"
#: rhodecode/templates/email_templates/pull_request_comment.html:4
-#, fuzzy, python-format
+#, python-format
msgid "User %s commented on pull request #%s for repository %s"
-msgstr ""
+msgstr "ユーザ %s がプルリクエスト #%s (リポジトリ %s) にコメントしました。"
#: rhodecode/templates/email_templates/pull_request_comment.html:10
-#, fuzzy
msgid "New status"
-msgstr "ステータスを変更する"
+msgstr "新しいステータス"
#: rhodecode/templates/email_templates/pull_request_comment.html:14
msgid "View this comment here"
-msgstr ""
+msgstr "このコメントを閲覧する"
#: rhodecode/templates/email_templates/registration.html:4
-#, fuzzy
msgid "A new user have registered in RhodeCode"
-msgstr "rhodecodeへの登録を受け付けました"
+msgstr "新しいユーザがRhodeCodeへ登録しました"
#: rhodecode/templates/email_templates/registration.html:9
msgid "View this user here"
-msgstr ""
+msgstr "このユーザを閲覧する"
#: rhodecode/templates/errors/error_document.html:46
#, python-format
@@ -3609,7 +3582,7 @@ msgstr "変更をコミット"
#: rhodecode/templates/files/files_browser.html:13
msgid "view"
-msgstr "表示"
+msgstr "閲覧"
#: rhodecode/templates/files/files_browser.html:14
msgid "previous revision"
@@ -3697,9 +3670,8 @@ msgid "show at revision"
msgstr "このリビジョンを見る"
#: rhodecode/templates/files/files_history_box.html:11
-#, fuzzy
msgid "show full history"
-msgstr "ファイル一覧を読み込み中..."
+msgstr "すべての履歴を表示"
#: rhodecode/templates/files/files_history_box.html:16
#, python-format
@@ -3708,9 +3680,8 @@ msgid_plural "%s authors"
msgstr[0] "%s 作成者"
#: rhodecode/templates/files/files_source.html:6
-#, fuzzy
msgid "Load file history"
-msgstr "ファイル一覧を読み込み中..."
+msgstr "ファイルの履歴を読み込む"
#: rhodecode/templates/files/files_source.html:21
msgid "show source"
@@ -3909,7 +3880,7 @@ msgstr "%s にクローズ"
#: rhodecode/templates/pullrequests/pullrequest_show.html:23
#, python-format
msgid "with status %s"
-msgstr ""
+msgstr "ステータス: %s"
#: rhodecode/templates/pullrequests/pullrequest_show.html:31
msgid "Status"
@@ -3930,9 +3901,8 @@ msgid_plural "%d reviewers"
msgstr[0] "%d レビュアー"
#: rhodecode/templates/pullrequests/pullrequest_show.html:50
-#, fuzzy
msgid "pull request was reviewed by all reviewers"
-msgstr "プルリクエストレビュアー"
+msgstr "プルリクエストはすべてのレビュアーにレビューされました"
#: rhodecode/templates/pullrequests/pullrequest_show.html:58
msgid "Created on"
@@ -3943,9 +3913,8 @@ msgid "Compare view"
msgstr "比較ビュー"
#: rhodecode/templates/pullrequests/pullrequest_show.html:112
-#, fuzzy
msgid "reviewer"
-msgstr "%d レビュアー"
+msgstr "レビュアー"
#: rhodecode/templates/pullrequests/pullrequest_show_all.html:4
msgid "all pull requests"
@@ -4012,12 +3981,10 @@ msgid "%s Settings"
msgstr "%s 設定"
#: rhodecode/templates/settings/repo_settings.html:102
-#, fuzzy
msgid "Delete repository"
-msgstr "リポジトリを[削除]"
+msgstr "リポジトリを削除"
#: rhodecode/templates/settings/repo_settings.html:109
-#, fuzzy
msgid "Remove repo"
msgstr "削除"
@@ -4080,19 +4047,18 @@ msgid "ATOM"
msgstr "ATOM"
#: rhodecode/templates/summary/summary.html:70
-#, fuzzy, python-format
+#, python-format
msgid "Repository locked by %s"
-msgstr ""
+msgstr "リポジトリは %s によってロックされました"
#: rhodecode/templates/summary/summary.html:72
-#, fuzzy
msgid "Repository unlocked"
msgstr "リポジトリはロックされていません"
#: rhodecode/templates/summary/summary.html:91
#, python-format
msgid "Non changable ID %s"
-msgstr ""
+msgstr "変更不能ID %s"
#: rhodecode/templates/summary/summary.html:96
msgid "public"
@@ -4100,7 +4066,7 @@ msgstr "公開"
#: rhodecode/templates/summary/summary.html:104
msgid "remote clone"
-msgstr ""
+msgstr "リモートクローン"
#: rhodecode/templates/summary/summary.html:125
msgid "Contact"
@@ -4171,7 +4137,7 @@ msgstr "クイックスタート"
#: rhodecode/templates/summary/summary.html:243
#, python-format
msgid "Readme file at revision '%s'"
-msgstr ""
+msgstr "リビジョン '%s' のReadmeファイル"
#: rhodecode/templates/summary/summary.html:246
msgid "Permalink to this readme"
@@ -4220,9 +4186,8 @@ msgid "%s Tags"
msgstr "%s タグ"
#: rhodecode/templates/tags/tags.html:29
-#, fuzzy
msgid "Compare tags"
-msgstr "比較"
+msgstr "タグの比較"
#~ msgid ""
#~ "%s repository is not mapped to db"
diff --git a/rhodecode/i18n/pl/LC_MESSAGES/rhodecode.po b/rhodecode/i18n/pl/LC_MESSAGES/rhodecode.po
index 370519b8..dc1136ec 100644
--- a/rhodecode/i18n/pl/LC_MESSAGES/rhodecode.po
+++ b/rhodecode/i18n/pl/LC_MESSAGES/rhodecode.po
@@ -3,20 +3,21 @@
# This file is distributed under the same license as the rhodecode project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
# Nemcio <bogdan114@g.pl>, 2012.
-# Nemo <areczek01@gmail.com>, 2012.
+# Nemo <areczek01@gmail.com>, 2012, 2013.
msgid ""
msgstr ""
"Project-Id-Version: rhodecode 0.1\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2012-12-14 04:19+0100\n"
-"PO-Revision-Date: 2012-11-25 03:42+0200\n"
-"Last-Translator: Nemo <areczek01@gmail.com>\n"
+"PO-Revision-Date: 2013-01-18 18:12+0100\n"
+"Last-Translator: Nemcio <bogdan114@g.pl>\n"
"Language-Team: Test\n"
-"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && "
-"(n%100<10 || n%100>=20) ? 1 : 2)\n"
+"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: Virtaal 0.7.1\n"
"Generated-By: Babel 0.9.6\n"
#: rhodecode/controllers/changelog.py:95
@@ -27,7 +28,8 @@ msgstr "Wszystkie gałęzie"
msgid "show white space"
msgstr "pokazuj spacje"
-#: rhodecode/controllers/changeset.py:90 rhodecode/controllers/changeset.py:97
+#: rhodecode/controllers/changeset.py:90
+#: rhodecode/controllers/changeset.py:97
msgid "ignore white space"
msgstr "ignoruj pokazywanie spacji"
@@ -43,12 +45,8 @@ msgid "Status change -> %s"
msgstr "Zmiana statusu -> %s"
#: rhodecode/controllers/changeset.py:345
-msgid ""
-"Changing status on a changeset associated witha closed pull request is "
-"not allowed"
-msgstr ""
-"Zmiana statusu na grupy zmian powiązania łączy zamkniętego wniosku jest "
-"niedozwolona"
+msgid "Changing status on a changeset associated witha closed pull request is not allowed"
+msgstr "Zmiana statusu na grupy zmian powiązania łączy zamkniętego wniosku jest niedozwolona"
#: rhodecode/controllers/compare.py:75
#: rhodecode/controllers/pullrequests.py:121
@@ -62,9 +60,7 @@ msgstr "Strona główna"
#: rhodecode/controllers/error.py:98
msgid "The request could not be understood by the server due to malformed syntax."
-msgstr ""
-"Wniosek nie może być rozumiany przez serwer z powodu zniekształconej "
-"składni."
+msgstr "Wniosek nie może być rozumiany przez serwer z powodu zniekształconej składni."
#: rhodecode/controllers/error.py:101
msgid "Unauthorized access to resource"
@@ -79,12 +75,8 @@ msgid "The resource could not be found"
msgstr "Zasób nie został znaleziony"
#: rhodecode/controllers/error.py:107
-msgid ""
-"The server encountered an unexpected condition which prevented it from "
-"fulfilling the request."
-msgstr ""
-"Serwer napotkał niespodziewany warunek, który uniemożliwia jej spełnienie"
-" żądania."
+msgid "The server encountered an unexpected condition which prevented it from fulfilling the request."
+msgstr "Serwer napotkał niespodziewany warunek, który uniemożliwia jej spełnienie żądania."
#: rhodecode/controllers/feed.py:52
#, python-format
@@ -119,7 +111,8 @@ msgstr "Kliknij tutaj, by dodać nowy plik"
msgid "There are no files yet %s"
msgstr "Brak plików %s"
-#: rhodecode/controllers/files.py:265 rhodecode/controllers/files.py:325
+#: rhodecode/controllers/files.py:265
+#: rhodecode/controllers/files.py:325
#, python-format
msgid "This repository is has been locked by %s on %s"
msgstr "Repozytorium zostało zablokowane przez %s na %s"
@@ -133,12 +126,14 @@ msgstr "Edytowanie %s w RhodeCode"
msgid "No changes"
msgstr "Bez zmian"
-#: rhodecode/controllers/files.py:308 rhodecode/controllers/files.py:372
+#: rhodecode/controllers/files.py:308
+#: rhodecode/controllers/files.py:372
#, python-format
msgid "Successfully committed to %s"
msgstr "Committ wykonany do %s"
-#: rhodecode/controllers/files.py:313 rhodecode/controllers/files.py:378
+#: rhodecode/controllers/files.py:313
+#: rhodecode/controllers/files.py:378
msgid "Error occurred during commit"
msgstr "Wystąpił błąd w trakcie zatwierdzania"
@@ -178,13 +173,17 @@ msgstr "Nieznany typ archiwum"
msgid "Changesets"
msgstr "Różnice"
-#: rhodecode/controllers/files.py:565 rhodecode/controllers/pullrequests.py:74
-#: rhodecode/controllers/summary.py:236 rhodecode/model/scm.py:550
+#: rhodecode/controllers/files.py:565
+#: rhodecode/controllers/pullrequests.py:74
+#: rhodecode/controllers/summary.py:236
+#: rhodecode/model/scm.py:550
msgid "Branches"
msgstr "Gałęzie"
-#: rhodecode/controllers/files.py:566 rhodecode/controllers/pullrequests.py:78
-#: rhodecode/controllers/summary.py:237 rhodecode/model/scm.py:561
+#: rhodecode/controllers/files.py:566
+#: rhodecode/controllers/pullrequests.py:78
+#: rhodecode/controllers/summary.py:237
+#: rhodecode/model/scm.py:561
msgid "Tags"
msgstr "Etykiety"
@@ -198,11 +197,13 @@ msgstr "gałęzi %s w repozytorium %s"
msgid "An error occurred during repository forking %s"
msgstr "Wystąpił błąd podczas rozgałęzienia %s repozytorium"
-#: rhodecode/controllers/journal.py:218 rhodecode/controllers/journal.py:261
+#: rhodecode/controllers/journal.py:218
+#: rhodecode/controllers/journal.py:261
msgid "public journal"
msgstr "Dziennik publiczny"
-#: rhodecode/controllers/journal.py:222 rhodecode/controllers/journal.py:265
+#: rhodecode/controllers/journal.py:222
+#: rhodecode/controllers/journal.py:265
#: rhodecode/templates/base/base.html:232
#: rhodecode/templates/journal/journal.html:12
msgid "journal"
@@ -217,12 +218,11 @@ msgid "Your password reset link was sent"
msgstr "Twój link zresetowania hasła został wysłany"
#: rhodecode/controllers/login.py:184
-msgid ""
-"Your password reset was successful, new password has been sent to your "
-"email"
+msgid "Your password reset was successful, new password has been sent to your email"
msgstr "Twoje hasło zostało zresetowane, nowe hasło zostanie wysłane na e-mail"
-#: rhodecode/controllers/pullrequests.py:76 rhodecode/model/scm.py:556
+#: rhodecode/controllers/pullrequests.py:76
+#: rhodecode/model/scm.py:556
msgid "Bookmarks"
msgstr "Zakładki"
@@ -247,8 +247,9 @@ msgid "Successfully deleted pull request"
msgstr "Prośba o skasowanie połączenia gałęzi została wykonana prawidłowo"
#: rhodecode/controllers/pullrequests.py:452
+#, fuzzy
msgid "Closing pull request on other statuses than rejected or approved forbidden"
-msgstr ""
+msgstr "Zamknij wszystkie wnioski połączenia gałęzi innych stanów niż odrzucony, zatwierdzony lub zabroniony"
#: rhodecode/controllers/search.py:134
msgid "Invalid search query. Try quoting it."
@@ -315,14 +316,12 @@ msgid "Statistics are disabled for this repository"
msgstr "Statystyki są wyłączone dla tego repozytorium"
#: rhodecode/controllers/admin/defaults.py:96
-#, fuzzy
msgid "Default settings updated successfully"
-msgstr "Ustawienia LDAP zostały zaktualizowane"
+msgstr "Domyślne ustawienia zostały pomyślnie zaktualizowane"
#: rhodecode/controllers/admin/defaults.py:110
-#, fuzzy
msgid "error occurred during update of defaults"
-msgstr "wystąpił błąd podczas aktualizacji użytkownika %s"
+msgstr "wystąpił błąd podczas aktualizacji wartości domyślnych"
#: rhodecode/controllers/admin/ldap_settings.py:50
msgid "BASE"
@@ -500,7 +499,8 @@ msgstr "Zaktualizowano widoczność stron w publicznym dzienniku"
msgid "An error occurred during setting this repository in public journal"
msgstr "Wystąpił błąd podczas ustawiania tego repozytorium w dzienniku publicznym"
-#: rhodecode/controllers/admin/repos.py:452 rhodecode/model/validators.py:300
+#: rhodecode/controllers/admin/repos.py:452
+#: rhodecode/model/validators.py:300
msgid "Token mismatch"
msgstr "Niezgodność tokenu"
@@ -576,9 +576,7 @@ msgstr "Wystąpił błąd podczas usuwania grup i grup użytkowników"
#: rhodecode/controllers/admin/settings.py:123
#, python-format
msgid "Repositories successfully rescanned added: %s,removed: %s"
-msgstr ""
-"Repozytoria z powodzeniem zostały ponownie zeskanowane dodano: %s, "
-"usunięto: %s"
+msgstr "Repozytoria z powodzeniem zostały ponownie zeskanowane dodano: %s, usunięto: %s"
#: rhodecode/controllers/admin/settings.py:131
msgid "Whoosh reindex task scheduled"
@@ -623,9 +621,7 @@ msgstr "E-mail został wysłany"
#: rhodecode/controllers/admin/settings.py:399
msgid "You can't edit this user since it's crucial for entire application"
-msgstr ""
-"Nie możesz edytować tego użytkownika ponieważ jest kluczowy dla całej "
-"aplikacji"
+msgstr "Nie możesz edytować tego użytkownika ponieważ jest kluczowy dla całej aplikacji"
#: rhodecode/controllers/admin/settings.py:430
msgid "Your account was updated successfully"
@@ -755,9 +751,7 @@ msgstr "plik binarny"
#: rhodecode/lib/diffs.py:90
msgid "Changeset was too big and was cut off, use diff menu to display this diff"
-msgstr ""
-"Lista zmian była zbyt duża i została obcięta, użyj menu porównań żeby "
-"wyświetlić różnice"
+msgstr "Lista zmian była zbyt duża i została obcięta, użyj menu porównań żeby wyświetlić różnice"
#: rhodecode/lib/diffs.py:100
msgid "No changes detected"
@@ -808,7 +802,8 @@ msgstr "i"
msgid "%s more"
msgstr "%s więcej"
-#: rhodecode/lib/helpers.py:617 rhodecode/templates/changelog/changelog.html:51
+#: rhodecode/lib/helpers.py:617
+#: rhodecode/templates/changelog/changelog.html:51
msgid "revisions"
msgstr "rewizja"
@@ -828,7 +823,8 @@ msgstr "Połączonych gałęzi #%s"
msgid "[deleted] repository"
msgstr "[usunięte] repozytorium"
-#: rhodecode/lib/helpers.py:666 rhodecode/lib/helpers.py:676
+#: rhodecode/lib/helpers.py:666
+#: rhodecode/lib/helpers.py:676
msgid "[created] repository"
msgstr "[utworzone] repozytorium"
@@ -836,11 +832,13 @@ msgstr "[utworzone] repozytorium"
msgid "[created] repository as fork"
msgstr "[utworzone] repozytorium jako rozgałęzienie"
-#: rhodecode/lib/helpers.py:670 rhodecode/lib/helpers.py:678
+#: rhodecode/lib/helpers.py:670
+#: rhodecode/lib/helpers.py:678
msgid "[forked] repository"
msgstr "[rozgałęzione] repozytorium"
-#: rhodecode/lib/helpers.py:672 rhodecode/lib/helpers.py:680
+#: rhodecode/lib/helpers.py:672
+#: rhodecode/lib/helpers.py:680
msgid "[updated] repository"
msgstr "[zaktualizowane] repozytorium"
@@ -911,14 +909,8 @@ msgstr "Brak Plików"
#: rhodecode/lib/helpers.py:1163
#, 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 ""
-"%s repozytorium nie jest mapowane do db może zostało utworzone lub "
-"zmienione z systemie plików proszę uruchomić aplikację ponownie, aby "
-"ponownie przeskanować repozytoria"
+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 "%s repozytorium nie jest mapowane do db może zostało utworzone lub zmienione z systemie plików proszę uruchomić aplikację ponownie, aby ponownie przeskanować repozytoria"
#: rhodecode/lib/utils2.py:403
#, python-format
@@ -996,83 +988,103 @@ msgstr "przed chwilą"
msgid "password reset link"
msgstr "łącze resetowania hasła"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1163 rhodecode/model/db.py:1183
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1163
+#: rhodecode/model/db.py:1183
msgid "Repository no access"
msgstr "Brak dostępu do repozytorium"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1164 rhodecode/model/db.py:1184
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1164
+#: rhodecode/model/db.py:1184
msgid "Repository read access"
msgstr "Repozytorium do odczytu"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1165 rhodecode/model/db.py:1185
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1165
+#: rhodecode/model/db.py:1185
msgid "Repository write access"
msgstr "Repozytorium do zapisu"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1166 rhodecode/model/db.py:1186
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1166
+#: rhodecode/model/db.py:1186
msgid "Repository admin access"
msgstr "Administracja dostępu do repozytorium"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1168 rhodecode/model/db.py:1188
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1168
+#: rhodecode/model/db.py:1188
msgid "Repositories Group no access"
msgstr "Grupy repozytoriów brak dostępu"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1169 rhodecode/model/db.py:1189
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1169
+#: rhodecode/model/db.py:1189
msgid "Repositories Group read access"
msgstr "Grupy repozytoriów dostęp do odczytu"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1170 rhodecode/model/db.py:1190
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1170
+#: rhodecode/model/db.py:1190
msgid "Repositories Group write access"
msgstr "Grupy repozytoriów dostęp do zapisu"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1171 rhodecode/model/db.py:1191
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1171
+#: rhodecode/model/db.py:1191
msgid "Repositories Group admin access"
msgstr "Repozytoria Grupy dostęp administratora"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1173 rhodecode/model/db.py:1193
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1173
+#: rhodecode/model/db.py:1193
msgid "RhodeCode Administrator"
msgstr "Administrator Repo"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1174 rhodecode/model/db.py:1194
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1174
+#: rhodecode/model/db.py:1194
msgid "Repository creation disabled"
msgstr "Repozytorium wyłączone"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1175 rhodecode/model/db.py:1195
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1175
+#: rhodecode/model/db.py:1195
msgid "Repository creation enabled"
msgstr "Repozytorium włączone"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1176 rhodecode/model/db.py:1196
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1176
+#: rhodecode/model/db.py:1196
msgid "Repository forking disabled"
msgstr "Rozwidlenie repozytorium wyłączone"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1177 rhodecode/model/db.py:1197
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1177
+#: rhodecode/model/db.py:1197
msgid "Repository forking enabled"
msgstr "Rozwidlenie repozytorium włączone"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1178 rhodecode/model/db.py:1198
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1178
+#: rhodecode/model/db.py:1198
msgid "Register disabled"
msgstr "Rejestracja wyłączona"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1179 rhodecode/model/db.py:1199
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1179
+#: rhodecode/model/db.py:1199
msgid "Register new user with RhodeCode with manual activation"
msgstr "Rejestracja nowego użytkownika na stronie z ręczną aktywacją"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1182 rhodecode/model/db.py:1202
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1182
+#: rhodecode/model/db.py:1202
msgid "Register new user with RhodeCode with auto activation"
msgstr "Rejestracja nowego użytkownika na stronie z automatyczną aktywacją"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1623 rhodecode/model/db.py:1643
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1623
+#: rhodecode/model/db.py:1643
msgid "Not Reviewed"
msgstr "Brak Korekty"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1624 rhodecode/model/db.py:1644
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1624
+#: rhodecode/model/db.py:1644
msgid "Approved"
msgstr "Zaakceptowano"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1625 rhodecode/model/db.py:1645
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1625
+#: rhodecode/model/db.py:1645
msgid "Rejected"
msgstr "Odrzucono"
-#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1626 rhodecode/model/db.py:1646
+#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1626
+#: rhodecode/model/db.py:1646
msgid "Under Review"
msgstr "Objęty Przeglądem"
@@ -1146,29 +1158,23 @@ msgstr "ostatni tip"
msgid "new user registration"
msgstr "nowy użytkownik się zarejestrował"
-#: rhodecode/model/user.py:257 rhodecode/model/user.py:281
+#: rhodecode/model/user.py:257
+#: rhodecode/model/user.py:281
#: rhodecode/model/user.py:303
msgid "You can't Edit this user since it's crucial for entire application"
-msgstr ""
-"Nie możesz edytować tego użytkownika ponieważ jest kluczowy dla całej "
-"aplikacji"
+msgstr "Nie możesz edytować tego użytkownika ponieważ jest kluczowy dla całej aplikacji"
#: rhodecode/model/user.py:327
msgid "You can't remove this user since it's crucial for entire application"
-msgstr ""
-"Nie możesz usunąć tego użytkownika ponieważ jest kluczowy dla całej "
-"aplikacji"
+msgstr "Nie możesz usunąć tego użytkownika ponieważ jest kluczowy dla całej aplikacji"
#: rhodecode/model/user.py:333
#, python-format
-msgid ""
-"user \"%s\" still owns %s repositories and cannot be removed. Switch "
-"owners or remove those repositories. %s"
-msgstr ""
-"użytkownik \"%s\" wciąż posiada repozytoria następujące %s i nie może "
-"zostać usunięty. Zmień właściciela lub usuń te repozytoria. %s"
+msgid "user \"%s\" still owns %s repositories and cannot be removed. Switch owners or remove those repositories. %s"
+msgstr "użytkownik \"%s\" wciąż posiada repozytoria następujące %s i nie może zostać usunięty. Zmień właściciela lub usuń te repozytoria. %s"
-#: rhodecode/model/validators.py:36 rhodecode/model/validators.py:37
+#: rhodecode/model/validators.py:36
+#: rhodecode/model/validators.py:37
msgid "Value cannot be an empty list"
msgstr "Wartość listy nie może być pusta"
@@ -1183,12 +1189,8 @@ msgid "Username \"%(username)s\" is forbidden"
msgstr "Nazwa użytkownika \"%(username)s\" jest zabroniona"
#: rhodecode/model/validators.py:87
-msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with alphanumeric character"
-msgstr ""
-"Nazwa użytkownika może zawierać tylko znaki alfanumeryczne, podkreślenia,"
-" kropki lub myślniki i musi zaczynać się znakiem alfanumerycznym"
+msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character"
+msgstr "Nazwa użytkownika może zawierać tylko znaki alfanumeryczne, podkreślenia, kropki lub myślniki i musi zaczynać się znakiem alfanumerycznym"
#: rhodecode/model/validators.py:115
#, python-format
@@ -1205,12 +1207,8 @@ msgid "Users group \"%(usersgroup)s\" already exists"
msgstr "Nazwa grupy \"%(usersgroup)s\" już istnieje"
#: rhodecode/model/validators.py:137
-msgid ""
-"users group name may only contain alphanumeric characters underscores, "
-"periods or dashes and must begin with alphanumeric character"
-msgstr ""
-"Nazwa grupy może zawierać tylko znaki alfanumeryczne, podkreślenia, "
-"kropki lub myślniki i musi zaczynać się znakiem alfanumerycznym"
+msgid "users group name may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character"
+msgstr "Nazwa grupy może zawierać tylko znaki alfanumeryczne, podkreślenia, kropki lub myślniki i musi zaczynać się znakiem alfanumerycznym"
#: rhodecode/model/validators.py:175
msgid "Cannot assign this group as parent"
@@ -1300,12 +1298,8 @@ msgid "e-mail \"%(email)s\" does not exist."
msgstr "e-mail \"%(email)s\" nie istnieje."
#: rhodecode/model/validators.py:663
-msgid ""
-"The LDAP Login attribute of the CN must be specified - this is the name "
-"of the attribute that is equivalent to \"username\""
-msgstr ""
-"Atrybut logowania CN do LDAP należy określić, jest to nazwa atrybutu, "
-"który jest odpowiednikiem \"username\""
+msgid "The LDAP Login attribute of the CN must be specified - this is the name of the attribute that is equivalent to \"username\""
+msgstr "Atrybut logowania CN do LDAP należy określić, jest to nazwa atrybutu, który jest odpowiednikiem \"username\""
#: rhodecode/model/validators.py:682
#, python-format
@@ -1498,7 +1492,8 @@ msgstr "Błąd danych."
msgid "Loading..."
msgstr "Wczytywanie..."
-#: rhodecode/templates/login.html:5 rhodecode/templates/login.html:54
+#: rhodecode/templates/login.html:5
+#: rhodecode/templates/login.html:54
msgid "Sign In"
msgstr "Zaloguj się"
@@ -1506,7 +1501,8 @@ msgstr "Zaloguj się"
msgid "Sign In to"
msgstr "Zarejestruj się"
-#: rhodecode/templates/login.html:31 rhodecode/templates/register.html:20
+#: rhodecode/templates/login.html:31
+#: rhodecode/templates/register.html:20
#: rhodecode/templates/admin/admin_log.html:5
#: rhodecode/templates/admin/users/user_add.html:32
#: rhodecode/templates/admin/users/user_edit.html:50
@@ -1516,7 +1512,8 @@ msgstr "Zarejestruj się"
msgid "Username"
msgstr "Nazwa użytkownika"
-#: rhodecode/templates/login.html:40 rhodecode/templates/register.html:29
+#: rhodecode/templates/login.html:40
+#: rhodecode/templates/register.html:29
#: rhodecode/templates/admin/ldap/ldap.html:46
#: rhodecode/templates/admin/users/user_add.html:41
#: rhodecode/templates/base/base.html:92
@@ -1531,7 +1528,8 @@ msgstr "Zapamiętaj mnie"
msgid "Forgot your password ?"
msgstr "Zapomniałeś hasła?"
-#: rhodecode/templates/login.html:63 rhodecode/templates/base/base.html:103
+#: rhodecode/templates/login.html:63
+#: rhodecode/templates/base/base.html:103
msgid "Don't have an account ?"
msgstr "Nie masz konta?"
@@ -1555,7 +1553,8 @@ msgstr "Zresetuj swoje hasło"
msgid "Password reset link will be send to matching email address"
msgstr "Link do zresetowania hasła zostanie wysłany na adres e-mail"
-#: rhodecode/templates/register.html:5 rhodecode/templates/register.html:74
+#: rhodecode/templates/register.html:5
+#: rhodecode/templates/register.html:74
msgid "Sign Up"
msgstr "Zarejestruj się"
@@ -1646,24 +1645,22 @@ msgid "Admin journal"
msgstr "Dziennik administratora"
#: rhodecode/templates/admin/admin.html:10
-#, fuzzy
msgid "journal filter..."
-msgstr "szybki filtr..."
+msgstr "szybkie wyszukiwanie..."
#: rhodecode/templates/admin/admin.html:12
#: rhodecode/templates/journal/journal.html:11
-#, fuzzy
msgid "filter"
-msgstr "pliki"
+msgstr "filtr"
#: rhodecode/templates/admin/admin.html:13
#: rhodecode/templates/journal/journal.html:12
#, python-format
msgid "%s entry"
msgid_plural "%s entries"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+msgstr[0] "%s wejście"
+msgstr[1] "%s wejść"
+msgstr[2] "%s wejść"
#: rhodecode/templates/admin/admin_log.html:6
#: rhodecode/templates/admin/repos/repos.html:74
@@ -1699,14 +1696,12 @@ msgstr "Brak akcji"
#: rhodecode/templates/admin/defaults/defaults.html:5
#: rhodecode/templates/admin/defaults/defaults.html:25
-#, fuzzy
msgid "Repositories defaults"
-msgstr "grupy w repozytorium"
+msgstr "Repozytoria domyślne"
#: rhodecode/templates/admin/defaults/defaults.html:11
-#, fuzzy
msgid "Defaults"
-msgstr "domyślne"
+msgstr "Domyślne"
#: rhodecode/templates/admin/defaults/defaults.html:35
#: rhodecode/templates/admin/repos/repo_add_base.html:38
@@ -1719,12 +1714,8 @@ msgstr "Typ"
#: rhodecode/templates/admin/repos/repo_edit.html:89
#: rhodecode/templates/forks/fork.html:72
#: rhodecode/templates/settings/repo_settings.html:80
-msgid ""
-"Private repositories are only visible to people explicitly added as "
-"collaborators."
-msgstr ""
-"Prywatne repozytoria są widoczne tylko dla osób bezpośrednio dodanych "
-"jako współpracownicy."
+msgid "Private repositories are only visible to people explicitly added as collaborators."
+msgstr "Prywatne repozytoria są widoczne tylko dla osób bezpośrednio dodanych jako współpracownicy."
#: rhodecode/templates/admin/defaults/defaults.html:55
#: rhodecode/templates/admin/repos/repo_edit.html:94
@@ -1900,14 +1891,8 @@ msgid "Anonymous access"
msgstr "Dostęp anonimowy"
#: rhodecode/templates/admin/permissions/permissions.html:49
-msgid ""
-"All default permissions on each repository will be reset to choosen "
-"permission, note that all custom default permission on repositories will "
-"be lost"
-msgstr ""
-"Wszystkie uprawnienia domyślne każdego repozytorium zostaną przywrócone. "
-"Wybrane uprawnienie zostaną skasowane. Pamiętaj, że wszystkie "
-"niestandardowe uprawnienia w repozytoriach zostaną utracone."
+msgid "All default permissions on each repository will be reset to choosen permission, note that all custom default permission on repositories will be lost"
+msgstr "Wszystkie uprawnienia domyślne każdego repozytorium zostaną przywrócone. Wybrane uprawnienie zostaną skasowane. Pamiętaj, że wszystkie niestandardowe uprawnienia w repozytoriach zostaną utracone."
#: rhodecode/templates/admin/permissions/permissions.html:50
#: rhodecode/templates/admin/permissions/permissions.html:63
@@ -1924,14 +1909,8 @@ msgid "Repository group"
msgstr "Repozytorium grupy"
#: rhodecode/templates/admin/permissions/permissions.html:62
-msgid ""
-"All default permissions on each repository group will be reset to choosen"
-" permission, note that all custom default permission on repositories "
-"group will be lost"
-msgstr ""
-"Wszystkie uprawnienia domyślne każdego repozytorium zostaną przywrócone. "
-"Wybrane uprawnienie zostaną skasowane. Pamiętaj, że wszystkie "
-"niestandardowe uprawnienia w repozytoriach zostaną utracone."
+msgid "All default permissions on each repository group will be reset to choosen permission, note that all custom default permission on repositories group will be lost"
+msgstr "Wszystkie uprawnienia domyślne każdego repozytorium zostaną przywrócone. Wybrane uprawnienie zostaną skasowane. Pamiętaj, że wszystkie niestandardowe uprawnienia w repozytoriach zostaną utracone."
#: rhodecode/templates/admin/permissions/permissions.html:69
msgid "Registration"
@@ -2112,12 +2091,8 @@ msgid "Confirm to invalidate repository cache"
msgstr "Potwierdź unieważnienie pamięci podręcznej repozytorium"
#: rhodecode/templates/admin/repos/repo_edit.html:193
-msgid ""
-"Manually invalidate cache for this repository. On first access repository"
-" will be cached again"
-msgstr ""
-"Ręcznie unieważnienie cache dla tego repozytorium. Przy pierwszym "
-"dostępie do repozytorium zostanie dodany do bufora ponownie"
+msgid "Manually invalidate cache for this repository. On first access repository will be cached again"
+msgstr "Ręcznie unieważnienie cache dla tego repozytorium. Przy pierwszym dostępie do repozytorium zostanie dodany do bufora ponownie"
#: rhodecode/templates/admin/repos/repo_edit.html:198
msgid "List of cached values"
@@ -2125,12 +2100,11 @@ msgstr "Lista buforowanych wartości"
#: rhodecode/templates/admin/repos/repo_edit.html:201
msgid "Prefix"
-msgstr ""
+msgstr "Prefiks"
#: rhodecode/templates/admin/repos/repo_edit.html:202
-#, fuzzy
msgid "Key"
-msgstr "Klucz API"
+msgstr "Klucz"
#: rhodecode/templates/admin/repos/repo_edit.html:203
#: rhodecode/templates/admin/users/user_add.html:86
@@ -2156,12 +2130,8 @@ msgid "Add to public journal"
msgstr "Dodaj do dziennika publicznego"
#: rhodecode/templates/admin/repos/repo_edit.html:231
-msgid ""
-"All actions made on this repository will be accessible to everyone in "
-"public journal"
-msgstr ""
-"Wszystkie działania wykonywane na tym repozytorium będą dostępne dla "
-"wszystkich w dzienniku publicznym"
+msgid "All actions made on this repository will be accessible to everyone in public journal"
+msgstr "Wszystkie działania wykonywane na tym repozytorium będą dostępne dla wszystkich w dzienniku publicznym"
#: rhodecode/templates/admin/repos/repo_edit.html:238
msgid "Locking"
@@ -2189,9 +2159,7 @@ msgstr "Repozytorium nie jest zablokowane"
#: rhodecode/templates/admin/repos/repo_edit.html:252
msgid "Force locking on repository. Works only when anonymous access is disabled"
-msgstr ""
-"Wymuś blokowanie na repozytorium. Działa tylko wtedy, gdy dostęp "
-"anonimowy jest wyłączony"
+msgstr "Wymuś blokowanie na repozytorium. Działa tylko wtedy, gdy dostęp anonimowy jest wyłączony"
#: rhodecode/templates/admin/repos/repo_edit.html:259
msgid "Set as fork of"
@@ -2218,15 +2186,8 @@ msgstr "Potwierdź, aby usunąć repozytorium"
#: rhodecode/templates/admin/repos/repo_edit.html:282
#: rhodecode/templates/settings/repo_settings.html:119
-#, fuzzy
-msgid ""
-"This repository will be renamed in a special way in order to be "
-"unaccesible for RhodeCode and VCS systems. If you need fully delete it "
-"from file system please do it manually"
-msgstr ""
-"To repozytorium zostanie zmienione w sposób szczególny, żeby było "
-"niedostępne dla strony i systemów VCS. Jeśli chcesz całkowicie usunąć go "
-"z systemu plików prosimy zrobić to ręcznie"
+msgid "This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need fully delete it from file system please do it manually"
+msgstr "To repozytorium zostanie zmienione w sposób szczególny, żeby było niedostępne dla strony i systemów VCS. Jeśli chcesz całkowicie usunąć go z systemu plików prosimy zrobić to ręcznie"
#: rhodecode/templates/admin/repos/repo_edit_perms.html:3
#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3
@@ -2295,14 +2256,14 @@ msgid "Repositories administration"
msgstr "Administracja repozytoriami"
#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:73
+#, fuzzy
msgid "apply to children"
-msgstr ""
+msgstr "dotyczy dzieci"
#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:74
-msgid ""
-"Set or revoke permission to all children of that group, including "
-"repositories and other groups"
-msgstr ""
+#, fuzzy
+msgid "Set or revoke permission to all children of that group, including repositories and other groups"
+msgstr "Ustawia lub cofa uprawnienia do wszystkich dzieci z tej grupy, w tym repozytoria oraz innych grup"
#: rhodecode/templates/admin/repos_groups/repos_groups.html:9
#: rhodecode/templates/base/base.html:122
@@ -2320,7 +2281,8 @@ msgstr ""
#: rhodecode/templates/files/files_add.html:15
#: rhodecode/templates/files/files_edit.html:15
#: rhodecode/templates/followers/followers.html:9
-#: rhodecode/templates/forks/fork.html:9 rhodecode/templates/forks/forks.html:9
+#: rhodecode/templates/forks/fork.html:9
+#: rhodecode/templates/forks/forks.html:9
#: rhodecode/templates/pullrequests/pullrequest.html:8
#: rhodecode/templates/pullrequests/pullrequest_show.html:8
#: rhodecode/templates/pullrequests/pullrequest_show_all.html:8
@@ -2370,12 +2332,8 @@ msgid "edit repos group"
msgstr "edytuj grupy repo"
#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:70
-msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
-msgstr ""
-"Włącz blokowanie pulling przez grupy. Opcja ta będzie stosowana do "
-"wszystkich innych grup i repozytoriów wewnątrz"
+msgid "Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside"
+msgstr "Włącz blokowanie pulling przez grupy. Opcja ta będzie stosowana do wszystkich innych grup i repozytoriów wewnątrz"
#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5
msgid "Repositories groups administration"
@@ -2404,12 +2362,12 @@ msgid "delete"
msgstr "usuń"
#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:55
-#, fuzzy, python-format
+#, 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] "Potwierdz aby usunąć grupę %s wraz z %s repozytorium"
-msgstr[1] "Potwierdz aby usunąć grupę %s wraz z %s repozytoriami"
-msgstr[2] "Potwierdz aby usunąć grupę %s wraz z %s repozytoriami"
+msgstr[0] "Potwierdź żeby usunąć grupę %s wraz z %s repozytorium"
+msgstr[1] "Potwierdź żeby usunąć grupę %s wraz z %s repozytoriami"
+msgstr[2] "Potwierdź żeby usunąć grupę %s wraz z %s repozytoriami"
#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:63
msgid "There are no repositories groups yet"
@@ -2451,26 +2409,16 @@ msgid "rescan option"
msgstr "ponowne skanowanie opcji"
#: rhodecode/templates/admin/settings/settings.html:38
-msgid ""
-"In case a repository was deleted from filesystem and there are leftovers "
-"in the database check this option to scan obsolete data in database and "
-"remove it."
-msgstr ""
-"W przypadku repozytoriów zostaną usunięte systemy plików i jeśli są "
-"pozostałości w bazie danych to ta opcja sprawdzi ją oraz przeskanuje, a "
-"następnie usunie je z bazy danych."
+msgid "In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it."
+msgstr "W przypadku repozytoriów zostaną usunięte systemy plików i jeśli są pozostałości w bazie danych to ta opcja sprawdzi ją oraz przeskanuje, a następnie usunie je z bazy danych."
#: rhodecode/templates/admin/settings/settings.html:39
msgid "destroy old data"
msgstr "zniszcz stare dane"
#: rhodecode/templates/admin/settings/settings.html:41
-msgid ""
-"Rescan repositories location for new repositories. Also deletes obsolete "
-"if `destroy` flag is checked "
-msgstr ""
-"Skanowanie ponowne lokalizacji dla nowych repozytoriów. Usuwa również "
-"nieaktualne jeśli została zaznaczona flaga `zniszcz` do sprawdzana"
+msgid "Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked "
+msgstr "Skanowanie ponowne lokalizacji dla nowych repozytoriów. Usuwa również nieaktualne jeśli została zaznaczona flaga `zniszcz` do sprawdzana"
#: rhodecode/templates/admin/settings/settings.html:46
msgid "Rescan repositories"
@@ -2519,9 +2467,8 @@ msgid "Visualisation settings"
msgstr "Ustawienia wizualizacji"
#: rhodecode/templates/admin/settings/settings.html:127
-#, fuzzy
msgid "General"
-msgstr "włącz"
+msgstr "Główne"
#: rhodecode/templates/admin/settings/settings.html:132
msgid "Use lightweight dashboard"
@@ -2560,12 +2507,8 @@ msgid "require ssl for vcs operations"
msgstr "wymagaj ssl dla operacji vcs"
#: rhodecode/templates/admin/settings/settings.html:203
-msgid ""
-"RhodeCode will require SSL for pushing or pulling. If SSL is missing it "
-"will return HTTP Error 406: Not Acceptable"
-msgstr ""
-"RhodeCode wymaga SSL do wysłania zmian lub pobierania. Jeśli brakuje SSL "
-"zwróci błąd HTTP 406: Not Acceptable"
+msgid "RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable"
+msgstr "RhodeCode wymaga SSL do wysłania zmian lub pobierania. Jeśli brakuje SSL zwróci błąd HTTP 406: Not Acceptable"
#: rhodecode/templates/admin/settings/settings.html:209
msgid "Hooks"
@@ -2604,26 +2547,16 @@ msgid "hgsubversion extensions"
msgstr "rozszerzenia hgsubversion"
#: rhodecode/templates/admin/settings/settings.html:246
-msgid ""
-"Requires hgsubversion library installed. Allows clonning from svn remote "
-"locations"
-msgstr ""
-"Wymaga biblioteki hgsubversion zainstalowanej. Umożliwia klonowanie z "
-"zdalnych lokalizacji svn"
+msgid "Requires hgsubversion library installed. Allows clonning from svn remote locations"
+msgstr "Wymaga biblioteki hgsubversion zainstalowanej. Umożliwia klonowanie z zdalnych lokalizacji svn"
#: rhodecode/templates/admin/settings/settings.html:256
msgid "Repositories location"
msgstr "Położenie repozytorium"
#: rhodecode/templates/admin/settings/settings.html:261
-msgid ""
-"This a crucial application setting. If you are really sure you need to "
-"change this, you must restart application in order to make this setting "
-"take effect. Click this label to unlock."
-msgstr ""
-"To kluczowe ustawienia aplikacji. Jeśli jesteś pewny, że chcesz to "
-"zmienić, należy ponownie uruchomić aplikację w celu zaktualizowania "
-"lokalizacji. Kliknij tą etykietę, żeby odblokować."
+msgid "This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock."
+msgstr "To kluczowe ustawienia aplikacji. Jeśli jesteś pewny, że chcesz to zmienić, należy ponownie uruchomić aplikację w celu zaktualizowania lokalizacji. Kliknij tą etykietę, żeby odblokować."
#: rhodecode/templates/admin/settings/settings.html:262
#: rhodecode/templates/base/base.html:221
@@ -2631,12 +2564,8 @@ msgid "unlock"
msgstr "odblokowany"
#: rhodecode/templates/admin/settings/settings.html:263
-msgid ""
-"Location where repositories are stored. After changing this value a "
-"restart, and rescan is required"
-msgstr ""
-"Miejsce, w którym przechowywane są repozytoria. Po zmianie tej wartości "
-"jest wymagany restart i ponowne skanowanie"
+msgid "Location where repositories are stored. After changing this value a restart, and rescan is required"
+msgstr "Miejsce, w którym przechowywane są repozytoria. Po zmianie tej wartości jest wymagany restart i ponowne skanowanie"
#: rhodecode/templates/admin/settings/settings.html:283
msgid "Test Email"
@@ -2716,12 +2645,8 @@ msgstr "Dziedziczą uprawnienia domyślne"
#: rhodecode/templates/admin/users/user_edit.html:156
#: rhodecode/templates/admin/users_groups/users_group_edit.html:113
#, python-format
-msgid ""
-"Select to inherit permissions from %s settings. With this selected below "
-"options does not have any action"
-msgstr ""
-"Zaznacz, żeby dziedziczyć uprawnienia z %s ustawień. Po wybraniu tej "
-"opcji, poniżej nie ma żadnych działań"
+msgid "Select to inherit permissions from %s settings. With this selected below options does not have any action"
+msgstr "Zaznacz, żeby dziedziczyć uprawnienia z %s ustawień. Po wybraniu tej opcji, poniżej nie ma żadnych działań"
#: rhodecode/templates/admin/users/user_edit.html:162
#: rhodecode/templates/admin/users_groups/users_group_edit.html:119
@@ -3010,7 +2935,8 @@ msgid "Products"
msgstr "Produkty"
#: rhodecode/templates/base/base.html:152
-#: rhodecode/templates/base/base.html:182 rhodecode/templates/base/root.html:47
+#: rhodecode/templates/base/base.html:182
+#: rhodecode/templates/base/root.html:47
msgid "loading..."
msgstr "wczytywanie..."
@@ -3064,7 +2990,8 @@ msgstr "ustawienia repozytorium"
msgid "fork"
msgstr "gałąż"
-#: rhodecode/templates/base/base.html:212 rhodecode/templates/base/root.html:50
+#: rhodecode/templates/base/base.html:212
+#: rhodecode/templates/base/root.html:50
#: rhodecode/templates/changelog/changelog.html:43
msgid "Open new pull request"
msgstr "Otwórz nową prośbę o połączenie gałęzi"
@@ -3095,7 +3022,6 @@ msgid "permissions"
msgstr "uprawnienia"
#: rhodecode/templates/base/base.html:239
-#, fuzzy
msgid "defaults"
msgstr "domyślne"
@@ -3215,9 +3141,8 @@ msgid "compare fork with %s"
msgstr "porównaj gałęzie %s"
#: rhodecode/templates/changelog/changelog.html:40
-#, fuzzy
msgid "Compare fork with parent"
-msgstr "porównaj fork w rodzicem"
+msgstr "porównaj gałąź w rodzicem"
#: rhodecode/templates/changelog/changelog.html:49
msgid "Show"
@@ -3319,8 +3244,9 @@ msgid "Changeset"
msgstr "Grupy zmian"
#: rhodecode/templates/changeset/changeset.html:52
+#, fuzzy
msgid "No children"
-msgstr ""
+msgstr "Brak dzieci"
#: rhodecode/templates/changeset/changeset.html:70
#: rhodecode/templates/changeset/diff_block.html:20
@@ -3357,22 +3283,22 @@ msgstr[2] "(%d linii)"
#: rhodecode/templates/changeset/changeset.html:122
#: rhodecode/templates/compare/compare_diff.html:44
#: rhodecode/templates/pullrequests/pullrequest_show.html:76
-#, fuzzy, python-format
+#, python-format
msgid "%s file changed"
msgid_plural "%s files changed"
-msgstr[0] "%s plik zmieniony"
-msgstr[1] "%s plików zmienionych"
-msgstr[2] "%s plików zmienionych"
+msgstr[0] "%s plik został zmieniony"
+msgstr[1] "%s pliki zostały zmienione"
+msgstr[2] "%s plików zostało zmienionych"
#: rhodecode/templates/changeset/changeset.html:124
#: rhodecode/templates/compare/compare_diff.html:46
#: rhodecode/templates/pullrequests/pullrequest_show.html:78
-#, fuzzy, python-format
+#, 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] "%s plik zmieniony z %s inserjcami i %s usunieciami"
-msgstr[1] "%s plików zmienionych z %s inserjcami i %s usunieciami"
-msgstr[2] "%s plików zmienionych z %s inserjcami i %s usunieciami"
+msgstr[0] "%s plik został zmieniony z %s inercjami i %s usunięciami"
+msgstr[1] "%s plików zostało zmienionych z %s inercjami i %s usunięciami"
+msgstr[2] "%s plików zostało zmienionych z %s inercjami i %s usunięciami"
#: rhodecode/templates/changeset/changeset_file_comment.html:42
msgid "Submitting..."
@@ -3391,9 +3317,7 @@ msgstr "Komentarze analizowane za pomocą %s składni od %s wsparcia."
#: rhodecode/templates/changeset/changeset_file_comment.html:48
#: rhodecode/templates/changeset/changeset_file_comment.html:123
msgid "Use @username inside this text to send notification to this RhodeCode user"
-msgstr ""
-"Użyj @username wewnątrz tego tekstu, aby wysłać powiadomienie do "
-"użytkownika strony"
+msgstr "Użyj @username wewnątrz tego tekstu, aby wysłać powiadomienie do użytkownika strony"
#: rhodecode/templates/changeset/changeset_file_comment.html:59
#: rhodecode/templates/changeset/changeset_file_comment.html:143
@@ -3440,9 +3364,8 @@ msgid "Compare View"
msgstr "Wyświetl Porównanie"
#: rhodecode/templates/changeset/changeset_range.html:29
-#, fuzzy
msgid "Show combined compare"
-msgstr "pokaż online komentarz"
+msgstr "Pokaż połączone porównaj"
#: rhodecode/templates/changeset/changeset_range.html:54
msgid "Files affected"
@@ -3462,18 +3385,17 @@ msgstr "Brak zestawienia zmian"
#: rhodecode/templates/compare/compare_diff.html:37
#: rhodecode/templates/pullrequests/pullrequest_show.html:69
-#, fuzzy, python-format
+#, python-format
msgid "Showing %s commit"
msgid_plural "Showing %s commits"
-msgstr[0] "Wyswietlane %s commit"
-msgstr[1] "Wyswietlane %s commits"
-msgstr[2] "Wyswietlane %s commits"
+msgstr[0] "Pokaż %s komentarz"
+msgstr[1] "Pokaż %s komentarze"
+msgstr[2] "Pokaż %s komentarze"
#: rhodecode/templates/compare/compare_diff.html:52
#: rhodecode/templates/pullrequests/pullrequest_show.html:84
-#, fuzzy
msgid "No files"
-msgstr "pliki"
+msgstr "Brak plików"
#: rhodecode/templates/data_table/_dt_elements.html:39
#: rhodecode/templates/data_table/_dt_elements.html:41
@@ -3527,42 +3449,37 @@ msgid "Confirm to delete this user: %s"
msgstr "Potwierdź usunięcie tego użytkownika: %s"
#: rhodecode/templates/email_templates/changeset_comment.html:10
-#, fuzzy
msgid "New status$"
-msgstr "zmień status"
+msgstr "Nowy status$"
#: rhodecode/templates/email_templates/main.html:8
-#, fuzzy
msgid "This is a notification from RhodeCode."
msgstr "To jest powiadomienie z strony"
#: rhodecode/templates/email_templates/password_reset.html:4
msgid "Hello"
-msgstr ""
+msgstr "Witaj"
#: rhodecode/templates/email_templates/password_reset.html:6
msgid "We received a request to create a new password for your account."
-msgstr ""
+msgstr "Otrzymaliśmy prośbę o utworzenie nowego hasła do twojego konta."
#: rhodecode/templates/email_templates/password_reset.html:8
msgid "You can generate it by clicking following URL"
-msgstr ""
+msgstr "Możesz wygenerować nowe hasło klikając w link URL poniżej:"
#: rhodecode/templates/email_templates/password_reset.html:12
msgid "If you didn't request new password please ignore this email."
-msgstr ""
+msgstr "Jeśli nie chcesz wygenerować nowego hasła to zignoruj tą wiadomość."
#: rhodecode/templates/email_templates/pull_request.html:4
#, python-format
-msgid ""
-"User %s opened pull request for repository %s and wants you to review "
-"changes."
-msgstr ""
+msgid "User %s opened pull request for repository %s and wants you to review changes."
+msgstr "Użytkownik %s zgłosił wniosek połączenia w repozytorium %s i chce żeby sprawdzić zmiany."
#: rhodecode/templates/email_templates/pull_request.html:5
-#, fuzzy
msgid "title"
-msgstr "Tytuł"
+msgstr "tytuł"
#: rhodecode/templates/email_templates/pull_request.html:6
#: rhodecode/templates/pullrequests/pullrequest.html:115
@@ -3571,37 +3488,32 @@ msgstr "opis"
#: rhodecode/templates/email_templates/pull_request.html:11
msgid "revisions for reviewing"
-msgstr ""
+msgstr "korekty dotyczące rewizji"
#: rhodecode/templates/email_templates/pull_request.html:18
-#, fuzzy
msgid "View this pull request here"
-msgstr "Pokarz wszystkie zmiany"
+msgstr "Wyświetl prośby pobrania tutaj"
#: rhodecode/templates/email_templates/pull_request_comment.html:4
-#, fuzzy, python-format
+#, python-format
msgid "User %s commented on pull request #%s for repository %s"
-msgstr ""
-"Użytkownik %s skomentował wniosek o połączenie gałęzi #%s dla "
-"repozytorium %s"
+msgstr "Użytkownik %s skomentował wniosek o połączenie gałęzi #%s dla repozytorium %s"
#: rhodecode/templates/email_templates/pull_request_comment.html:10
-#, fuzzy
msgid "New status"
-msgstr "zmień status"
+msgstr "Nowy status"
#: rhodecode/templates/email_templates/pull_request_comment.html:14
msgid "View this comment here"
-msgstr ""
+msgstr "Zobacz ten komentarz tutaj"
#: rhodecode/templates/email_templates/registration.html:4
-#, fuzzy
msgid "A new user have registered in RhodeCode"
-msgstr "Udało Ci się zarejestrować na stronie"
+msgstr "Nowy użytkownik został zarejestrowany na stronie"
#: rhodecode/templates/email_templates/registration.html:9
msgid "View this user here"
-msgstr ""
+msgstr "Zobacz tego użytkownika tutaj"
#: rhodecode/templates/errors/error_document.html:46
#, python-format
@@ -3771,9 +3683,8 @@ msgid "show at revision"
msgstr "wskaż zmiany"
#: rhodecode/templates/files/files_history_box.html:11
-#, fuzzy
msgid "show full history"
-msgstr "Wczytywanie listy plików..."
+msgstr "pokaż pełną historię"
#: rhodecode/templates/files/files_history_box.html:16
#, python-format
@@ -3784,9 +3695,8 @@ msgstr[1] "%s autorzy"
msgstr[2] "%s autorzy"
#: rhodecode/templates/files/files_source.html:6
-#, fuzzy
msgid "Load file history"
-msgstr "Wczytywanie listy plików..."
+msgstr "Załaduj historię pliku"
#: rhodecode/templates/files/files_source.html:21
msgid "show source"
@@ -4020,9 +3930,8 @@ msgid "Compare view"
msgstr "Wyświetl porównanie"
#: rhodecode/templates/pullrequests/pullrequest_show.html:112
-#, fuzzy
msgid "reviewer"
-msgstr "%d recenzent"
+msgstr "recenzent"
#: rhodecode/templates/pullrequests/pullrequest_show_all.html:4
msgid "all pull requests"
@@ -4089,14 +3998,12 @@ msgid "%s Settings"
msgstr "Ustawienia %s"
#: rhodecode/templates/settings/repo_settings.html:102
-#, fuzzy
msgid "Delete repository"
-msgstr "[skasowane] repozytorium"
+msgstr "Usuń repozytorium"
#: rhodecode/templates/settings/repo_settings.html:109
-#, fuzzy
msgid "Remove repo"
-msgstr "usuń"
+msgstr "Usuń repo"
#: rhodecode/templates/shortlog/shortlog.html:5
#, python-format
@@ -4296,9 +4203,8 @@ msgid "%s Tags"
msgstr "Etykiety pliku %s"
#: rhodecode/templates/tags/tags.html:29
-#, fuzzy
msgid "Compare tags"
-msgstr "porównanie"
+msgstr "Porównaj tagi"
#~ msgid ""
#~ "%s repository is not mapped to db"
@@ -4325,4 +4231,3 @@ msgstr "porównanie"
#~ "zmienione w systemie plików proszę "
#~ "uruchomić aplikację ponownie, aby ponownie "
#~ "przeskanować repozytoria"
-
diff --git a/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.mo b/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.mo
index 872afff1..11819a8a 100644
--- a/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.mo
+++ b/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.mo
Binary files differ
diff --git a/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.po b/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.po
index 0c533502..350b47ec 100644
--- a/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.po
+++ b/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.po
@@ -8,15 +8,16 @@ msgid ""
msgstr ""
"Project-Id-Version: RhodeCode 1.4.4\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2012-12-14 04:19+0100\n"
-"PO-Revision-Date: 2012-11-26 15:19+0800\n"
+"POT-Creation-Date: 2012-12-14 12:53+0800\n"
+"PO-Revision-Date: 2012-12-14 12:57+0800\n"
"Last-Translator: xpol <xpolife@gmail.com>\n"
"Language-Team: mikespook\n"
-"Plural-Forms: nplurals=1; plural=0\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
+"X-Generator: Poedit 1.5.4\n"
#: rhodecode/controllers/changelog.py:95
msgid "All Branches"
@@ -43,8 +44,8 @@ msgstr "状态修改为%s"
#: rhodecode/controllers/changeset.py:345
msgid ""
-"Changing status on a changeset associated witha closed pull request is "
-"not allowed"
+"Changing status on a changeset associated witha closed pull request is not "
+"allowed"
msgstr "不允许修改已关闭拉取请求的修订集状态"
#: rhodecode/controllers/compare.py:75
@@ -58,7 +59,8 @@ msgid "Home page"
msgstr "主页"
#: rhodecode/controllers/error.py:98
-msgid "The request could not be understood by the server due to malformed syntax."
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
msgstr "由于错误的语法,服务器无法对请求进行响应。"
#: rhodecode/controllers/error.py:101
@@ -211,8 +213,7 @@ msgstr "密码重置链接已经发送"
#: rhodecode/controllers/login.py:184
msgid ""
-"Your password reset was successful, new password has been sent to your "
-"email"
+"Your password reset was successful, new password has been sent to your email"
msgstr "密码已经成功重置,新密码已经发送到你的邮箱"
#: rhodecode/controllers/pullrequests.py:76 rhodecode/model/scm.py:556
@@ -240,8 +241,9 @@ msgid "Successfully deleted pull request"
msgstr "成功删除拉取请求"
#: rhodecode/controllers/pullrequests.py:452
-msgid "Closing pull request on other statuses than rejected or approved forbidden"
-msgstr ""
+msgid ""
+"Closing pull request on other statuses than rejected or approved forbidden"
+msgstr "只能以批准或者驳回的状态关闭拉取请求"
#: rhodecode/controllers/search.py:134
msgid "Invalid search query. Try quoting it."
@@ -308,14 +310,12 @@ msgid "Statistics are disabled for this repository"
msgstr "该版本库统计功能已经禁用"
#: rhodecode/controllers/admin/defaults.py:96
-#, fuzzy
msgid "Default settings updated successfully"
-msgstr "LDAP设置已经成功更新"
+msgstr "默认设置已经成功更新"
#: rhodecode/controllers/admin/defaults.py:110
-#, fuzzy
msgid "error occurred during update of defaults"
-msgstr "更新用户%s时发生错误"
+msgstr "更新默认设置时发生错误"
#: rhodecode/controllers/admin/ldap_settings.py:50
msgid "BASE"
@@ -654,11 +654,11 @@ msgstr "无法编辑该用户"
#: rhodecode/controllers/admin/users.py:272
msgid "Granted 'repository create' permission to user"
-msgstr "已授予用户‘创建版本库’的权限"
+msgstr "已授予用户“创建版本库”的权限"
#: rhodecode/controllers/admin/users.py:277
msgid "Revoked 'repository create' permission to user"
-msgstr "已撤销用户‘创建版本库’的权限"
+msgstr "已撤销用户“创建版本库”的权限"
#: rhodecode/controllers/admin/users.py:283
msgid "Granted 'repository fork' permission to user"
@@ -716,19 +716,19 @@ msgstr "删除用户组时发生错误"
#: rhodecode/controllers/admin/users_groups.py:257
msgid "Granted 'repository create' permission to users group"
-msgstr "已授予用户组‘创建版本库’的权限"
+msgstr "已授予用户组“创建版本库”的权限"
#: rhodecode/controllers/admin/users_groups.py:262
msgid "Revoked 'repository create' permission to users group"
-msgstr "已撤销用户组‘创建版本库’的权限"
+msgstr "已撤销用户组“创建版本库”的权限"
#: rhodecode/controllers/admin/users_groups.py:268
msgid "Granted 'repository fork' permission to users group"
-msgstr "已授予用户组‘复刻版本库’的权限"
+msgstr "已授予用户组“复刻版本库”的权限"
#: rhodecode/controllers/admin/users_groups.py:273
msgid "Revoked 'repository fork' permission to users group"
-msgstr "已撤销用户组‘复刻版本库’的权限"
+msgstr "已撤销用户组“复刻版本库”的权限"
#: rhodecode/lib/auth.py:499
msgid "You need to be a registered user to perform this action"
@@ -743,7 +743,8 @@ msgid "binary file"
msgstr "二进制文件"
#: rhodecode/lib/diffs.py:90
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
msgstr "修订集因过大而被截断,可查看原始修订集作为替代"
#: rhodecode/lib/diffs.py:100
@@ -795,7 +796,8 @@ msgstr "还有"
msgid "%s more"
msgstr "%s个"
-#: rhodecode/lib/helpers.py:617 rhodecode/templates/changelog/changelog.html:51
+#: rhodecode/lib/helpers.py:617
+#: rhodecode/templates/changelog/changelog.html:51
msgid "revisions"
msgstr "修订"
@@ -899,10 +901,11 @@ msgstr "没有文件"
#: rhodecode/lib/helpers.py:1163
#, 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 "版本库%s没有映射到数据库,可能是从文件系统创建或者重命名,请重启RhodeCode以重新扫描版本库"
+"%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 ""
+"版本库%s没有映射到数据库,可能是从文件系统创建或者重命名,请重启RhodeCode以重"
+"新扫描版本库"
#: rhodecode/lib/utils2.py:403
#, python-format
@@ -1130,9 +1133,10 @@ msgstr "由于是系统帐号,无法删除该用户"
#: rhodecode/model/user.py:333
#, python-format
msgid ""
-"user \"%s\" still owns %s repositories and cannot be removed. Switch "
-"owners or remove those repositories. %s"
-msgstr "由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本库。%s"
+"user \"%s\" still owns %s repositories and cannot be removed. Switch owners "
+"or remove those repositories. %s"
+msgstr ""
+"由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本库。%s"
#: rhodecode/model/validators.py:36 rhodecode/model/validators.py:37
msgid "Value cannot be an empty list"
@@ -1150,9 +1154,10 @@ msgstr "不允许用户名 \"%(username)s\""
#: rhodecode/model/validators.py:87
msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with alphanumeric character"
-msgstr "只能使用字母、数字、下划线、小数点或减号作为用户名,且必须由数字或字母开头"
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with alphanumeric character"
+msgstr ""
+"只能使用字母、数字、下划线、小数点或减号作为用户名,且必须由数字或字母开头"
#: rhodecode/model/validators.py:115
#, python-format
@@ -1172,7 +1177,8 @@ msgstr "用户组 \"%(usersgroup)s\" 已经存在"
msgid ""
"users group name may only contain alphanumeric characters underscores, "
"periods or dashes and must begin with alphanumeric character"
-msgstr "只能使用字母、数字、下划线、小数点或减号作为用户组名,且必须由数字或字母开头"
+msgstr ""
+"只能使用字母、数字、下划线、小数点或减号作为用户组名,且必须由数字或字母开头"
#: rhodecode/model/validators.py:175
msgid "Cannot assign this group as parent"
@@ -1263,8 +1269,8 @@ msgstr "邮件地址\"%(email)s\"不存在"
#: rhodecode/model/validators.py:663
msgid ""
-"The LDAP Login attribute of the CN must be specified - this is the name "
-"of the attribute that is equivalent to \"username\""
+"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 必须指定 - 这个名字作为用户名"
#: rhodecode/model/validators.py:682
@@ -1606,22 +1612,20 @@ msgid "Admin journal"
msgstr "系统日志"
#: rhodecode/templates/admin/admin.html:10
-#, fuzzy
msgid "journal filter..."
-msgstr "快速过滤..."
+msgstr "日志过滤..."
#: rhodecode/templates/admin/admin.html:12
#: rhodecode/templates/journal/journal.html:11
-#, fuzzy
msgid "filter"
-msgstr "文件"
+msgstr "过滤"
#: rhodecode/templates/admin/admin.html:13
#: rhodecode/templates/journal/journal.html:12
#, python-format
msgid "%s entry"
msgid_plural "%s entries"
-msgstr[0] ""
+msgstr[0] "%s条"
#: rhodecode/templates/admin/admin_log.html:6
#: rhodecode/templates/admin/repos/repos.html:74
@@ -1657,14 +1661,12 @@ msgstr "无操作"
#: rhodecode/templates/admin/defaults/defaults.html:5
#: rhodecode/templates/admin/defaults/defaults.html:25
-#, fuzzy
msgid "Repositories defaults"
-msgstr "版本库组"
+msgstr "版本库默认设置"
#: rhodecode/templates/admin/defaults/defaults.html:11
-#, fuzzy
msgid "Defaults"
-msgstr "默认"
+msgstr "默认设置"
#: rhodecode/templates/admin/defaults/defaults.html:35
#: rhodecode/templates/admin/repos/repo_add_base.html:38
@@ -1858,9 +1860,10 @@ msgstr "匿名访问"
#: rhodecode/templates/admin/permissions/permissions.html:49
msgid ""
"All default permissions on each repository will be reset to choosen "
-"permission, note that all custom default permission on repositories will "
-"be lost"
-msgstr "所有版本库的默认权限将被重置到选择的权限,所有版本库的自定义权限将被丢弃"
+"permission, note that all custom default permission on repositories will be "
+"lost"
+msgstr ""
+"所有版本库的默认权限将被重置到选择的权限,所有版本库的自定义权限将被丢弃"
#: rhodecode/templates/admin/permissions/permissions.html:50
#: rhodecode/templates/admin/permissions/permissions.html:63
@@ -1877,12 +1880,12 @@ msgid "Repository group"
msgstr "版本库组"
#: rhodecode/templates/admin/permissions/permissions.html:62
-#, fuzzy
msgid ""
-"All default permissions on each repository group will be reset to choosen"
-" permission, note that all custom default permission on repositories "
-"group will be lost"
-msgstr "所有版本库的默认权限将被重置到选择的权限,所有版本库的自定义权限将被丢弃"
+"All default permissions on each repository group will be reset to choosen "
+"permission, note that all custom default permission on repositories group "
+"will be lost"
+msgstr ""
+"所有版本库组的默认权限将被重置到选择的权限,所有版本库组的自定义权限将被丢弃"
#: rhodecode/templates/admin/permissions/permissions.html:69
msgid "Registration"
@@ -1955,7 +1958,8 @@ msgstr "文件浏览、下载、whoosh和README的默认修订版本"
#: rhodecode/templates/admin/repos/repo_edit.html:79
#: rhodecode/templates/forks/fork.html:63
#: rhodecode/templates/settings/repo_settings.html:70
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
msgstr "保持简短。用README文件来写更长的描述。"
#: rhodecode/templates/admin/repos/repo_add_base.html:73
@@ -2064,8 +2068,8 @@ msgstr "确认清除版本库缓存"
#: rhodecode/templates/admin/repos/repo_edit.html:193
msgid ""
-"Manually invalidate cache for this repository. On first access repository"
-" will be cached again"
+"Manually invalidate cache for this repository. On first access repository "
+"will be cached again"
msgstr "手动清除版本库缓存。之后第一次访问的时候将重建缓存"
#: rhodecode/templates/admin/repos/repo_edit.html:198
@@ -2105,8 +2109,8 @@ msgstr "添加到公共日志"
#: rhodecode/templates/admin/repos/repo_edit.html:231
msgid ""
-"All actions made on this repository will be accessible to everyone in "
-"public journal"
+"All actions made on this repository will be accessible to everyone in public "
+"journal"
msgstr "任何人都可以在公共日志上看到这个版本库上的所有动作"
#: rhodecode/templates/admin/repos/repo_edit.html:238
@@ -2134,7 +2138,8 @@ msgid "Repository is not locked"
msgstr "版本库未锁定"
#: rhodecode/templates/admin/repos/repo_edit.html:252
-msgid "Force locking on repository. Works only when anonymous access is disabled"
+msgid ""
+"Force locking on repository. Works only when anonymous access is disabled"
msgstr "强制锁定版本库。只有在禁止匿名访问时候才有效"
#: rhodecode/templates/admin/repos/repo_edit.html:259
@@ -2162,14 +2167,13 @@ msgstr "确认删除版本库"
#: rhodecode/templates/admin/repos/repo_edit.html:282
#: rhodecode/templates/settings/repo_settings.html:119
-#, fuzzy
msgid ""
-"This repository will be renamed in a special way in order to be "
-"unaccesible for RhodeCode and VCS systems. If you need fully delete it "
-"from file system please do it manually"
+"This repository will be renamed in a special way in order to be unaccesible "
+"for RhodeCode and VCS systems. If you need fully delete it from file system "
+"please do it manually"
msgstr ""
-"这个版本库将以特殊的方式重命名这样RhodeCode和版本控制系统将不能访问它。\n"
-" 如果需要从文件系统完全删除,你需要手动操作"
+"这个版本库将以特殊的方式重命名这样RhodeCode和版本控制系统将不能访问它。如果需"
+"要从文件系统完全删除,请要手动操作"
#: rhodecode/templates/admin/repos/repo_edit_perms.html:3
#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3
@@ -2263,7 +2267,8 @@ msgstr "设置或者撤销该组所有成员的权限,包括版本库和其他
#: rhodecode/templates/files/files_add.html:15
#: rhodecode/templates/files/files_edit.html:15
#: rhodecode/templates/followers/followers.html:9
-#: rhodecode/templates/forks/fork.html:9 rhodecode/templates/forks/forks.html:9
+#: rhodecode/templates/forks/fork.html:9
+#: rhodecode/templates/forks/forks.html:9
#: rhodecode/templates/pullrequests/pullrequest.html:8
#: rhodecode/templates/pullrequests/pullrequest_show.html:8
#: rhodecode/templates/pullrequests/pullrequest_show_all.html:8
@@ -2314,8 +2319,8 @@ msgstr "编辑版本库组"
#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:70
msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
msgstr "启用组的拉取锁定。这个选项将应用到组内的其他组和版本库"
#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5
@@ -2391,10 +2396,11 @@ msgstr "重新扫描选项"
#: rhodecode/templates/admin/settings/settings.html:38
msgid ""
-"In case a repository was deleted from filesystem and there are leftovers "
-"in the database check this option to scan obsolete data in database and "
-"remove it."
-msgstr "如果版本库已经从文件系统删除,但数据库仍然有遗留信息,请勾选该项进行清理"
+"In case a repository was deleted from filesystem and there are leftovers in "
+"the database check this option to scan obsolete data in database and remove "
+"it."
+msgstr ""
+"如果版本库已经从文件系统删除,但数据库仍然有遗留信息,请勾选该项进行清理"
#: rhodecode/templates/admin/settings/settings.html:39
msgid "destroy old data"
@@ -2402,9 +2408,10 @@ msgstr "清理旧数据"
#: rhodecode/templates/admin/settings/settings.html:41
msgid ""
-"Rescan repositories location for new repositories. Also deletes obsolete "
-"if `destroy` flag is checked "
-msgstr "重新扫描版本库路径以发现新版本库。 同时删除过时的,如果设置有 `destroy` 标志"
+"Rescan repositories location for new repositories. Also deletes obsolete if "
+"`destroy` flag is checked "
+msgstr ""
+"重新扫描版本库路径以发现新版本库。 同时删除过时的,如果设置有 `destroy` 标志"
#: rhodecode/templates/admin/settings/settings.html:46
msgid "Rescan repositories"
@@ -2494,9 +2501,11 @@ msgstr "要求使用SSL进行版本控制系统操作"
#: rhodecode/templates/admin/settings/settings.html:203
msgid ""
-"RhodeCode will require SSL for pushing or pulling. If SSL is missing it "
-"will return HTTP Error 406: Not Acceptable"
-msgstr "勾选后RhodeCode将要求使用SSL进行推送和拉取。如果没有使用SSL将返回HTTP 406错误:Not Acceptable"
+"RhodeCode will require SSL for pushing or pulling. If SSL is missing it will "
+"return HTTP Error 406: Not Acceptable"
+msgstr ""
+"勾选后RhodeCode将要求使用SSL进行推送和拉取。如果没有使用SSL将返回HTTP 406错"
+"误:Not Acceptable"
#: rhodecode/templates/admin/settings/settings.html:209
msgid "Hooks"
@@ -2547,8 +2556,8 @@ msgstr "版本库路径"
#: rhodecode/templates/admin/settings/settings.html:261
msgid ""
"This a crucial application setting. If you are really sure you need to "
-"change this, you must restart application in order to make this setting "
-"take effect. Click this label to unlock."
+"change this, you must restart application in order to make this setting take "
+"effect. Click this label to unlock."
msgstr "这是一个关键设置。如果确认修改该项设置,请重启服务以便设置生效。"
#: rhodecode/templates/admin/settings/settings.html:262
@@ -2558,8 +2567,8 @@ msgstr "解锁"
#: rhodecode/templates/admin/settings/settings.html:263
msgid ""
-"Location where repositories are stored. After changing this value a "
-"restart, and rescan is required"
+"Location where repositories are stored. After changing this value a restart, "
+"and rescan is required"
msgstr "版本库存储路径。 修改后需要重启和重新扫描"
#: rhodecode/templates/admin/settings/settings.html:283
@@ -2932,7 +2941,8 @@ msgid "Products"
msgstr "产品"
#: rhodecode/templates/base/base.html:152
-#: rhodecode/templates/base/base.html:182 rhodecode/templates/base/root.html:47
+#: rhodecode/templates/base/base.html:182
+#: rhodecode/templates/base/root.html:47
msgid "loading..."
msgstr "载入中..."
@@ -2986,7 +2996,8 @@ msgstr "版本库选项"
msgid "fork"
msgstr "复刻"
-#: rhodecode/templates/base/base.html:212 rhodecode/templates/base/root.html:50
+#: rhodecode/templates/base/base.html:212
+#: rhodecode/templates/base/root.html:50
#: rhodecode/templates/changelog/changelog.html:43
msgid "Open new pull request"
msgstr "新建拉取请求"
@@ -3017,9 +3028,8 @@ msgid "permissions"
msgstr "权限"
#: rhodecode/templates/base/base.html:239
-#, fuzzy
msgid "defaults"
-msgstr "默认"
+msgstr "默认设置"
#: rhodecode/templates/base/base.html:240
msgid "settings"
@@ -3238,9 +3248,8 @@ msgid "Changeset"
msgstr "修订集"
#: rhodecode/templates/changeset/changeset.html:52
-#, fuzzy
msgid "No children"
-msgstr "应用到成员"
+msgstr "无子对象"
#: rhodecode/templates/changeset/changeset.html:70
#: rhodecode/templates/changeset/diff_block.html:20
@@ -3302,7 +3311,8 @@ msgstr "评论使用%s语法并支持%s"
#: rhodecode/templates/changeset/changeset_file_comment.html:48
#: rhodecode/templates/changeset/changeset_file_comment.html:123
-msgid "Use @username inside this text to send notification to this RhodeCode user"
+msgid ""
+"Use @username inside this text to send notification to this RhodeCode user"
msgstr "在文本中使用 @用户名 以发送通知到该RhodeCode用户"
#: rhodecode/templates/changeset/changeset_file_comment.html:59
@@ -3433,9 +3443,8 @@ msgid "Confirm to delete this user: %s"
msgstr "确认删除用户:%s"
#: rhodecode/templates/email_templates/changeset_comment.html:10
-#, fuzzy
msgid "New status$"
-msgstr "改变状态"
+msgstr "新状态$"
#: rhodecode/templates/email_templates/main.html:8
msgid "This is a notification from RhodeCode."
@@ -3443,29 +3452,28 @@ msgstr "这是一个RhodeCode通知。"
#: rhodecode/templates/email_templates/password_reset.html:4
msgid "Hello"
-msgstr ""
+msgstr "你好"
#: rhodecode/templates/email_templates/password_reset.html:6
msgid "We received a request to create a new password for your account."
-msgstr ""
+msgstr "我们收到重置你用户密码的请求。"
#: rhodecode/templates/email_templates/password_reset.html:8
msgid "You can generate it by clicking following URL"
-msgstr ""
+msgstr "点击下面的链接以重新生成密码:"
#: rhodecode/templates/email_templates/password_reset.html:12
msgid "If you didn't request new password please ignore this email."
-msgstr ""
+msgstr "如果你没有要求重置密码,请忽略这封邮件。"
#: rhodecode/templates/email_templates/pull_request.html:4
#, python-format
msgid ""
"User %s opened pull request for repository %s and wants you to review "
"changes."
-msgstr ""
+msgstr "用户%s在版本库%s中创建了一个拉取请求需要你检视"
#: rhodecode/templates/email_templates/pull_request.html:5
-#, fuzzy
msgid "title"
msgstr "标题"
@@ -3476,35 +3484,32 @@ msgstr "描述"
#: rhodecode/templates/email_templates/pull_request.html:11
msgid "revisions for reviewing"
-msgstr ""
+msgstr "待检视修订"
#: rhodecode/templates/email_templates/pull_request.html:18
-#, fuzzy
msgid "View this pull request here"
-msgstr "为这个拉取请求增加检视人员"
+msgstr "查看拉取请求"
#: rhodecode/templates/email_templates/pull_request_comment.html:4
-#, fuzzy, python-format
+#, python-format
msgid "User %s commented on pull request #%s for repository %s"
-msgstr ""
+msgstr "用户%s评论了版本库%s的拉取请求%s"
#: rhodecode/templates/email_templates/pull_request_comment.html:10
-#, fuzzy
msgid "New status"
-msgstr "改变状态"
+msgstr "新状态"
#: rhodecode/templates/email_templates/pull_request_comment.html:14
msgid "View this comment here"
-msgstr ""
+msgstr "查看评论"
#: rhodecode/templates/email_templates/registration.html:4
-#, fuzzy
msgid "A new user have registered in RhodeCode"
-msgstr "成功注册到RhodeCode"
+msgstr "新用户注册RhodeCode"
#: rhodecode/templates/email_templates/registration.html:9
msgid "View this user here"
-msgstr ""
+msgstr "查看用户"
#: rhodecode/templates/errors/error_document.html:46
#, python-format
@@ -3674,9 +3679,8 @@ msgid "show at revision"
msgstr "显示修订"
#: rhodecode/templates/files/files_history_box.html:11
-#, fuzzy
msgid "show full history"
-msgstr "加载文件历史记录..."
+msgstr "显示全部历史记录"
#: rhodecode/templates/files/files_history_box.html:16
#, python-format
@@ -3918,9 +3922,8 @@ msgid "Compare view"
msgstr "比较显示"
#: rhodecode/templates/pullrequests/pullrequest_show.html:112
-#, fuzzy
msgid "reviewer"
-msgstr "%d个检视者"
+msgstr "检视者"
#: rhodecode/templates/pullrequests/pullrequest_show_all.html:4
msgid "all pull requests"
@@ -3987,14 +3990,12 @@ msgid "%s Settings"
msgstr "%s设置"
#: rhodecode/templates/settings/repo_settings.html:102
-#, fuzzy
msgid "Delete repository"
-msgstr "[删除]版本库"
+msgstr "删除版本库"
#: rhodecode/templates/settings/repo_settings.html:109
-#, fuzzy
msgid "Remove repo"
-msgstr "删除"
+msgstr "删除版本库"
#: rhodecode/templates/shortlog/shortlog.html:5
#, python-format
@@ -4196,20 +4197,3 @@ msgstr "%s标签"
#: rhodecode/templates/tags/tags.html:29
msgid "Compare tags"
msgstr "比较标签"
-
-#~ msgid ""
-#~ "%s repository is not mapped to db"
-#~ " perhaps it was created or renamed"
-#~ " from the file system please run "
-#~ "the application again in order to "
-#~ "rescan repositories"
-#~ msgstr " 版本库%s没有映射到数据库,可能是从文件系统创建或者重命名,请重启RhodeCode以重新扫描版本库"
-
-#~ msgid ""
-#~ "%s repository is not mapped to db"
-#~ " perhaps it was moved or renamed "
-#~ "from the filesystem please run the "
-#~ "application again in order to rescan "
-#~ "repositories"
-#~ msgstr "版本库%s没有映射到数据库,可能是从文件系统创建或者重命名,请重启RhodeCode以重新扫描版本库"
-
diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py
index d234e3e2..151a720e 100644
--- a/rhodecode/lib/auth.py
+++ b/rhodecode/lib/auth.py
@@ -45,7 +45,8 @@ from rhodecode.lib.auth_ldap import AuthLdap
from rhodecode.model import meta
from rhodecode.model.user import UserModel
-from rhodecode.model.db import Permission, RhodeCodeSetting, User
+from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap
+from rhodecode.lib.caching_query import FromCache
log = logging.getLogger(__name__)
@@ -269,21 +270,34 @@ def login_container_auth(username):
return user
-def get_container_username(environ, config):
+def get_container_username(environ, config, clean_username=False):
+ """
+ Get's the container_auth username (or email). It tries to get username
+ from REMOTE_USER if container_auth_enabled is enabled, if that fails
+ it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled
+ is enabled. clean_username extracts the username from this data if it's
+ having @ in it.
+
+ :param environ:
+ :param config:
+ :param clean_username:
+ """
username = None
if str2bool(config.get('container_auth_enabled', False)):
from paste.httpheaders import REMOTE_USER
username = REMOTE_USER(environ)
+ log.debug('extracted REMOTE_USER:%s' % (username))
if not username and str2bool(config.get('proxypass_auth_enabled', False)):
username = environ.get('HTTP_X_FORWARDED_USER')
+ log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username))
- if username:
+ if username and clean_username:
# Removing realm and domain from username
username = username.partition('@')[0]
username = username.rpartition('\\')[2]
- log.debug('Received username %s from container' % username)
+ log.debug('Received username %s from container' % username)
return username
@@ -313,11 +327,12 @@ class AuthUser(object):
in
"""
- def __init__(self, user_id=None, api_key=None, username=None):
+ def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
self.user_id = user_id
self.api_key = None
self.username = username
+ self.ip_addr = ip_addr
self.name = ''
self.lastname = ''
@@ -380,6 +395,24 @@ class AuthUser(object):
def is_admin(self):
return self.admin
+ @property
+ def ip_allowed(self):
+ """
+ Checks if ip_addr used in constructor is allowed from defined list of
+ allowed ip_addresses for user
+
+ :returns: boolean, True if ip is in allowed ip range
+ """
+ #check IP
+ allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True)
+ if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips):
+ log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips))
+ return True
+ else:
+ log.info('Access for IP:%s forbidden, '
+ 'not in %s' % (self.ip_addr, allowed_ips))
+ return False
+
def __repr__(self):
return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
self.is_authenticated)
@@ -406,6 +439,17 @@ class AuthUser(object):
api_key = cookie_store.get('api_key')
return AuthUser(user_id, api_key, username)
+ @classmethod
+ def get_allowed_ips(cls, user_id, cache=False):
+ _set = set()
+ user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
+ if cache:
+ user_ips = user_ips.options(FromCache("sql_cache_short",
+ "get_user_ips_%s" % user_id))
+ for ip in user_ips:
+ _set.add(ip.ip_addr)
+ return _set or set(['0.0.0.0/0'])
+
def set_available_permissions(config):
"""
@@ -450,6 +494,15 @@ class LoginRequired(object):
def __wrapper(self, func, *fargs, **fkwargs):
cls = fargs[0]
user = cls.rhodecode_user
+ loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
+
+ #check IP
+ ip_access_ok = True
+ if not user.ip_allowed:
+ from rhodecode.lib import helpers as h
+ h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
+ category='warning')
+ ip_access_ok = False
api_access_ok = False
if self.api_access:
@@ -458,9 +511,9 @@ class LoginRequired(object):
api_access_ok = True
else:
log.debug("API KEY token not valid")
- loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
+
log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
- if user.is_authenticated or api_access_ok:
+ if (user.is_authenticated or api_access_ok) and ip_access_ok:
reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
log.info('user %s is authenticated and granted access to %s '
'using %s' % (user.username, loc, reason)
@@ -682,12 +735,12 @@ class PermsFunction(object):
return False
self.user_perms = user.permissions
if self.check_permissions():
- log.debug('Permission granted for user: %s @ %s', user,
+ log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
check_Location or 'unspecified location')
return True
else:
- log.debug('Permission denied for user: %s @ %s', user,
+ log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
check_Location or 'unspecified location')
return False
@@ -821,3 +874,122 @@ class HasPermissionAnyMiddleware(object):
)
)
return False
+
+
+#==============================================================================
+# SPECIAL VERSION TO HANDLE API AUTH
+#==============================================================================
+class _BaseApiPerm(object):
+ def __init__(self, *perms):
+ self.required_perms = set(perms)
+
+ def __call__(self, check_location='unspecified', user=None, repo_name=None):
+ cls_name = self.__class__.__name__
+ check_scope = 'user:%s, repo:%s' % (user, repo_name)
+ log.debug('checking cls:%s %s %s @ %s', cls_name,
+ self.required_perms, check_scope, check_location)
+ if not user:
+ log.debug('Empty User passed into arguments')
+ return False
+
+ ## process user
+ if not isinstance(user, AuthUser):
+ user = AuthUser(user.user_id)
+
+ if self.check_permissions(user.permissions, repo_name):
+ log.debug('Permission to %s granted for user: %s @ %s', repo_name,
+ user, check_location)
+ return True
+
+ else:
+ log.debug('Permission to %s denied for user: %s @ %s', repo_name,
+ user, check_location)
+ return False
+
+ def check_permissions(self, perm_defs, repo_name):
+ """
+ implement in child class should return True if permissions are ok,
+ False otherwise
+
+ :param perm_defs: dict with permission definitions
+ :param repo_name: repo name
+ """
+ raise NotImplementedError()
+
+
+class HasPermissionAllApi(_BaseApiPerm):
+ def __call__(self, user, check_location=''):
+ return super(HasPermissionAllApi, self)\
+ .__call__(check_location=check_location, user=user)
+
+ def check_permissions(self, perm_defs, repo):
+ if self.required_perms.issubset(perm_defs.get('global')):
+ return True
+ return False
+
+
+class HasPermissionAnyApi(_BaseApiPerm):
+ def __call__(self, user, check_location=''):
+ return super(HasPermissionAnyApi, self)\
+ .__call__(check_location=check_location, user=user)
+
+ def check_permissions(self, perm_defs, repo):
+ if self.required_perms.intersection(perm_defs.get('global')):
+ return True
+ return False
+
+
+class HasRepoPermissionAllApi(_BaseApiPerm):
+ def __call__(self, user, repo_name, check_location=''):
+ return super(HasRepoPermissionAllApi, self)\
+ .__call__(check_location=check_location, user=user,
+ repo_name=repo_name)
+
+ def check_permissions(self, perm_defs, repo_name):
+
+ try:
+ self._user_perms = set(
+ [perm_defs['repositories'][repo_name]]
+ )
+ except KeyError:
+ log.warning(traceback.format_exc())
+ return False
+ if self.required_perms.issubset(self._user_perms):
+ return True
+ return False
+
+
+class HasRepoPermissionAnyApi(_BaseApiPerm):
+ def __call__(self, user, repo_name, check_location=''):
+ return super(HasRepoPermissionAnyApi, self)\
+ .__call__(check_location=check_location, user=user,
+ repo_name=repo_name)
+
+ def check_permissions(self, perm_defs, repo_name):
+
+ try:
+ _user_perms = set(
+ [perm_defs['repositories'][repo_name]]
+ )
+ except KeyError:
+ log.warning(traceback.format_exc())
+ return False
+ if self.required_perms.intersection(_user_perms):
+ return True
+ return False
+
+
+def check_ip_access(source_ip, allowed_ips=None):
+ """
+ Checks if source_ip is a subnet of any of allowed_ips.
+
+ :param source_ip:
+ :param allowed_ips: list of allowed ips together with mask
+ """
+ from rhodecode.lib import ipaddr
+ log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
+ if isinstance(allowed_ips, (tuple, list, set)):
+ for ip in allowed_ips:
+ if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
+ return True
+ return False
diff --git a/rhodecode/lib/base.py b/rhodecode/lib/base.py
index bf60b70f..873ece86 100644
--- a/rhodecode/lib/base.py
+++ b/rhodecode/lib/base.py
@@ -37,13 +37,18 @@ def _get_ip_addr(environ):
proxy_key2 = 'HTTP_X_FORWARDED_FOR'
def_key = 'REMOTE_ADDR'
- ip = environ.get(proxy_key2)
+ ip = environ.get(proxy_key)
if ip:
return ip
- ip = environ.get(proxy_key)
-
+ ip = environ.get(proxy_key2)
if ip:
+ # HTTP_X_FORWARDED_FOR can have mutliple ips inside
+ # the left-most being the original client, and each successive proxy
+ # that passed the request adding the IP address where it received the
+ # request from.
+ if ',' in ip:
+ ip = ip.split(',')[0].strip()
return ip
ip = environ.get(def_key, '0.0.0.0')
@@ -101,7 +106,7 @@ class BaseVCSController(object):
#authenticate this mercurial request using authfunc
self.authenticate = BasicAuth('', authfunc,
config.get('auth_ret_code'))
- self.ipaddr = '0.0.0.0'
+ self.ip_addr = '0.0.0.0'
def _handle_request(self, environ, start_response):
raise NotImplementedError()
@@ -136,7 +141,7 @@ class BaseVCSController(object):
"""
invalidate_cache('get_repo_cached_%s' % repo_name)
- def _check_permission(self, action, user, repo_name):
+ def _check_permission(self, action, user, repo_name, ip_addr=None):
"""
Checks permissions using action (push/pull) user and repository
name
@@ -145,6 +150,12 @@ class BaseVCSController(object):
:param user: user instance
:param repo_name: repository name
"""
+ #check IP
+ authuser = AuthUser(user_id=user.user_id, ip_addr=ip_addr)
+ if not authuser.ip_allowed:
+ return False
+ else:
+ log.info('Access for IP:%s allowed' % (ip_addr))
if action == 'push':
if not HasPermissionAnyMiddleware('repository.write',
'repository.admin')(user,
@@ -235,6 +246,9 @@ class BaseVCSController(object):
class BaseController(WSGIController):
def __before__(self):
+ """
+ __before__ is called before controller methods and after __call__
+ """
c.rhodecode_version = __version__
c.rhodecode_instanceid = config.get('instance_id')
c.rhodecode_name = config.get('rhodecode_title')
@@ -258,7 +272,6 @@ class BaseController(WSGIController):
self.sa = meta.Session
self.scm_model = ScmModel(self.sa)
- self.ip_addr = ''
def __call__(self, environ, start_response):
"""Invoke the Controller"""
@@ -273,7 +286,7 @@ class BaseController(WSGIController):
cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
user_id = cookie_store.get('user_id', None)
username = get_container_username(environ, config)
- auth_user = AuthUser(user_id, api_key, username)
+ auth_user = AuthUser(user_id, api_key, username, self.ip_addr)
request.user = auth_user
self.rhodecode_user = c.rhodecode_user = auth_user
if not self.rhodecode_user.is_authenticated and \
@@ -311,7 +324,7 @@ class BaseRepoController(BaseController):
dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
# update last change according to VCS data
- dbr.update_last_change(c.rhodecode_repo.last_change)
+ dbr.update_changeset_cache(dbr.get_changeset())
if c.rhodecode_repo is None:
log.error('%s this repository is present in database but it '
'cannot be created as an scm instance', c.repo_name)
diff --git a/rhodecode/lib/celerylib/tasks.py b/rhodecode/lib/celerylib/tasks.py
index 0e601e15..0a72f0f0 100644
--- a/rhodecode/lib/celerylib/tasks.py
+++ b/rhodecode/lib/celerylib/tasks.py
@@ -347,6 +347,10 @@ def send_email(recipients, subject, body, html_body=''):
debug = str2bool(config.get('debug'))
smtp_auth = email_config.get('smtp_auth')
+ if not mail_server:
+ log.error("SMTP mail server not configured - cannot send mail")
+ return False
+
try:
m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
mail_port, ssl, tls, debug=debug)
diff --git a/rhodecode/lib/db_manage.py b/rhodecode/lib/db_manage.py
index 872db568..2a0df345 100644
--- a/rhodecode/lib/db_manage.py
+++ b/rhodecode/lib/db_manage.py
@@ -164,8 +164,8 @@ class DbManage(object):
def step_0(self):
# step 0 is the schema upgrade, and than follow proper upgrades
- notify('attempting to do database upgrade to version %s' \
- % __dbversion__)
+ notify('attempting to do database upgrade from '
+ 'version %s to version %s' %(curr_version, __dbversion__))
api.upgrade(db_uri, repository_path, __dbversion__)
notify('Schema upgrade completed')
@@ -286,6 +286,9 @@ class DbManage(object):
'Please validate and check default permissions '
'in admin panel')
+ def step_10(self):
+ pass
+
upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
# CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_2_0.py b/rhodecode/lib/dbmigrate/schema/db_1_2_0.py
index 823ab2f6..71e97f7d 100755
--- a/rhodecode/lib/dbmigrate/schema/db_1_2_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_2_0.py
@@ -619,7 +619,7 @@ class Repository(Base, BaseModel):
hg_ui = ret
for ui_ in hg_ui:
if ui_.ui_active:
- log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
+ log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
ui_.ui_key, ui_.ui_value)
baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_3_0.py b/rhodecode/lib/dbmigrate/schema/db_1_3_0.py
index 7eadfbda..f59ccb9e 100644
--- a/rhodecode/lib/dbmigrate/schema/db_1_3_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_3_0.py
@@ -623,7 +623,7 @@ class Repository(Base, BaseModel):
hg_ui = ret
for ui_ in hg_ui:
if ui_.ui_active:
- log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
+ log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
ui_.ui_key, ui_.ui_value)
baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_5_0.py b/rhodecode/lib/dbmigrate/schema/db_1_5_0.py
index fb883c3a..d1372ccc 100644
--- a/rhodecode/lib/dbmigrate/schema/db_1_5_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_5_0.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
"""
- rhodecode.model.db_1_4_0
+ rhodecode.model.db_1_5_0
~~~~~~~~~~~~~~~~~~~~~~~~
- Database Models for RhodeCode <=1.5.X
+ Database Models for RhodeCode <=1.5.2
:created_on: Apr 08, 2010
:author: marcink
@@ -23,6 +23,1812 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#TODO: replace that will db.py content after 1.6 Release
+import os
+import logging
+import datetime
+import traceback
+import hashlib
+import time
+from collections import defaultdict
-from rhodecode.model.db import *
+from sqlalchemy import *
+from sqlalchemy.ext.hybrid import hybrid_property
+from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
+from sqlalchemy.exc import DatabaseError
+from beaker.cache import cache_region, region_invalidate
+from webob.exc import HTTPNotFound
+
+from pylons.i18n.translation import lazy_ugettext as _
+
+from rhodecode.lib.vcs import get_backend
+from rhodecode.lib.vcs.utils.helpers import get_scm
+from rhodecode.lib.vcs.exceptions import VCSError
+from rhodecode.lib.vcs.utils.lazy import LazyProperty
+
+from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
+ safe_unicode, remove_suffix, remove_prefix
+from rhodecode.lib.compat import json
+from rhodecode.lib.caching_query import FromCache
+
+from rhodecode.model.meta import Base, Session
+
+URL_SEP = '/'
+log = logging.getLogger(__name__)
+
+#==============================================================================
+# BASE CLASSES
+#==============================================================================
+
+_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
+
+
+class BaseModel(object):
+ """
+ Base Model for all classess
+ """
+
+ @classmethod
+ def _get_keys(cls):
+ """return column names for this model """
+ return class_mapper(cls).c.keys()
+
+ def get_dict(self):
+ """
+ return dict with keys and values corresponding
+ to this model data """
+
+ d = {}
+ for k in self._get_keys():
+ d[k] = getattr(self, k)
+
+ # also use __json__() if present to get additional fields
+ _json_attr = getattr(self, '__json__', None)
+ if _json_attr:
+ # update with attributes from __json__
+ if callable(_json_attr):
+ _json_attr = _json_attr()
+ for k, val in _json_attr.iteritems():
+ d[k] = val
+ return d
+
+ def get_appstruct(self):
+ """return list with keys and values tupples corresponding
+ to this model data """
+
+ l = []
+ for k in self._get_keys():
+ l.append((k, getattr(self, k),))
+ return l
+
+ def populate_obj(self, populate_dict):
+ """populate model with data from given populate_dict"""
+
+ for k in self._get_keys():
+ if k in populate_dict:
+ setattr(self, k, populate_dict[k])
+
+ @classmethod
+ def query(cls):
+ return Session().query(cls)
+
+ @classmethod
+ def get(cls, id_):
+ if id_:
+ return cls.query().get(id_)
+
+ @classmethod
+ def get_or_404(cls, id_):
+ try:
+ id_ = int(id_)
+ except (TypeError, ValueError):
+ raise HTTPNotFound
+
+ res = cls.query().get(id_)
+ if not res:
+ raise HTTPNotFound
+ return res
+
+ @classmethod
+ def getAll(cls):
+ return cls.query().all()
+
+ @classmethod
+ def delete(cls, id_):
+ obj = cls.query().get(id_)
+ Session().delete(obj)
+
+ def __repr__(self):
+ if hasattr(self, '__unicode__'):
+ # python repr needs to return str
+ return safe_str(self.__unicode__())
+ return '<DB:%s>' % (self.__class__.__name__)
+
+
+class RhodeCodeSetting(Base, BaseModel):
+ __tablename__ = 'rhodecode_settings'
+ __table_args__ = (
+ UniqueConstraint('app_settings_name'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'}
+ )
+ 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(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ _app_settings_value = Column("app_settings_value", String(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 self.app_settings_name in ["ldap_active",
+ "default_repo_enable_statistics",
+ "default_repo_enable_locking",
+ "default_repo_private",
+ "default_repo_enable_downloads"]:
+ 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 __unicode__(self):
+ return u"<%s('%s:%s')>" % (
+ self.__class__.__name__,
+ self.app_settings_name, self.app_settings_value
+ )
+
+ @classmethod
+ def get_by_name(cls, key):
+ return cls.query()\
+ .filter(cls.app_settings_name == key).scalar()
+
+ @classmethod
+ def get_by_name_or_create(cls, key):
+ res = cls.get_by_name(key)
+ if not res:
+ res = cls(key)
+ return res
+
+ @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['rhodecode_' + 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
+
+ @classmethod
+ def get_default_repo_settings(cls, cache=False, strip_prefix=False):
+ ret = cls.query()\
+ .filter(cls.app_settings_name.startswith('default_')).all()
+ fd = {}
+ for row in ret:
+ key = row.app_settings_name
+ if strip_prefix:
+ key = remove_prefix(key, prefix='default_')
+ fd.update({key: row.app_settings_value})
+
+ return fd
+
+
+class RhodeCodeUi(Base, BaseModel):
+ __tablename__ = 'rhodecode_ui'
+ __table_args__ = (
+ UniqueConstraint('ui_key'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'}
+ )
+
+ HOOK_UPDATE = 'changegroup.update'
+ HOOK_REPO_SIZE = 'changegroup.repo_size'
+ HOOK_PUSH = 'changegroup.push_logger'
+ HOOK_PRE_PUSH = 'prechangegroup.pre_push'
+ HOOK_PULL = 'outgoing.pull_logger'
+ HOOK_PRE_PULL = 'preoutgoing.pre_pull'
+
+ ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+ ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ ui_value = Column("ui_value", String(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).scalar()
+
+ @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_PRE_PUSH,
+ cls.HOOK_PULL, cls.HOOK_PRE_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_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 '<DB:%s[%s:%s]>' % (self.__class__.__name__, 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'}
+ )
+ DEFAULT_USER = 'default'
+ DEFAULT_PERMISSIONS = [
+ 'hg.register.manual_activate', 'hg.create.repository',
+ 'hg.fork.repository', 'repository.read', 'group.read'
+ ]
+ user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+ username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+ admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
+ name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ _email = Column("email", String(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(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+
+ user_log = relationship('UserLog')
+ 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')
+ 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('UsersGroupMember', 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')
+
+ @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 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
+
+ def __unicode__(self):
+ return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
+ self.user_id, self.username)
+
+ @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):
+ q = cls.query().filter(cls.api_key == api_key)
+
+ if cache:
+ q = q.options(FromCache("sql_cache_short",
+ "get_api_key_%s" % api_key))
+ return q.scalar()
+
+ @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
+
+ def update_lastlogin(self):
+ """Update user lastlogin"""
+ self.last_login = datetime.datetime.now()
+ Session().add(self)
+ log.debug('updated user %s lastlogin' % self.username)
+
+ def get_api_data(self):
+ """
+ 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,
+ api_key=user.api_key,
+ active=user.active,
+ admin=user.admin,
+ ldap_dn=user.ldap_dn,
+ last_login=user.last_login,
+ )
+ 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 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'}
+ )
+ __mapper_args__ = {}
+
+ email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+ user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+ _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+ user = relationship('User', lazy='joined')
+
+ @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 UserLog(Base, BaseModel):
+ __tablename__ = 'user_logs'
+ __table_args__ = (
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'},
+ )
+ 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=True, unique=None, default=None)
+ username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
+ repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ action = Column("action", UnicodeText(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 datetime.date(*self.action_date.timetuple()[:3])
+
+ user = relationship('User')
+ repository = relationship('Repository', cascade='')
+
+
+class UsersGroup(Base, BaseModel):
+ __tablename__ = 'users_groups'
+ __table_args__ = (
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'},
+ )
+
+ 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(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)
+ inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
+
+ members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
+ users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
+ users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
+
+ def __unicode__(self):
+ return u'<userGroup(%s)>' % (self.users_group_name)
+
+ @classmethod
+ def get_by_group_name(cls, group_name, cache=False,
+ case_insensitive=False):
+ if case_insensitive:
+ q = cls.query().filter(cls.users_group_name.ilike(group_name))
+ else:
+ q = cls.query().filter(cls.users_group_name == group_name)
+ if cache:
+ q = q.options(FromCache(
+ "sql_cache_short",
+ "get_user_%s" % _hash_key(group_name)
+ )
+ )
+ return q.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)
+
+ def get_api_data(self):
+ users_group = self
+
+ data = dict(
+ users_group_id=users_group.users_group_id,
+ group_name=users_group.users_group_name,
+ active=users_group.users_group_active,
+ )
+
+ return data
+
+
+class UsersGroupMember(Base, BaseModel):
+ __tablename__ = 'users_groups_members'
+ __table_args__ = (
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'},
+ )
+
+ 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('UsersGroup')
+
+ def __init__(self, gr_id='', u_id=''):
+ self.users_group_id = gr_id
+ self.user_id = u_id
+
+
+class Repository(Base, BaseModel):
+ __tablename__ = 'repositories'
+ __table_args__ = (
+ UniqueConstraint('repo_name'),
+ Index('r_repo_name_idx', 'repo_name'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'},
+ )
+
+ repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+ repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+ clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+ repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+ 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(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+ updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+ landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
+ enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+ _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+
+ 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('UsersGroupRepoToPerm', 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')
+ comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
+
+ pull_requests_org = relationship('PullRequest',
+ primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
+ cascade="all, delete, delete-orphan")
+
+ pull_requests_other = relationship('PullRequest',
+ primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
+ cascade="all, delete, delete-orphan")
+
+ def __unicode__(self):
+ return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
+ self.repo_name)
+
+ @hybrid_property
+ def locked(self):
+ # always should return [user_id, timelocked]
+ if self._locked:
+ _lock_info = self._locked.split(':')
+ return int(_lock_info[0]), _lock_info[1]
+ return [None, None]
+
+ @locked.setter
+ def locked(self, val):
+ if val and isinstance(val, (list, tuple)):
+ self._locked = ':'.join(map(str, val))
+ else:
+ self._locked = None
+
+ @classmethod
+ def url_sep(cls):
+ return URL_SEP
+
+ @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))
+ return q.scalar()
+
+ @classmethod
+ def get_by_full_path(cls, repo_full_path):
+ repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
+ return cls.get_by_repo_name(repo_name.strip(URL_SEP))
+
+ @classmethod
+ def get_repo_forks(cls, repo_id):
+ return cls.query().filter(Repository.fork_id == repo_id)
+
+ @classmethod
+ def base_path(cls):
+ """
+ Returns base path when all repos are stored
+
+ :param cls:
+ """
+ q = Session().query(RhodeCodeUi)\
+ .filter(RhodeCodeUi.ui_key == cls.url_sep())
+ q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+ return q.one().ui_value
+
+ @property
+ def forks(self):
+ """
+ Return forks of this repo
+ """
+ return Repository.get_repo_forks(self.repo_id)
+
+ @property
+ def parent(self):
+ """
+ Returns fork parent
+ """
+ return self.fork
+
+ @property
+ def just_name(self):
+ return self.repo_name.split(Repository.url_sep())[-1]
+
+ @property
+ def groups_with_parents(self):
+ groups = []
+ if self.group is None:
+ return groups
+
+ cur_gr = self.group
+ groups.insert(0, cur_gr)
+ while 1:
+ gr = getattr(cur_gr, 'parent_group', None)
+ cur_gr = cur_gr.parent_group
+ if gr is None:
+ break
+ groups.insert(0, gr)
+
+ return groups
+
+ @property
+ def groups_and_repo(self):
+ return self.groups_with_parents, self.just_name
+
+ @LazyProperty
+ def repo_path(self):
+ """
+ Returns base full path for that repository means where it actually
+ exists on a filesystem
+ """
+ q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
+ Repository.url_sep())
+ q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
+ return q.one().ui_value
+
+ @property
+ def repo_full_path(self):
+ p = [self.repo_path]
+ # we need to split the name by / since this is how we store the
+ # names in the database, but that eventually needs to be converted
+ # into a valid system path
+ p += self.repo_name.split(Repository.url_sep())
+ return os.path.join(*p)
+
+ @property
+ def cache_keys(self):
+ """
+ Returns associated cache keys for that repo
+ """
+ return CacheInvalidation.query()\
+ .filter(CacheInvalidation.cache_args == self.repo_name)\
+ .order_by(CacheInvalidation.cache_key)\
+ .all()
+
+ def get_new_name(self, repo_name):
+ """
+ returns new full repository name based on assigned group and new new
+
+ :param group_name:
+ """
+ path_prefix = self.group.full_path_splitted if self.group else []
+ return Repository.url_sep().join(path_prefix + [repo_name])
+
+ @property
+ def _ui(self):
+ """
+ Creates an db based ui object for this repository
+ """
+ from rhodecode.lib.utils import make_ui
+ return make_ui('db', clear_session=False)
+
+ @classmethod
+ def inject_ui(cls, repo, extras={}):
+ from rhodecode.lib.vcs.backends.hg import MercurialRepository
+ from rhodecode.lib.vcs.backends.git import GitRepository
+ required = (MercurialRepository, GitRepository)
+ if not isinstance(repo, required):
+ raise Exception('repo must be instance of %s' % required)
+
+ # inject ui extra param to log this action via push logger
+ for k, v in extras.items():
+ repo._repo.ui.setconfig('rhodecode_extras', k, v)
+
+ @classmethod
+ def is_valid(cls, repo_name):
+ """
+ returns True if given repo name is a valid filesystem repository
+
+ :param cls:
+ :param repo_name:
+ """
+ from rhodecode.lib.utils import is_valid_repo
+
+ return is_valid_repo(repo_name, cls.base_path())
+
+ def get_api_data(self):
+ """
+ Common function for generating repo api data
+
+ """
+ repo = self
+ data = dict(
+ repo_id=repo.repo_id,
+ repo_name=repo.repo_name,
+ repo_type=repo.repo_type,
+ clone_uri=repo.clone_uri,
+ private=repo.private,
+ created_on=repo.created_on,
+ description=repo.description,
+ landing_rev=repo.landing_rev,
+ owner=repo.user.username,
+ fork_of=repo.fork.repo_name if repo.fork else None
+ )
+
+ return data
+
+ @classmethod
+ def lock(cls, repo, user_id):
+ repo.locked = [user_id, time.time()]
+ Session().add(repo)
+ Session().commit()
+
+ @classmethod
+ def unlock(cls, repo):
+ repo.locked = None
+ Session().add(repo)
+ Session().commit()
+
+ @property
+ def last_db_change(self):
+ return self.updated_on
+
+ #==========================================================================
+ # SCM PROPERTIES
+ #==========================================================================
+
+ def get_changeset(self, rev=None):
+ return get_changeset_safe(self.scm_instance, rev)
+
+ def get_landing_changeset(self):
+ """
+ Returns landing changeset, or if that doesn't exist returns the tip
+ """
+ cs = self.get_changeset(self.landing_rev) or self.get_changeset()
+ return cs
+
+ def update_last_change(self, last_change=None):
+ if last_change is None:
+ last_change = datetime.datetime.now()
+ if self.updated_on is None or self.updated_on != last_change:
+ log.debug('updated repo %s with new date %s' % (self, last_change))
+ self.updated_on = last_change
+ Session().add(self)
+ Session().commit()
+
+ @property
+ def tip(self):
+ return self.get_changeset('tip')
+
+ @property
+ def author(self):
+ return self.tip.author
+
+ @property
+ def last_change(self):
+ return self.scm_instance.last_change
+
+ def get_comments(self, revisions=None):
+ """
+ Returns comments for this repository grouped by revisions
+
+ :param revisions: filter query by revisions only
+ """
+ cmts = ChangesetComment.query()\
+ .filter(ChangesetComment.repo == self)
+ if revisions:
+ cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
+ grouped = defaultdict(list)
+ for cmt in cmts.all():
+ grouped[cmt.revision].append(cmt)
+ return grouped
+
+ def statuses(self, revisions=None):
+ """
+ Returns statuses for this repository
+
+ :param revisions: list of revisions to get statuses for
+ :type revisions: list
+ """
+
+ statuses = ChangesetStatus.query()\
+ .filter(ChangesetStatus.repo == self)\
+ .filter(ChangesetStatus.version == 0)
+ if revisions:
+ statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
+ grouped = {}
+
+ #maybe we have open new pullrequest without a status ?
+ stat = ChangesetStatus.STATUS_UNDER_REVIEW
+ status_lbl = ChangesetStatus.get_status_lbl(stat)
+ for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
+ for rev in pr.revisions:
+ pr_id = pr.pull_request_id
+ pr_repo = pr.other_repo.repo_name
+ grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
+
+ for stat in statuses.all():
+ pr_id = pr_repo = None
+ if stat.pull_request:
+ pr_id = stat.pull_request.pull_request_id
+ pr_repo = stat.pull_request.other_repo.repo_name
+ grouped[stat.revision] = [str(stat.status), stat.status_lbl,
+ pr_id, pr_repo]
+ return grouped
+
+ #==========================================================================
+ # SCM CACHE INSTANCE
+ #==========================================================================
+
+ @property
+ def invalidate(self):
+ return CacheInvalidation.invalidate(self.repo_name)
+
+ def set_invalidate(self):
+ """
+ set a cache for invalidation for this instance
+ """
+ CacheInvalidation.set_invalidate(repo_name=self.repo_name)
+
+ @LazyProperty
+ def scm_instance(self):
+ import rhodecode
+ full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
+ if full_cache:
+ return self.scm_instance_cached()
+ return self.__get_instance()
+
+ def scm_instance_cached(self, cache_map=None):
+ @cache_region('long_term')
+ def _c(repo_name):
+ return self.__get_instance()
+ rn = self.repo_name
+ log.debug('Getting cached instance of repo')
+
+ if cache_map:
+ # get using prefilled cache_map
+ invalidate_repo = cache_map[self.repo_name]
+ if invalidate_repo:
+ invalidate_repo = (None if invalidate_repo.cache_active
+ else invalidate_repo)
+ else:
+ # get from invalidate
+ invalidate_repo = self.invalidate
+
+ if invalidate_repo is not None:
+ region_invalidate(_c, None, rn)
+ # update our cache
+ CacheInvalidation.set_valid(invalidate_repo.cache_key)
+ return _c(rn)
+
+ def __get_instance(self):
+ repo_full_path = self.repo_full_path
+ try:
+ alias = get_scm(repo_full_path)[0]
+ log.debug('Creating instance of %s repository' % alias)
+ backend = get_backend(alias)
+ except VCSError:
+ log.error(traceback.format_exc())
+ log.error('Perhaps this repository is in db and not in '
+ 'filesystem run rescan repositories with '
+ '"destroy old data " option from admin panel')
+ return
+
+ if alias == 'hg':
+
+ repo = backend(safe_str(repo_full_path), create=False,
+ baseui=self._ui)
+ # skip hidden web repository
+ if repo._get_hidden():
+ return
+ else:
+ repo = backend(repo_full_path, create=False)
+
+ return repo
+
+
+class RepoGroup(Base, BaseModel):
+ __tablename__ = 'groups'
+ __table_args__ = (
+ UniqueConstraint('group_name', 'group_parent_id'),
+ CheckConstraint('group_id != group_parent_id'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'},
+ )
+ __mapper_args__ = {'order_by': 'group_name'}
+
+ group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+ group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+ group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
+ group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
+
+ repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
+ users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
+
+ parent_group = relationship('RepoGroup', remote_side=group_id)
+
+ def __init__(self, group_name='', parent_group=None):
+ self.group_name = group_name
+ self.parent_group = parent_group
+
+ def __unicode__(self):
+ return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
+ self.group_name)
+
+ @classmethod
+ def groups_choices(cls, check_perms=False):
+ from webhelpers.html import literal as _literal
+ from rhodecode.model.scm import ScmModel
+ groups = cls.query().all()
+ if check_perms:
+ #filter group user have access to, it's done
+ #magically inside ScmModel based on current user
+ groups = ScmModel().get_repos_groups(groups)
+ repo_groups = [('', '')]
+ sep = ' &raquo; '
+ _name = lambda k: _literal(sep.join(k))
+
+ repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
+ for x in groups])
+
+ repo_groups = sorted(repo_groups, key=lambda t: t[1].split(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 = 5
+ 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 infinit loops
+ log.error('group nested more than %s' %
+ parents_recursion_limit)
+ 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_groups_and_repos(self):
+ """
+ Recursive return all groups, with repositories in those groups
+ """
+ all_ = []
+
+ def _get_members(root_gr):
+ 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 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])
+
+
+class Permission(Base, BaseModel):
+ __tablename__ = 'permissions'
+ __table_args__ = (
+ Index('p_perm_name_idx', 'permission_name'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'},
+ )
+ PERMS = [
+ ('repository.none', _('Repository no access')),
+ ('repository.read', _('Repository read access')),
+ ('repository.write', _('Repository write access')),
+ ('repository.admin', _('Repository admin access')),
+
+ ('group.none', _('Repositories Group no access')),
+ ('group.read', _('Repositories Group read access')),
+ ('group.write', _('Repositories Group write access')),
+ ('group.admin', _('Repositories Group admin access')),
+
+ ('hg.admin', _('RhodeCode Administrator')),
+ ('hg.create.none', _('Repository creation disabled')),
+ ('hg.create.repository', _('Repository creation enabled')),
+ ('hg.fork.none', _('Repository forking disabled')),
+ ('hg.fork.repository', _('Repository forking enabled')),
+ ('hg.register.none', _('Register disabled')),
+ ('hg.register.manual_activate', _('Register new user with RhodeCode '
+ 'with manual activation')),
+
+ ('hg.register.auto_activate', _('Register new user with RhodeCode '
+ 'with auto activation')),
+ ]
+
+ # defines which permissions are more important higher 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,
+
+ 'hg.fork.none': 0,
+ 'hg.fork.repository': 1,
+ 'hg.create.none': 0,
+ 'hg.create.repository':1
+ }
+
+ permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+ permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), 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()
+
+
+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'}
+ )
+ repo_to_perm_id = Column("repo_to_perm_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)
+ permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+ repository_id = Column("repository_id", 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'<user:%s => %s >' % (self.user, self.repository)
+
+
+class UserToPerm(Base, BaseModel):
+ __tablename__ = 'user_to_perm'
+ __table_args__ = (
+ UniqueConstraint('user_id', 'permission_id'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'}
+ )
+ user_to_perm_id = Column("user_to_perm_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)
+ permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+ user = relationship('User')
+ permission = relationship('Permission', lazy='joined')
+
+
+class UsersGroupRepoToPerm(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'}
+ )
+ users_group_to_perm_id = Column("users_group_to_perm_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)
+ permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+ repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+ users_group = relationship('UsersGroup')
+ 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'<userGroup:%s => %s >' % (self.users_group, self.repository)
+
+
+class UsersGroupToPerm(Base, BaseModel):
+ __tablename__ = 'users_group_to_perm'
+ __table_args__ = (
+ UniqueConstraint('users_group_id', 'permission_id',),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'}
+ )
+ users_group_to_perm_id = Column("users_group_to_perm_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)
+ permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+ users_group = relationship('UsersGroup')
+ permission = relationship('Permission')
+
+
+class UserRepoGroupToPerm(Base, BaseModel):
+ __tablename__ = 'user_repo_group_to_perm'
+ __table_args__ = (
+ UniqueConstraint('user_id', 'group_id', 'permission_id'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'}
+ )
+
+ group_to_perm_id = Column("group_to_perm_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)
+ group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+ permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+ user = relationship('User')
+ group = relationship('RepoGroup')
+ permission = relationship('Permission')
+
+
+class UsersGroupRepoGroupToPerm(Base, BaseModel):
+ __tablename__ = 'users_group_repo_group_to_perm'
+ __table_args__ = (
+ UniqueConstraint('users_group_id', 'group_id'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'}
+ )
+
+ users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_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)
+ group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
+ permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+ users_group = relationship('UsersGroup')
+ permission = relationship('Permission')
+ group = relationship('RepoGroup')
+
+
+class Statistics(Base, BaseModel):
+ __tablename__ = 'statistics'
+ __table_args__ = (
+ UniqueConstraint('repository_id'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'}
+ )
+ stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+ repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
+ stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
+ commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
+ commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
+ languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
+
+ repository = relationship('Repository', single_parent=True)
+
+
+class UserFollowing(Base, BaseModel):
+ __tablename__ = 'user_followings'
+ __table_args__ = (
+ UniqueConstraint('user_id', 'follows_repository_id'),
+ UniqueConstraint('user_id', 'follows_user_id'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'}
+ )
+
+ user_following_id = Column("user_following_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)
+ follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
+ follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+ follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+
+ user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
+
+ follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
+ follows_repository = relationship('Repository', order_by='Repository.repo_name')
+
+ @classmethod
+ def get_repo_followers(cls, repo_id):
+ return cls.query().filter(cls.follows_repo_id == repo_id)
+
+
+class CacheInvalidation(Base, BaseModel):
+ __tablename__ = 'cache_invalidation'
+ __table_args__ = (
+ UniqueConstraint('cache_key'),
+ Index('key_idx', 'cache_key'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'},
+ )
+ cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+ cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+ cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
+
+ def __init__(self, cache_key, cache_args=''):
+ self.cache_key = cache_key
+ self.cache_args = cache_args
+ self.cache_active = False
+
+ def __unicode__(self):
+ return u"<%s('%s:%s')>" % (self.__class__.__name__,
+ self.cache_id, self.cache_key)
+
+ @property
+ def prefix(self):
+ _split = self.cache_key.split(self.cache_args, 1)
+ if _split and len(_split) == 2:
+ return _split[0]
+ return ''
+
+ @classmethod
+ def clear_cache(cls):
+ cls.query().delete()
+
+ @classmethod
+ def _get_key(cls, key):
+ """
+ Wrapper for generating a key, together with a prefix
+
+ :param key:
+ """
+ import rhodecode
+ prefix = ''
+ org_key = key
+ iid = rhodecode.CONFIG.get('instance_id')
+ if iid:
+ prefix = iid
+
+ return "%s%s" % (prefix, key), prefix, org_key
+
+ @classmethod
+ def get_by_key(cls, key):
+ return cls.query().filter(cls.cache_key == key).scalar()
+
+ @classmethod
+ def get_by_repo_name(cls, repo_name):
+ return cls.query().filter(cls.cache_args == repo_name).all()
+
+ @classmethod
+ def _get_or_create_key(cls, key, repo_name, commit=True):
+ inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
+ if not inv_obj:
+ try:
+ inv_obj = CacheInvalidation(key, repo_name)
+ Session().add(inv_obj)
+ if commit:
+ Session().commit()
+ except Exception:
+ log.error(traceback.format_exc())
+ Session().rollback()
+ return inv_obj
+
+ @classmethod
+ def invalidate(cls, key):
+ """
+ Returns Invalidation object if this given key should be invalidated
+ None otherwise. `cache_active = False` means that this cache
+ state is not valid and needs to be invalidated
+
+ :param key:
+ """
+ repo_name = key
+ repo_name = remove_suffix(repo_name, '_README')
+ repo_name = remove_suffix(repo_name, '_RSS')
+ repo_name = remove_suffix(repo_name, '_ATOM')
+
+ # adds instance prefix
+ key, _prefix, _org_key = cls._get_key(key)
+ inv = cls._get_or_create_key(key, repo_name)
+
+ if inv and inv.cache_active is False:
+ return inv
+
+ @classmethod
+ def set_invalidate(cls, key=None, repo_name=None):
+ """
+ Mark this Cache key for invalidation, either by key or whole
+ cache sets based on repo_name
+
+ :param key:
+ """
+ if key:
+ key, _prefix, _org_key = cls._get_key(key)
+ inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
+ elif repo_name:
+ inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
+
+ log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
+ % (len(inv_objs), key, repo_name))
+ try:
+ for inv_obj in inv_objs:
+ inv_obj.cache_active = False
+ Session().add(inv_obj)
+ Session().commit()
+ except Exception:
+ log.error(traceback.format_exc())
+ Session().rollback()
+
+ @classmethod
+ def set_valid(cls, key):
+ """
+ Mark this cache key as active and currently cached
+
+ :param key:
+ """
+ inv_obj = cls.get_by_key(key)
+ inv_obj.cache_active = True
+ Session().add(inv_obj)
+ Session().commit()
+
+ @classmethod
+ def get_cache_map(cls):
+
+ class cachemapdict(dict):
+
+ def __init__(self, *args, **kwargs):
+ fixkey = kwargs.get('fixkey')
+ if fixkey:
+ del kwargs['fixkey']
+ self.fixkey = fixkey
+ super(cachemapdict, self).__init__(*args, **kwargs)
+
+ def __getattr__(self, name):
+ key = name
+ if self.fixkey:
+ key, _prefix, _org_key = cls._get_key(key)
+ if key in self.__dict__:
+ return self.__dict__[key]
+ else:
+ return self[key]
+
+ def __getitem__(self, key):
+ if self.fixkey:
+ key, _prefix, _org_key = cls._get_key(key)
+ try:
+ return super(cachemapdict, self).__getitem__(key)
+ except KeyError:
+ return
+
+ cache_map = cachemapdict(fixkey=True)
+ for obj in cls.query().all():
+ cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
+ return cache_map
+
+
+class ChangesetComment(Base, BaseModel):
+ __tablename__ = 'changeset_comments'
+ __table_args__ = (
+ Index('cc_revision_idx', 'revision'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'},
+ )
+ comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
+ repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+ revision = Column('revision', String(40), nullable=True)
+ pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+ line_no = Column('line_no', Unicode(10), nullable=True)
+ hl_lines = Column('hl_lines', Unicode(512), nullable=True)
+ f_path = Column('f_path', Unicode(1000), nullable=True)
+ user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
+ text = Column('text', UnicodeText(25000), nullable=False)
+ created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+ modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+
+ author = relationship('User', lazy='joined')
+ repo = relationship('Repository')
+ status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
+ pull_request = relationship('PullRequest', lazy='joined')
+
+ @classmethod
+ def get_users(cls, revision=None, pull_request_id=None):
+ """
+ Returns user associated with this ChangesetComment. ie those
+ who actually commented
+
+ :param cls:
+ :param revision:
+ """
+ q = Session().query(User)\
+ .join(ChangesetComment.author)
+ if revision:
+ q = q.filter(cls.revision == revision)
+ elif pull_request_id:
+ q = q.filter(cls.pull_request_id == pull_request_id)
+ return q.all()
+
+
+class ChangesetStatus(Base, BaseModel):
+ __tablename__ = 'changeset_statuses'
+ __table_args__ = (
+ Index('cs_revision_idx', 'revision'),
+ Index('cs_version_idx', 'version'),
+ UniqueConstraint('repo_id', 'revision', 'version'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'}
+ )
+ STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
+ STATUS_APPROVED = 'approved'
+ STATUS_REJECTED = 'rejected'
+ STATUS_UNDER_REVIEW = 'under_review'
+
+ STATUSES = [
+ (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
+ (STATUS_APPROVED, _("Approved")),
+ (STATUS_REJECTED, _("Rejected")),
+ (STATUS_UNDER_REVIEW, _("Under Review")),
+ ]
+
+ changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
+ repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+ user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+ revision = Column('revision', String(40), nullable=False)
+ status = Column('status', String(128), nullable=False, default=DEFAULT)
+ changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
+ modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
+ version = Column('version', Integer(), nullable=False, default=0)
+ pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+
+ author = relationship('User', lazy='joined')
+ repo = relationship('Repository')
+ comment = relationship('ChangesetComment', lazy='joined')
+ pull_request = relationship('PullRequest', lazy='joined')
+
+ def __unicode__(self):
+ return u"<%s('%s:%s')>" % (
+ self.__class__.__name__,
+ self.status, self.author
+ )
+
+ @classmethod
+ def get_status_lbl(cls, value):
+ return dict(cls.STATUSES).get(value)
+
+ @property
+ def status_lbl(self):
+ return ChangesetStatus.get_status_lbl(self.status)
+
+
+class PullRequest(Base, BaseModel):
+ __tablename__ = 'pull_requests'
+ __table_args__ = (
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'},
+ )
+
+ STATUS_NEW = u'new'
+ STATUS_OPEN = u'open'
+ STATUS_CLOSED = u'closed'
+
+ pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
+ title = Column('title', Unicode(256), nullable=True)
+ description = Column('description', UnicodeText(10240), nullable=True)
+ status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
+ created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+ updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+ user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
+ _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
+ org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+ org_ref = Column('org_ref', Unicode(256), nullable=False)
+ other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+ other_ref = Column('other_ref', Unicode(256), nullable=False)
+
+ @hybrid_property
+ def revisions(self):
+ return self._revisions.split(':')
+
+ @revisions.setter
+ def revisions(self, val):
+ self._revisions = ':'.join(val)
+
+ author = relationship('User', lazy='joined')
+ reviewers = relationship('PullRequestReviewers',
+ cascade="all, delete, delete-orphan")
+ org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
+ other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
+ statuses = relationship('ChangesetStatus')
+ comments = relationship('ChangesetComment',
+ cascade="all, delete, delete-orphan")
+
+ def is_closed(self):
+ return self.status == self.STATUS_CLOSED
+
+ def __json__(self):
+ return dict(
+ revisions=self.revisions
+ )
+
+
+class PullRequestReviewers(Base, BaseModel):
+ __tablename__ = 'pull_request_reviewers'
+ __table_args__ = (
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'},
+ )
+
+ def __init__(self, user=None, pull_request=None):
+ self.user = user
+ self.pull_request = pull_request
+
+ pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
+ pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
+ user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
+
+ user = relationship('User')
+ pull_request = relationship('PullRequest')
+
+
+class Notification(Base, BaseModel):
+ __tablename__ = 'notifications'
+ __table_args__ = (
+ Index('notification_type_idx', 'type'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'},
+ )
+
+ TYPE_CHANGESET_COMMENT = u'cs_comment'
+ TYPE_MESSAGE = u'message'
+ TYPE_MENTION = u'mention'
+ TYPE_REGISTRATION = u'registration'
+ TYPE_PULL_REQUEST = u'pull_request'
+ TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
+
+ notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
+ subject = Column('subject', Unicode(512), nullable=True)
+ body = Column('body', UnicodeText(50000), nullable=True)
+ created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
+ created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+ type_ = Column('type', Unicode(256))
+
+ created_by_user = relationship('User')
+ notifications_to_users = relationship('UserNotification', lazy='joined',
+ cascade="all, delete, delete-orphan")
+
+ @property
+ def recipients(self):
+ return [x.user for x in UserNotification.query()\
+ .filter(UserNotification.notification == self)\
+ .order_by(UserNotification.user_id.asc()).all()]
+
+ @classmethod
+ def create(cls, created_by, subject, body, recipients, type_=None):
+ if type_ is None:
+ type_ = Notification.TYPE_MESSAGE
+
+ notification = cls()
+ notification.created_by_user = created_by
+ notification.subject = subject
+ notification.body = body
+ notification.type_ = type_
+ notification.created_on = datetime.datetime.now()
+
+ for u in recipients:
+ assoc = UserNotification()
+ assoc.notification = notification
+ u.notifications.append(assoc)
+ Session().add(notification)
+ return notification
+
+ @property
+ def description(self):
+ from rhodecode.model.notification import NotificationModel
+ return NotificationModel().make_description(self)
+
+
+class UserNotification(Base, BaseModel):
+ __tablename__ = 'user_to_notification'
+ __table_args__ = (
+ UniqueConstraint('user_id', 'notification_id'),
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'}
+ )
+ user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
+ notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
+ read = Column('read', Boolean, default=False)
+ sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
+
+ user = relationship('User', lazy="joined")
+ notification = relationship('Notification', lazy="joined",
+ order_by=lambda: Notification.created_on.desc(),)
+
+ def mark_as_read(self):
+ self.read = True
+ Session().add(self)
+
+
+class DbMigrateVersion(Base, BaseModel):
+ __tablename__ = 'db_migrate_version'
+ __table_args__ = (
+ {'extend_existing': True, 'mysql_engine': 'InnoDB',
+ 'mysql_charset': 'utf8'},
+ )
+ repository_id = Column('repository_id', String(250), primary_key=True)
+ repository_path = Column('repository_path', Text)
+ version = Column('version', Integer)
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_5_2.py b/rhodecode/lib/dbmigrate/schema/db_1_5_2.py
new file mode 100644
index 00000000..fb883c3a
--- /dev/null
+++ b/rhodecode/lib/dbmigrate/schema/db_1_5_2.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+"""
+ rhodecode.model.db_1_4_0
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Database Models for RhodeCode <=1.5.X
+
+ :created_on: Apr 08, 2010
+ :author: marcink
+ :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
+ :license: GPLv3, see COPYING for more details.
+"""
+# 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/>.
+
+#TODO: replace that will db.py content after 1.6 Release
+
+from rhodecode.model.db import *
diff --git a/rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py b/rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py
index 83dd550b..35575d83 100644
--- a/rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py
+++ b/rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py
@@ -12,6 +12,7 @@ from rhodecode.lib.dbmigrate.migrate.changeset import *
from rhodecode.model.meta import Base
from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base
log = logging.getLogger(__name__)
@@ -49,12 +50,7 @@ def upgrade(migrate_engine):
tbl = ChangesetStatus.__table__
tbl.create()
- ## RESET COMPLETLY THE metadata for sqlalchemy to use the 1_3_0 Base
- Base = declarative_base()
- Base.metadata.clear()
- Base.metadata = MetaData()
- Base.metadata.bind = migrate_engine
- meta.Base = Base
+ _reset_base(migrate_engine)
#==========================================================================
# USERS TABLE
@@ -173,12 +169,7 @@ def upgrade(migrate_engine):
ForeignKey('pull_requests.pull_request_id'),
nullable=True)
pull_request_id.create(table=tbl)
- ## RESET COMPLETLY THE metadata for sqlalchemy back after using 1_3_0
- Base = declarative_base()
- Base.metadata.clear()
- Base.metadata = MetaData()
- Base.metadata.bind = migrate_engine
- meta.Base = Base
+ _reset_base(migrate_engine)
def downgrade(migrate_engine):
diff --git a/rhodecode/lib/dbmigrate/versions/008_version_1_5_0.py b/rhodecode/lib/dbmigrate/versions/008_version_1_5_0.py
index 746a4d30..178cd4b1 100644
--- a/rhodecode/lib/dbmigrate/versions/008_version_1_5_0.py
+++ b/rhodecode/lib/dbmigrate/versions/008_version_1_5_0.py
@@ -12,6 +12,7 @@ from rhodecode.lib.dbmigrate.migrate.changeset import *
from rhodecode.model.meta import Base
from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base
log = logging.getLogger(__name__)
@@ -24,6 +25,7 @@ def upgrade(migrate_engine):
#==========================================================================
# USER LOGS
#==========================================================================
+ _reset_base(migrate_engine)
from rhodecode.lib.dbmigrate.schema.db_1_5_0 import UserLog
tbl = UserLog.__table__
username = Column("username", String(255, convert_unicode=False,
diff --git a/rhodecode/lib/dbmigrate/versions/010_version_1_5_2.py b/rhodecode/lib/dbmigrate/versions/010_version_1_5_2.py
new file mode 100644
index 00000000..7dedf69a
--- /dev/null
+++ b/rhodecode/lib/dbmigrate/versions/010_version_1_5_2.py
@@ -0,0 +1,50 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+from rhodecode.lib.dbmigrate.versions import _reset_base
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+ """
+ Upgrade operations go here.
+ Don't create your own engine; bind migrate_engine to your metadata
+ """
+ _reset_base(migrate_engine)
+ #==========================================================================
+ # USER LOGS
+ #==========================================================================
+ from rhodecode.lib.dbmigrate.schema.db_1_5_2 import UserIpMap
+ tbl = UserIpMap.__table__
+ tbl.create()
+
+ #==========================================================================
+ # REPOSITORIES
+ #==========================================================================
+ from rhodecode.lib.dbmigrate.schema.db_1_5_2 import Repository
+ tbl = Repository.__table__
+ changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True)
+ # create username column
+ changeset_cache.create(table=tbl)
+
+ #fix cache data
+ repositories = Repository.getAll()
+ for entry in repositories:
+ entry.update_changeset_cache()
+
+
+def downgrade(migrate_engine):
+ meta = MetaData()
+ meta.bind = migrate_engine
diff --git a/rhodecode/lib/dbmigrate/versions/__init__.py b/rhodecode/lib/dbmigrate/versions/__init__.py
index 3ab0e613..af304849 100644
--- a/rhodecode/lib/dbmigrate/versions/__init__.py
+++ b/rhodecode/lib/dbmigrate/versions/__init__.py
@@ -22,3 +22,23 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper, joinedload
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+
+
+def _reset_base(migrate_engine):
+ ## RESET COMPLETLY THE metadata for sqlalchemy to use previous declared Base
+ Base = declarative_base()
+ Base.metadata.clear()
+ Base.metadata = MetaData()
+ Base.metadata.bind = migrate_engine
+ meta.Base = Base
diff --git a/rhodecode/lib/diffs.py b/rhodecode/lib/diffs.py
index 85050893..8f520c9a 100644
--- a/rhodecode/lib/diffs.py
+++ b/rhodecode/lib/diffs.py
@@ -583,7 +583,7 @@ class DiffProcessor(object):
#return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
def as_html(self, table_class='code-difftable', line_class='line',
- new_lineno_class='lineno old', old_lineno_class='lineno new',
+ old_lineno_class='lineno old', new_lineno_class='lineno new',
code_class='code', enable_comments=False, parsed_lines=None):
"""
Return given diff as html table with customized css classes
diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py
index a4a210c4..2183ae69 100644
--- a/rhodecode/lib/helpers.py
+++ b/rhodecode/lib/helpers.py
@@ -464,7 +464,7 @@ def desc_stylize(value):
'<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
- value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
+ value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
'<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
'<div class="metatag" tag="lang">\\2</div>', value)
@@ -1164,3 +1164,9 @@ def not_mapped_error(repo_name):
' it was created or renamed from the filesystem'
' please run the application again'
' in order to rescan repositories') % repo_name, category='error')
+
+
+def ip_range(ip_addr):
+ from rhodecode.model.db import UserIpMap
+ s, e = UserIpMap._get_ip_range(ip_addr)
+ return '%s - %s' % (s, e)
diff --git a/rhodecode/lib/ipaddr.py b/rhodecode/lib/ipaddr.py
new file mode 100644
index 00000000..72fda26f
--- /dev/null
+++ b/rhodecode/lib/ipaddr.py
@@ -0,0 +1,1901 @@
+# Copyright 2007 Google Inc.
+# Licensed to PSF under a Contributor Agreement.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+
+"""A fast, lightweight IPv4/IPv6 manipulation library in Python.
+
+This library is used to create/poke/manipulate IPv4 and IPv6 addresses
+and networks.
+
+"""
+
+__version__ = 'trunk'
+
+import struct
+
+IPV4LENGTH = 32
+IPV6LENGTH = 128
+
+
+class AddressValueError(ValueError):
+ """A Value Error related to the address."""
+
+
+class NetmaskValueError(ValueError):
+ """A Value Error related to the netmask."""
+
+
+def IPAddress(address, version=None):
+ """Take an IP string/int and return an object of the correct type.
+
+ Args:
+ address: A string or integer, the IP address. Either IPv4 or
+ IPv6 addresses may be supplied; integers less than 2**32 will
+ be considered to be IPv4 by default.
+ version: An Integer, 4 or 6. If set, don't try to automatically
+ determine what the IP address type is. important for things
+ like IPAddress(1), which could be IPv4, '0.0.0.1', or IPv6,
+ '::1'.
+
+ Returns:
+ An IPv4Address or IPv6Address object.
+
+ Raises:
+ ValueError: if the string passed isn't either a v4 or a v6
+ address.
+
+ """
+ if version:
+ if version == 4:
+ return IPv4Address(address)
+ elif version == 6:
+ return IPv6Address(address)
+
+ try:
+ return IPv4Address(address)
+ except (AddressValueError, NetmaskValueError):
+ pass
+
+ try:
+ return IPv6Address(address)
+ except (AddressValueError, NetmaskValueError):
+ pass
+
+ raise ValueError('%r does not appear to be an IPv4 or IPv6 address' %
+ address)
+
+
+def IPNetwork(address, version=None, strict=False):
+ """Take an IP string/int and return an object of the correct type.
+
+ Args:
+ address: A string or integer, the IP address. Either IPv4 or
+ IPv6 addresses may be supplied; integers less than 2**32 will
+ be considered to be IPv4 by default.
+ version: An Integer, if set, don't try to automatically
+ determine what the IP address type is. important for things
+ like IPNetwork(1), which could be IPv4, '0.0.0.1/32', or IPv6,
+ '::1/128'.
+
+ Returns:
+ An IPv4Network or IPv6Network object.
+
+ Raises:
+ ValueError: if the string passed isn't either a v4 or a v6
+ address. Or if a strict network was requested and a strict
+ network wasn't given.
+
+ """
+ if version:
+ if version == 4:
+ return IPv4Network(address, strict)
+ elif version == 6:
+ return IPv6Network(address, strict)
+
+ try:
+ return IPv4Network(address, strict)
+ except (AddressValueError, NetmaskValueError):
+ pass
+
+ try:
+ return IPv6Network(address, strict)
+ except (AddressValueError, NetmaskValueError):
+ pass
+
+ raise ValueError('%r does not appear to be an IPv4 or IPv6 network' %
+ address)
+
+
+def v4_int_to_packed(address):
+ """The binary representation of this address.
+
+ Args:
+ address: An integer representation of an IPv4 IP address.
+
+ Returns:
+ The binary representation of this address.
+
+ Raises:
+ ValueError: If the integer is too large to be an IPv4 IP
+ address.
+ """
+ if address > _BaseV4._ALL_ONES:
+ raise ValueError('Address too large for IPv4')
+ return Bytes(struct.pack('!I', address))
+
+
+def v6_int_to_packed(address):
+ """The binary representation of this address.
+
+ Args:
+ address: An integer representation of an IPv6 IP address.
+
+ Returns:
+ The binary representation of this address.
+ """
+ return Bytes(struct.pack('!QQ', address >> 64, address & (2 ** 64 - 1)))
+
+
+def _find_address_range(addresses):
+ """Find a sequence of addresses.
+
+ Args:
+ addresses: a list of IPv4 or IPv6 addresses.
+
+ Returns:
+ A tuple containing the first and last IP addresses in the sequence.
+
+ """
+ first = last = addresses[0]
+ for ip in addresses[1:]:
+ if ip._ip == last._ip + 1:
+ last = ip
+ else:
+ break
+ return (first, last)
+
+
+def _get_prefix_length(number1, number2, bits):
+ """Get the number of leading bits that are same for two numbers.
+
+ Args:
+ number1: an integer.
+ number2: another integer.
+ bits: the maximum number of bits to compare.
+
+ Returns:
+ The number of leading bits that are the same for two numbers.
+
+ """
+ for i in range(bits):
+ if number1 >> i == number2 >> i:
+ return bits - i
+ return 0
+
+
+def _count_righthand_zero_bits(number, bits):
+ """Count the number of zero bits on the right hand side.
+
+ Args:
+ number: an integer.
+ bits: maximum number of bits to count.
+
+ Returns:
+ The number of zero bits on the right hand side of the number.
+
+ """
+ if number == 0:
+ return bits
+ for i in range(bits):
+ if (number >> i) % 2:
+ return i
+
+
+def summarize_address_range(first, last):
+ """Summarize a network range given the first and last IP addresses.
+
+ Example:
+ >>> summarize_address_range(IPv4Address('1.1.1.0'),
+ IPv4Address('1.1.1.130'))
+ [IPv4Network('1.1.1.0/25'), IPv4Network('1.1.1.128/31'),
+ IPv4Network('1.1.1.130/32')]
+
+ Args:
+ first: the first IPv4Address or IPv6Address in the range.
+ last: the last IPv4Address or IPv6Address in the range.
+
+ Returns:
+ The address range collapsed to a list of IPv4Network's or
+ IPv6Network's.
+
+ Raise:
+ TypeError:
+ If the first and last objects are not IP addresses.
+ If the first and last objects are not the same version.
+ ValueError:
+ If the last object is not greater than the first.
+ If the version is not 4 or 6.
+
+ """
+ if not (isinstance(first, _BaseIP) and isinstance(last, _BaseIP)):
+ raise TypeError('first and last must be IP addresses, not networks')
+ if first.version != last.version:
+ raise TypeError("%s and %s are not of the same version" % (
+ str(first), str(last)))
+ if first > last:
+ raise ValueError('last IP address must be greater than first')
+
+ networks = []
+
+ if first.version == 4:
+ ip = IPv4Network
+ elif first.version == 6:
+ ip = IPv6Network
+ else:
+ raise ValueError('unknown IP version')
+
+ ip_bits = first._max_prefixlen
+ first_int = first._ip
+ last_int = last._ip
+ while first_int <= last_int:
+ nbits = _count_righthand_zero_bits(first_int, ip_bits)
+ current = None
+ while nbits >= 0:
+ addend = 2 ** nbits - 1
+ current = first_int + addend
+ nbits -= 1
+ if current <= last_int:
+ break
+ prefix = _get_prefix_length(first_int, current, ip_bits)
+ net = ip('%s/%d' % (str(first), prefix))
+ networks.append(net)
+ if current == ip._ALL_ONES:
+ break
+ first_int = current + 1
+ first = IPAddress(first_int, version=first._version)
+ return networks
+
+
+def _collapse_address_list_recursive(addresses):
+ """Loops through the addresses, collapsing concurrent netblocks.
+
+ Example:
+
+ ip1 = IPv4Network('1.1.0.0/24')
+ ip2 = IPv4Network('1.1.1.0/24')
+ ip3 = IPv4Network('1.1.2.0/24')
+ ip4 = IPv4Network('1.1.3.0/24')
+ ip5 = IPv4Network('1.1.4.0/24')
+ ip6 = IPv4Network('1.1.0.1/22')
+
+ _collapse_address_list_recursive([ip1, ip2, ip3, ip4, ip5, ip6]) ->
+ [IPv4Network('1.1.0.0/22'), IPv4Network('1.1.4.0/24')]
+
+ This shouldn't be called directly; it is called via
+ collapse_address_list([]).
+
+ Args:
+ addresses: A list of IPv4Network's or IPv6Network's
+
+ Returns:
+ A list of IPv4Network's or IPv6Network's depending on what we were
+ passed.
+
+ """
+ ret_array = []
+ optimized = False
+
+ for cur_addr in addresses:
+ if not ret_array:
+ ret_array.append(cur_addr)
+ continue
+ if cur_addr in ret_array[-1]:
+ optimized = True
+ elif cur_addr == ret_array[-1].supernet().subnet()[1]:
+ ret_array.append(ret_array.pop().supernet())
+ optimized = True
+ else:
+ ret_array.append(cur_addr)
+
+ if optimized:
+ return _collapse_address_list_recursive(ret_array)
+
+ return ret_array
+
+
+def collapse_address_list(addresses):
+ """Collapse a list of IP objects.
+
+ Example:
+ collapse_address_list([IPv4('1.1.0.0/24'), IPv4('1.1.1.0/24')]) ->
+ [IPv4('1.1.0.0/23')]
+
+ Args:
+ addresses: A list of IPv4Network or IPv6Network objects.
+
+ Returns:
+ A list of IPv4Network or IPv6Network objects depending on what we
+ were passed.
+
+ Raises:
+ TypeError: If passed a list of mixed version objects.
+
+ """
+ i = 0
+ addrs = []
+ ips = []
+ nets = []
+
+ # split IP addresses and networks
+ for ip in addresses:
+ if isinstance(ip, _BaseIP):
+ if ips and ips[-1]._version != ip._version:
+ raise TypeError("%s and %s are not of the same version" % (
+ str(ip), str(ips[-1])))
+ ips.append(ip)
+ elif ip._prefixlen == ip._max_prefixlen:
+ if ips and ips[-1]._version != ip._version:
+ raise TypeError("%s and %s are not of the same version" % (
+ str(ip), str(ips[-1])))
+ ips.append(ip.ip)
+ else:
+ if nets and nets[-1]._version != ip._version:
+ raise TypeError("%s and %s are not of the same version" % (
+ str(ip), str(nets[-1])))
+ nets.append(ip)
+
+ # sort and dedup
+ ips = sorted(set(ips))
+ nets = sorted(set(nets))
+
+ while i < len(ips):
+ (first, last) = _find_address_range(ips[i:])
+ i = ips.index(last) + 1
+ addrs.extend(summarize_address_range(first, last))
+
+ return _collapse_address_list_recursive(sorted(
+ addrs + nets, key=_BaseNet._get_networks_key))
+
+# backwards compatibility
+CollapseAddrList = collapse_address_list
+
+# We need to distinguish between the string and packed-bytes representations
+# of an IP address. For example, b'0::1' is the IPv4 address 48.58.58.49,
+# while '0::1' is an IPv6 address.
+#
+# In Python 3, the native 'bytes' type already provides this functionality,
+# so we use it directly. For earlier implementations where bytes is not a
+# distinct type, we create a subclass of str to serve as a tag.
+#
+# Usage example (Python 2):
+# ip = ipaddr.IPAddress(ipaddr.Bytes('xxxx'))
+#
+# Usage example (Python 3):
+# ip = ipaddr.IPAddress(b'xxxx')
+try:
+ if bytes is str:
+ raise TypeError("bytes is not a distinct type")
+ Bytes = bytes
+except (NameError, TypeError):
+ class Bytes(str):
+ def __repr__(self):
+ return 'Bytes(%s)' % str.__repr__(self)
+
+
+def get_mixed_type_key(obj):
+ """Return a key suitable for sorting between networks and addresses.
+
+ Address and Network objects are not sortable by default; they're
+ fundamentally different so the expression
+
+ IPv4Address('1.1.1.1') <= IPv4Network('1.1.1.1/24')
+
+ doesn't make any sense. There are some times however, where you may wish
+ to have ipaddr sort these for you anyway. If you need to do this, you
+ can use this function as the key= argument to sorted().
+
+ Args:
+ obj: either a Network or Address object.
+ Returns:
+ appropriate key.
+
+ """
+ if isinstance(obj, _BaseNet):
+ return obj._get_networks_key()
+ elif isinstance(obj, _BaseIP):
+ return obj._get_address_key()
+ return NotImplemented
+
+
+class _IPAddrBase(object):
+
+ """The mother class."""
+
+ def __index__(self):
+ return self._ip
+
+ def __int__(self):
+ return self._ip
+
+ def __hex__(self):
+ return hex(self._ip)
+
+ @property
+ def exploded(self):
+ """Return the longhand version of the IP address as a string."""
+ return self._explode_shorthand_ip_string()
+
+ @property
+ def compressed(self):
+ """Return the shorthand version of the IP address as a string."""
+ return str(self)
+
+
+class _BaseIP(_IPAddrBase):
+
+ """A generic IP object.
+
+ This IP class contains the version independent methods which are
+ used by single IP addresses.
+
+ """
+
+ def __eq__(self, other):
+ try:
+ return (self._ip == other._ip
+ and self._version == other._version)
+ except AttributeError:
+ return NotImplemented
+
+ def __ne__(self, other):
+ eq = self.__eq__(other)
+ if eq is NotImplemented:
+ return NotImplemented
+ return not eq
+
+ def __le__(self, other):
+ gt = self.__gt__(other)
+ if gt is NotImplemented:
+ return NotImplemented
+ return not gt
+
+ def __ge__(self, other):
+ lt = self.__lt__(other)
+ if lt is NotImplemented:
+ return NotImplemented
+ return not lt
+
+ def __lt__(self, other):
+ if self._version != other._version:
+ raise TypeError('%s and %s are not of the same version' % (
+ str(self), str(other)))
+ if not isinstance(other, _BaseIP):
+ raise TypeError('%s and %s are not of the same type' % (
+ str(self), str(other)))
+ if self._ip != other._ip:
+ return self._ip < other._ip
+ return False
+
+ def __gt__(self, other):
+ if self._version != other._version:
+ raise TypeError('%s and %s are not of the same version' % (
+ str(self), str(other)))
+ if not isinstance(other, _BaseIP):
+ raise TypeError('%s and %s are not of the same type' % (
+ str(self), str(other)))
+ if self._ip != other._ip:
+ return self._ip > other._ip
+ return False
+
+ # Shorthand for Integer addition and subtraction. This is not
+ # meant to ever support addition/subtraction of addresses.
+ def __add__(self, other):
+ if not isinstance(other, int):
+ return NotImplemented
+ return IPAddress(int(self) + other, version=self._version)
+
+ def __sub__(self, other):
+ if not isinstance(other, int):
+ return NotImplemented
+ return IPAddress(int(self) - other, version=self._version)
+
+ def __repr__(self):
+ return '%s(%r)' % (self.__class__.__name__, str(self))
+
+ def __str__(self):
+ return '%s' % self._string_from_ip_int(self._ip)
+
+ def __hash__(self):
+ return hash(hex(long(self._ip)))
+
+ def _get_address_key(self):
+ return (self._version, self)
+
+ @property
+ def version(self):
+ raise NotImplementedError('BaseIP has no version')
+
+
+class _BaseNet(_IPAddrBase):
+
+ """A generic IP object.
+
+ This IP class contains the version independent methods which are
+ used by networks.
+
+ """
+
+ def __init__(self, address):
+ self._cache = {}
+
+ def __repr__(self):
+ return '%s(%r)' % (self.__class__.__name__, str(self))
+
+ def iterhosts(self):
+ """Generate Iterator over usable hosts in a network.
+
+ This is like __iter__ except it doesn't return the network
+ or broadcast addresses.
+
+ """
+ cur = int(self.network) + 1
+ bcast = int(self.broadcast) - 1
+ while cur <= bcast:
+ cur += 1
+ yield IPAddress(cur - 1, version=self._version)
+
+ def __iter__(self):
+ cur = int(self.network)
+ bcast = int(self.broadcast)
+ while cur <= bcast:
+ cur += 1
+ yield IPAddress(cur - 1, version=self._version)
+
+ def __getitem__(self, n):
+ network = int(self.network)
+ broadcast = int(self.broadcast)
+ if n >= 0:
+ if network + n > broadcast:
+ raise IndexError
+ return IPAddress(network + n, version=self._version)
+ else:
+ n += 1
+ if broadcast + n < network:
+ raise IndexError
+ return IPAddress(broadcast + n, version=self._version)
+
+ def __lt__(self, other):
+ if self._version != other._version:
+ raise TypeError('%s and %s are not of the same version' % (
+ str(self), str(other)))
+ if not isinstance(other, _BaseNet):
+ raise TypeError('%s and %s are not of the same type' % (
+ str(self), str(other)))
+ if self.network != other.network:
+ return self.network < other.network
+ if self.netmask != other.netmask:
+ return self.netmask < other.netmask
+ return False
+
+ def __gt__(self, other):
+ if self._version != other._version:
+ raise TypeError('%s and %s are not of the same version' % (
+ str(self), str(other)))
+ if not isinstance(other, _BaseNet):
+ raise TypeError('%s and %s are not of the same type' % (
+ str(self), str(other)))
+ if self.network != other.network:
+ return self.network > other.network
+ if self.netmask != other.netmask:
+ return self.netmask > other.netmask
+ return False
+
+ def __le__(self, other):
+ gt = self.__gt__(other)
+ if gt is NotImplemented:
+ return NotImplemented
+ return not gt
+
+ def __ge__(self, other):
+ lt = self.__lt__(other)
+ if lt is NotImplemented:
+ return NotImplemented
+ return not lt
+
+ def __eq__(self, other):
+ try:
+ return (self._version == other._version
+ and self.network == other.network
+ and int(self.netmask) == int(other.netmask))
+ except AttributeError:
+ if isinstance(other, _BaseIP):
+ return (self._version == other._version
+ and self._ip == other._ip)
+
+ def __ne__(self, other):
+ eq = self.__eq__(other)
+ if eq is NotImplemented:
+ return NotImplemented
+ return not eq
+
+ def __str__(self):
+ return '%s/%s' % (str(self.ip),
+ str(self._prefixlen))
+
+ def __hash__(self):
+ return hash(int(self.network) ^ int(self.netmask))
+
+ def __contains__(self, other):
+ # always false if one is v4 and the other is v6.
+ if self._version != other._version:
+ return False
+ # dealing with another network.
+ if isinstance(other, _BaseNet):
+ return (self.network <= other.network and
+ self.broadcast >= other.broadcast)
+ # dealing with another address
+ else:
+ return (int(self.network) <= int(other._ip) <=
+ int(self.broadcast))
+
+ def overlaps(self, other):
+ """Tell if self is partly contained in other."""
+ return self.network in other or self.broadcast in other or (
+ other.network in self or other.broadcast in self)
+
+ @property
+ def network(self):
+ x = self._cache.get('network')
+ if x is None:
+ x = IPAddress(self._ip & int(self.netmask), version=self._version)
+ self._cache['network'] = x
+ return x
+
+ @property
+ def broadcast(self):
+ x = self._cache.get('broadcast')
+ if x is None:
+ x = IPAddress(self._ip | int(self.hostmask), version=self._version)
+ self._cache['broadcast'] = x
+ return x
+
+ @property
+ def hostmask(self):
+ x = self._cache.get('hostmask')
+ if x is None:
+ x = IPAddress(int(self.netmask) ^ self._ALL_ONES,
+ version=self._version)
+ self._cache['hostmask'] = x
+ return x
+
+ @property
+ def with_prefixlen(self):
+ return '%s/%d' % (str(self.ip), self._prefixlen)
+
+ @property
+ def with_netmask(self):
+ return '%s/%s' % (str(self.ip), str(self.netmask))
+
+ @property
+ def with_hostmask(self):
+ return '%s/%s' % (str(self.ip), str(self.hostmask))
+
+ @property
+ def numhosts(self):
+ """Number of hosts in the current subnet."""
+ return int(self.broadcast) - int(self.network) + 1
+
+ @property
+ def version(self):
+ raise NotImplementedError('BaseNet has no version')
+
+ @property
+ def prefixlen(self):
+ return self._prefixlen
+
+ def address_exclude(self, other):
+ """Remove an address from a larger block.
+
+ For example:
+
+ addr1 = IPNetwork('10.1.1.0/24')
+ addr2 = IPNetwork('10.1.1.0/26')
+ addr1.address_exclude(addr2) =
+ [IPNetwork('10.1.1.64/26'), IPNetwork('10.1.1.128/25')]
+
+ or IPv6:
+
+ addr1 = IPNetwork('::1/32')
+ addr2 = IPNetwork('::1/128')
+ addr1.address_exclude(addr2) = [IPNetwork('::0/128'),
+ IPNetwork('::2/127'),
+ IPNetwork('::4/126'),
+ IPNetwork('::8/125'),
+ ...
+ IPNetwork('0:0:8000::/33')]
+
+ Args:
+ other: An IPvXNetwork object of the same type.
+
+ Returns:
+ A sorted list of IPvXNetwork objects addresses which is self
+ minus other.
+
+ Raises:
+ TypeError: If self and other are of difffering address
+ versions, or if other is not a network object.
+ ValueError: If other is not completely contained by self.
+
+ """
+ if not self._version == other._version:
+ raise TypeError("%s and %s are not of the same version" % (
+ str(self), str(other)))
+
+ if not isinstance(other, _BaseNet):
+ raise TypeError("%s is not a network object" % str(other))
+
+ if other not in self:
+ raise ValueError('%s not contained in %s' % (str(other),
+ str(self)))
+ if other == self:
+ return []
+
+ ret_addrs = []
+
+ # Make sure we're comparing the network of other.
+ other = IPNetwork('%s/%s' % (str(other.network), str(other.prefixlen)),
+ version=other._version)
+
+ s1, s2 = self.subnet()
+ while s1 != other and s2 != other:
+ if other in s1:
+ ret_addrs.append(s2)
+ s1, s2 = s1.subnet()
+ elif other in s2:
+ ret_addrs.append(s1)
+ s1, s2 = s2.subnet()
+ else:
+ # If we got here, there's a bug somewhere.
+ assert True == False, ('Error performing exclusion: '
+ 's1: %s s2: %s other: %s' %
+ (str(s1), str(s2), str(other)))
+ if s1 == other:
+ ret_addrs.append(s2)
+ elif s2 == other:
+ ret_addrs.append(s1)
+ else:
+ # If we got here, there's a bug somewhere.
+ assert True == False, ('Error performing exclusion: '
+ 's1: %s s2: %s other: %s' %
+ (str(s1), str(s2), str(other)))
+
+ return sorted(ret_addrs, key=_BaseNet._get_networks_key)
+
+ def compare_networks(self, other):
+ """Compare two IP objects.
+
+ This is only concerned about the comparison of the integer
+ representation of the network addresses. This means that the
+ host bits aren't considered at all in this method. If you want
+ to compare host bits, you can easily enough do a
+ 'HostA._ip < HostB._ip'
+
+ Args:
+ other: An IP object.
+
+ Returns:
+ If the IP versions of self and other are the same, returns:
+
+ -1 if self < other:
+ eg: IPv4('1.1.1.0/24') < IPv4('1.1.2.0/24')
+ IPv6('1080::200C:417A') < IPv6('1080::200B:417B')
+ 0 if self == other
+ eg: IPv4('1.1.1.1/24') == IPv4('1.1.1.2/24')
+ IPv6('1080::200C:417A/96') == IPv6('1080::200C:417B/96')
+ 1 if self > other
+ eg: IPv4('1.1.1.0/24') > IPv4('1.1.0.0/24')
+ IPv6('1080::1:200C:417A/112') >
+ IPv6('1080::0:200C:417A/112')
+
+ If the IP versions of self and other are different, returns:
+
+ -1 if self._version < other._version
+ eg: IPv4('10.0.0.1/24') < IPv6('::1/128')
+ 1 if self._version > other._version
+ eg: IPv6('::1/128') > IPv4('255.255.255.0/24')
+
+ """
+ if self._version < other._version:
+ return -1
+ if self._version > other._version:
+ return 1
+ # self._version == other._version below here:
+ if self.network < other.network:
+ return -1
+ if self.network > other.network:
+ return 1
+ # self.network == other.network below here:
+ if self.netmask < other.netmask:
+ return -1
+ if self.netmask > other.netmask:
+ return 1
+ # self.network == other.network and self.netmask == other.netmask
+ return 0
+
+ def _get_networks_key(self):
+ """Network-only key function.
+
+ Returns an object that identifies this address' network and
+ netmask. This function is a suitable "key" argument for sorted()
+ and list.sort().
+
+ """
+ return (self._version, self.network, self.netmask)
+
+ def _ip_int_from_prefix(self, prefixlen=None):
+ """Turn the prefix length netmask into a int for comparison.
+
+ Args:
+ prefixlen: An integer, the prefix length.
+
+ Returns:
+ An integer.
+
+ """
+ if not prefixlen and prefixlen != 0:
+ prefixlen = self._prefixlen
+ return self._ALL_ONES ^ (self._ALL_ONES >> prefixlen)
+
+ def _prefix_from_ip_int(self, ip_int, mask=32):
+ """Return prefix length from the decimal netmask.
+
+ Args:
+ ip_int: An integer, the IP address.
+ mask: The netmask. Defaults to 32.
+
+ Returns:
+ An integer, the prefix length.
+
+ """
+ while mask:
+ if ip_int & 1 == 1:
+ break
+ ip_int >>= 1
+ mask -= 1
+
+ return mask
+
+ def _ip_string_from_prefix(self, prefixlen=None):
+ """Turn a prefix length into a dotted decimal string.
+
+ Args:
+ prefixlen: An integer, the netmask prefix length.
+
+ Returns:
+ A string, the dotted decimal netmask string.
+
+ """
+ if not prefixlen:
+ prefixlen = self._prefixlen
+ return self._string_from_ip_int(self._ip_int_from_prefix(prefixlen))
+
+ def iter_subnets(self, prefixlen_diff=1, new_prefix=None):
+ """The subnets which join to make the current subnet.
+
+ In the case that self contains only one IP
+ (self._prefixlen == 32 for IPv4 or self._prefixlen == 128
+ for IPv6), return a list with just ourself.
+
+ Args:
+ prefixlen_diff: An integer, the amount the prefix length
+ should be increased by. This should not be set if
+ new_prefix is also set.
+ new_prefix: The desired new prefix length. This must be a
+ larger number (smaller prefix) than the existing prefix.
+ This should not be set if prefixlen_diff is also set.
+
+ Returns:
+ An iterator of IPv(4|6) objects.
+
+ Raises:
+ ValueError: The prefixlen_diff is too small or too large.
+ OR
+ prefixlen_diff and new_prefix are both set or new_prefix
+ is a smaller number than the current prefix (smaller
+ number means a larger network)
+
+ """
+ if self._prefixlen == self._max_prefixlen:
+ yield self
+ return
+
+ if new_prefix is not None:
+ if new_prefix < self._prefixlen:
+ raise ValueError('new prefix must be longer')
+ if prefixlen_diff != 1:
+ raise ValueError('cannot set prefixlen_diff and new_prefix')
+ prefixlen_diff = new_prefix - self._prefixlen
+
+ if prefixlen_diff < 0:
+ raise ValueError('prefix length diff must be > 0')
+ new_prefixlen = self._prefixlen + prefixlen_diff
+
+ if not self._is_valid_netmask(str(new_prefixlen)):
+ raise ValueError(
+ 'prefix length diff %d is invalid for netblock %s' % (
+ new_prefixlen, str(self)))
+
+ first = IPNetwork('%s/%s' % (str(self.network),
+ str(self._prefixlen + prefixlen_diff)),
+ version=self._version)
+
+ yield first
+ current = first
+ while True:
+ broadcast = current.broadcast
+ if broadcast == self.broadcast:
+ return
+ new_addr = IPAddress(int(broadcast) + 1, version=self._version)
+ current = IPNetwork('%s/%s' % (str(new_addr), str(new_prefixlen)),
+ version=self._version)
+
+ yield current
+
+ def masked(self):
+ """Return the network object with the host bits masked out."""
+ return IPNetwork('%s/%d' % (self.network, self._prefixlen),
+ version=self._version)
+
+ def subnet(self, prefixlen_diff=1, new_prefix=None):
+ """Return a list of subnets, rather than an iterator."""
+ return list(self.iter_subnets(prefixlen_diff, new_prefix))
+
+ def supernet(self, prefixlen_diff=1, new_prefix=None):
+ """The supernet containing the current network.
+
+ Args:
+ prefixlen_diff: An integer, the amount the prefix length of
+ the network should be decreased by. For example, given a
+ /24 network and a prefixlen_diff of 3, a supernet with a
+ /21 netmask is returned.
+
+ Returns:
+ An IPv4 network object.
+
+ Raises:
+ ValueError: If self.prefixlen - prefixlen_diff < 0. I.e., you have a
+ negative prefix length.
+ OR
+ If prefixlen_diff and new_prefix are both set or new_prefix is a
+ larger number than the current prefix (larger number means a
+ smaller network)
+
+ """
+ if self._prefixlen == 0:
+ return self
+
+ if new_prefix is not None:
+ if new_prefix > self._prefixlen:
+ raise ValueError('new prefix must be shorter')
+ if prefixlen_diff != 1:
+ raise ValueError('cannot set prefixlen_diff and new_prefix')
+ prefixlen_diff = self._prefixlen - new_prefix
+
+ if self.prefixlen - prefixlen_diff < 0:
+ raise ValueError(
+ 'current prefixlen is %d, cannot have a prefixlen_diff of %d' %
+ (self.prefixlen, prefixlen_diff))
+ return IPNetwork('%s/%s' % (str(self.network),
+ str(self.prefixlen - prefixlen_diff)),
+ version=self._version)
+
+ # backwards compatibility
+ Subnet = subnet
+ Supernet = supernet
+ AddressExclude = address_exclude
+ CompareNetworks = compare_networks
+ Contains = __contains__
+
+
+class _BaseV4(object):
+
+ """Base IPv4 object.
+
+ The following methods are used by IPv4 objects in both single IP
+ addresses and networks.
+
+ """
+
+ # Equivalent to 255.255.255.255 or 32 bits of 1's.
+ _ALL_ONES = (2 ** IPV4LENGTH) - 1
+ _DECIMAL_DIGITS = frozenset('0123456789')
+
+ def __init__(self, address):
+ self._version = 4
+ self._max_prefixlen = IPV4LENGTH
+
+ def _explode_shorthand_ip_string(self):
+ return str(self)
+
+ def _ip_int_from_string(self, ip_str):
+ """Turn the given IP string into an integer for comparison.
+
+ Args:
+ ip_str: A string, the IP ip_str.
+
+ Returns:
+ The IP ip_str as an integer.
+
+ Raises:
+ AddressValueError: if ip_str isn't a valid IPv4 Address.
+
+ """
+ octets = ip_str.split('.')
+ if len(octets) != 4:
+ raise AddressValueError(ip_str)
+
+ packed_ip = 0
+ for oc in octets:
+ try:
+ packed_ip = (packed_ip << 8) | self._parse_octet(oc)
+ except ValueError:
+ raise AddressValueError(ip_str)
+ return packed_ip
+
+ def _parse_octet(self, octet_str):
+ """Convert a decimal octet into an integer.
+
+ Args:
+ octet_str: A string, the number to parse.
+
+ Returns:
+ The octet as an integer.
+
+ Raises:
+ ValueError: if the octet isn't strictly a decimal from [0..255].
+
+ """
+ # Whitelist the characters, since int() allows a lot of bizarre stuff.
+ if not self._DECIMAL_DIGITS.issuperset(octet_str):
+ raise ValueError
+ octet_int = int(octet_str, 10)
+ # Disallow leading zeroes, because no clear standard exists on
+ # whether these should be interpreted as decimal or octal.
+ if octet_int > 255 or (octet_str[0] == '0' and len(octet_str) > 1):
+ raise ValueError
+ return octet_int
+
+ def _string_from_ip_int(self, ip_int):
+ """Turns a 32-bit integer into dotted decimal notation.
+
+ Args:
+ ip_int: An integer, the IP address.
+
+ Returns:
+ The IP address as a string in dotted decimal notation.
+
+ """
+ octets = []
+ for _ in xrange(4):
+ octets.insert(0, str(ip_int & 0xFF))
+ ip_int >>= 8
+ return '.'.join(octets)
+
+ @property
+ def max_prefixlen(self):
+ return self._max_prefixlen
+
+ @property
+ def packed(self):
+ """The binary representation of this address."""
+ return v4_int_to_packed(self._ip)
+
+ @property
+ def version(self):
+ return self._version
+
+ @property
+ def is_reserved(self):
+ """Test if the address is otherwise IETF reserved.
+
+ Returns:
+ A boolean, True if the address is within the
+ reserved IPv4 Network range.
+
+ """
+ return self in IPv4Network('240.0.0.0/4')
+
+ @property
+ def is_private(self):
+ """Test if this address is allocated for private networks.
+
+ Returns:
+ A boolean, True if the address is reserved per RFC 1918.
+
+ """
+ return (self in IPv4Network('10.0.0.0/8') or
+ self in IPv4Network('172.16.0.0/12') or
+ self in IPv4Network('192.168.0.0/16'))
+
+ @property
+ def is_multicast(self):
+ """Test if the address is reserved for multicast use.
+
+ Returns:
+ A boolean, True if the address is multicast.
+ See RFC 3171 for details.
+
+ """
+ return self in IPv4Network('224.0.0.0/4')
+
+ @property
+ def is_unspecified(self):
+ """Test if the address is unspecified.
+
+ Returns:
+ A boolean, True if this is the unspecified address as defined in
+ RFC 5735 3.
+
+ """
+ return self in IPv4Network('0.0.0.0')
+
+ @property
+ def is_loopback(self):
+ """Test if the address is a loopback address.
+
+ Returns:
+ A boolean, True if the address is a loopback per RFC 3330.
+
+ """
+ return self in IPv4Network('127.0.0.0/8')
+
+ @property
+ def is_link_local(self):
+ """Test if the address is reserved for link-local.
+
+ Returns:
+ A boolean, True if the address is link-local per RFC 3927.
+
+ """
+ return self in IPv4Network('169.254.0.0/16')
+
+
+class IPv4Address(_BaseV4, _BaseIP):
+
+ """Represent and manipulate single IPv4 Addresses."""
+
+ def __init__(self, address):
+
+ """
+ Args:
+ address: A string or integer representing the IP
+ '192.168.1.1'
+
+ Additionally, an integer can be passed, so
+ IPv4Address('192.168.1.1') == IPv4Address(3232235777).
+ or, more generally
+ IPv4Address(int(IPv4Address('192.168.1.1'))) ==
+ IPv4Address('192.168.1.1')
+
+ Raises:
+ AddressValueError: If ipaddr isn't a valid IPv4 address.
+
+ """
+ _BaseV4.__init__(self, address)
+
+ # Efficient constructor from integer.
+ if isinstance(address, (int, long)):
+ self._ip = address
+ if address < 0 or address > self._ALL_ONES:
+ raise AddressValueError(address)
+ return
+
+ # Constructing from a packed address
+ if isinstance(address, Bytes):
+ try:
+ self._ip, = struct.unpack('!I', address)
+ except struct.error:
+ raise AddressValueError(address) # Wrong length.
+ return
+
+ # Assume input argument to be string or any object representation
+ # which converts into a formatted IP string.
+ addr_str = str(address)
+ self._ip = self._ip_int_from_string(addr_str)
+
+
+class IPv4Network(_BaseV4, _BaseNet):
+
+ """This class represents and manipulates 32-bit IPv4 networks.
+
+ Attributes: [examples for IPv4Network('1.2.3.4/27')]
+ ._ip: 16909060
+ .ip: IPv4Address('1.2.3.4')
+ .network: IPv4Address('1.2.3.0')
+ .hostmask: IPv4Address('0.0.0.31')
+ .broadcast: IPv4Address('1.2.3.31')
+ .netmask: IPv4Address('255.255.255.224')
+ .prefixlen: 27
+
+ """
+
+ # the valid octets for host and netmasks. only useful for IPv4.
+ _valid_mask_octets = set((255, 254, 252, 248, 240, 224, 192, 128, 0))
+
+ def __init__(self, address, strict=False):
+ """Instantiate a new IPv4 network object.
+
+ Args:
+ address: A string or integer representing the IP [& network].
+ '192.168.1.1/24'
+ '192.168.1.1/255.255.255.0'
+ '192.168.1.1/0.0.0.255'
+ are all functionally the same in IPv4. Similarly,
+ '192.168.1.1'
+ '192.168.1.1/255.255.255.255'
+ '192.168.1.1/32'
+ are also functionaly equivalent. That is to say, failing to
+ provide a subnetmask will create an object with a mask of /32.
+
+ If the mask (portion after the / in the argument) is given in
+ dotted quad form, it is treated as a netmask if it starts with a
+ non-zero field (e.g. /255.0.0.0 == /8) and as a hostmask if it
+ starts with a zero field (e.g. 0.255.255.255 == /8), with the
+ single exception of an all-zero mask which is treated as a
+ netmask == /0. If no mask is given, a default of /32 is used.
+
+ Additionally, an integer can be passed, so
+ IPv4Network('192.168.1.1') == IPv4Network(3232235777).
+ or, more generally
+ IPv4Network(int(IPv4Network('192.168.1.1'))) ==
+ IPv4Network('192.168.1.1')
+
+ strict: A boolean. If true, ensure that we have been passed
+ A true network address, eg, 192.168.1.0/24 and not an
+ IP address on a network, eg, 192.168.1.1/24.
+
+ Raises:
+ AddressValueError: If ipaddr isn't a valid IPv4 address.
+ NetmaskValueError: If the netmask isn't valid for
+ an IPv4 address.
+ ValueError: If strict was True and a network address was not
+ supplied.
+
+ """
+ _BaseNet.__init__(self, address)
+ _BaseV4.__init__(self, address)
+
+ # Constructing from an integer or packed bytes.
+ if isinstance(address, (int, long, Bytes)):
+ self.ip = IPv4Address(address)
+ self._ip = self.ip._ip
+ self._prefixlen = self._max_prefixlen
+ self.netmask = IPv4Address(self._ALL_ONES)
+ return
+
+ # Assume input argument to be string or any object representation
+ # which converts into a formatted IP prefix string.
+ addr = str(address).split('/')
+
+ if len(addr) > 2:
+ raise AddressValueError(address)
+
+ self._ip = self._ip_int_from_string(addr[0])
+ self.ip = IPv4Address(self._ip)
+
+ if len(addr) == 2:
+ mask = addr[1].split('.')
+ if len(mask) == 4:
+ # We have dotted decimal netmask.
+ if self._is_valid_netmask(addr[1]):
+ self.netmask = IPv4Address(self._ip_int_from_string(
+ addr[1]))
+ elif self._is_hostmask(addr[1]):
+ self.netmask = IPv4Address(
+ self._ip_int_from_string(addr[1]) ^ self._ALL_ONES)
+ else:
+ raise NetmaskValueError('%s is not a valid netmask'
+ % addr[1])
+
+ self._prefixlen = self._prefix_from_ip_int(int(self.netmask))
+ else:
+ # We have a netmask in prefix length form.
+ if not self._is_valid_netmask(addr[1]):
+ raise NetmaskValueError(addr[1])
+ self._prefixlen = int(addr[1])
+ self.netmask = IPv4Address(self._ip_int_from_prefix(
+ self._prefixlen))
+ else:
+ self._prefixlen = self._max_prefixlen
+ self.netmask = IPv4Address(self._ip_int_from_prefix(
+ self._prefixlen))
+ if strict:
+ if self.ip != self.network:
+ raise ValueError('%s has host bits set' %
+ self.ip)
+ if self._prefixlen == (self._max_prefixlen - 1):
+ self.iterhosts = self.__iter__
+
+ def _is_hostmask(self, ip_str):
+ """Test if the IP string is a hostmask (rather than a netmask).
+
+ Args:
+ ip_str: A string, the potential hostmask.
+
+ Returns:
+ A boolean, True if the IP string is a hostmask.
+
+ """
+ bits = ip_str.split('.')
+ try:
+ parts = [int(x) for x in bits if int(x) in self._valid_mask_octets]
+ except ValueError:
+ return False
+ if len(parts) != len(bits):
+ return False
+ if parts[0] < parts[-1]:
+ return True
+ return False
+
+ def _is_valid_netmask(self, netmask):
+ """Verify that the netmask is valid.
+
+ Args:
+ netmask: A string, either a prefix or dotted decimal
+ netmask.
+
+ Returns:
+ A boolean, True if the prefix represents a valid IPv4
+ netmask.
+
+ """
+ mask = netmask.split('.')
+ if len(mask) == 4:
+ if [x for x in mask if int(x) not in self._valid_mask_octets]:
+ return False
+ if [y for idx, y in enumerate(mask) if idx > 0 and
+ y > mask[idx - 1]]:
+ return False
+ return True
+ try:
+ netmask = int(netmask)
+ except ValueError:
+ return False
+ return 0 <= netmask <= self._max_prefixlen
+
+ # backwards compatibility
+ IsRFC1918 = lambda self: self.is_private
+ IsMulticast = lambda self: self.is_multicast
+ IsLoopback = lambda self: self.is_loopback
+ IsLinkLocal = lambda self: self.is_link_local
+
+
+class _BaseV6(object):
+
+ """Base IPv6 object.
+
+ The following methods are used by IPv6 objects in both single IP
+ addresses and networks.
+
+ """
+
+ _ALL_ONES = (2 ** IPV6LENGTH) - 1
+ _HEXTET_COUNT = 8
+ _HEX_DIGITS = frozenset('0123456789ABCDEFabcdef')
+
+ def __init__(self, address):
+ self._version = 6
+ self._max_prefixlen = IPV6LENGTH
+
+ def _ip_int_from_string(self, ip_str):
+ """Turn an IPv6 ip_str into an integer.
+
+ Args:
+ ip_str: A string, the IPv6 ip_str.
+
+ Returns:
+ A long, the IPv6 ip_str.
+
+ Raises:
+ AddressValueError: if ip_str isn't a valid IPv6 Address.
+
+ """
+ parts = ip_str.split(':')
+
+ # An IPv6 address needs at least 2 colons (3 parts).
+ if len(parts) < 3:
+ raise AddressValueError(ip_str)
+
+ # If the address has an IPv4-style suffix, convert it to hexadecimal.
+ if '.' in parts[-1]:
+ ipv4_int = IPv4Address(parts.pop())._ip
+ parts.append('%x' % ((ipv4_int >> 16) & 0xFFFF))
+ parts.append('%x' % (ipv4_int & 0xFFFF))
+
+ # An IPv6 address can't have more than 8 colons (9 parts).
+ if len(parts) > self._HEXTET_COUNT + 1:
+ raise AddressValueError(ip_str)
+
+ # Disregarding the endpoints, find '::' with nothing in between.
+ # This indicates that a run of zeroes has been skipped.
+ try:
+ skip_index, = (
+ [i for i in xrange(1, len(parts) - 1) if not parts[i]] or
+ [None])
+ except ValueError:
+ # Can't have more than one '::'
+ raise AddressValueError(ip_str)
+
+ # parts_hi is the number of parts to copy from above/before the '::'
+ # parts_lo is the number of parts to copy from below/after the '::'
+ if skip_index is not None:
+ # If we found a '::', then check if it also covers the endpoints.
+ parts_hi = skip_index
+ parts_lo = len(parts) - skip_index - 1
+ if not parts[0]:
+ parts_hi -= 1
+ if parts_hi:
+ raise AddressValueError(ip_str) # ^: requires ^::
+ if not parts[-1]:
+ parts_lo -= 1
+ if parts_lo:
+ raise AddressValueError(ip_str) # :$ requires ::$
+ parts_skipped = self._HEXTET_COUNT - (parts_hi + parts_lo)
+ if parts_skipped < 1:
+ raise AddressValueError(ip_str)
+ else:
+ # Otherwise, allocate the entire address to parts_hi. The endpoints
+ # could still be empty, but _parse_hextet() will check for that.
+ if len(parts) != self._HEXTET_COUNT:
+ raise AddressValueError(ip_str)
+ parts_hi = len(parts)
+ parts_lo = 0
+ parts_skipped = 0
+
+ try:
+ # Now, parse the hextets into a 128-bit integer.
+ ip_int = 0L
+ for i in xrange(parts_hi):
+ ip_int <<= 16
+ ip_int |= self._parse_hextet(parts[i])
+ ip_int <<= 16 * parts_skipped
+ for i in xrange(-parts_lo, 0):
+ ip_int <<= 16
+ ip_int |= self._parse_hextet(parts[i])
+ return ip_int
+ except ValueError:
+ raise AddressValueError(ip_str)
+
+ def _parse_hextet(self, hextet_str):
+ """Convert an IPv6 hextet string into an integer.
+
+ Args:
+ hextet_str: A string, the number to parse.
+
+ Returns:
+ The hextet as an integer.
+
+ Raises:
+ ValueError: if the input isn't strictly a hex number from [0..FFFF].
+
+ """
+ # Whitelist the characters, since int() allows a lot of bizarre stuff.
+ if not self._HEX_DIGITS.issuperset(hextet_str):
+ raise ValueError
+ if len(hextet_str) > 4:
+ raise ValueError
+ hextet_int = int(hextet_str, 16)
+ if hextet_int > 0xFFFF:
+ raise ValueError
+ return hextet_int
+
+ def _compress_hextets(self, hextets):
+ """Compresses a list of hextets.
+
+ Compresses a list of strings, replacing the longest continuous
+ sequence of "0" in the list with "" and adding empty strings at
+ the beginning or at the end of the string such that subsequently
+ calling ":".join(hextets) will produce the compressed version of
+ the IPv6 address.
+
+ Args:
+ hextets: A list of strings, the hextets to compress.
+
+ Returns:
+ A list of strings.
+
+ """
+ best_doublecolon_start = -1
+ best_doublecolon_len = 0
+ doublecolon_start = -1
+ doublecolon_len = 0
+ for index in range(len(hextets)):
+ if hextets[index] == '0':
+ doublecolon_len += 1
+ if doublecolon_start == -1:
+ # Start of a sequence of zeros.
+ doublecolon_start = index
+ if doublecolon_len > best_doublecolon_len:
+ # This is the longest sequence of zeros so far.
+ best_doublecolon_len = doublecolon_len
+ best_doublecolon_start = doublecolon_start
+ else:
+ doublecolon_len = 0
+ doublecolon_start = -1
+
+ if best_doublecolon_len > 1:
+ best_doublecolon_end = (best_doublecolon_start +
+ best_doublecolon_len)
+ # For zeros at the end of the address.
+ if best_doublecolon_end == len(hextets):
+ hextets += ['']
+ hextets[best_doublecolon_start:best_doublecolon_end] = ['']
+ # For zeros at the beginning of the address.
+ if best_doublecolon_start == 0:
+ hextets = [''] + hextets
+
+ return hextets
+
+ def _string_from_ip_int(self, ip_int=None):
+ """Turns a 128-bit integer into hexadecimal notation.
+
+ Args:
+ ip_int: An integer, the IP address.
+
+ Returns:
+ A string, the hexadecimal representation of the address.
+
+ Raises:
+ ValueError: The address is bigger than 128 bits of all ones.
+
+ """
+ if not ip_int and ip_int != 0:
+ ip_int = int(self._ip)
+
+ if ip_int > self._ALL_ONES:
+ raise ValueError('IPv6 address is too large')
+
+ hex_str = '%032x' % ip_int
+ hextets = []
+ for x in range(0, 32, 4):
+ hextets.append('%x' % int(hex_str[x:x + 4], 16))
+
+ hextets = self._compress_hextets(hextets)
+ return ':'.join(hextets)
+
+ def _explode_shorthand_ip_string(self):
+ """Expand a shortened IPv6 address.
+
+ Args:
+ ip_str: A string, the IPv6 address.
+
+ Returns:
+ A string, the expanded IPv6 address.
+
+ """
+ if isinstance(self, _BaseNet):
+ ip_str = str(self.ip)
+ else:
+ ip_str = str(self)
+
+ ip_int = self._ip_int_from_string(ip_str)
+ parts = []
+ for i in xrange(self._HEXTET_COUNT):
+ parts.append('%04x' % (ip_int & 0xFFFF))
+ ip_int >>= 16
+ parts.reverse()
+ if isinstance(self, _BaseNet):
+ return '%s/%d' % (':'.join(parts), self.prefixlen)
+ return ':'.join(parts)
+
+ @property
+ def max_prefixlen(self):
+ return self._max_prefixlen
+
+ @property
+ def packed(self):
+ """The binary representation of this address."""
+ return v6_int_to_packed(self._ip)
+
+ @property
+ def version(self):
+ return self._version
+
+ @property
+ def is_multicast(self):
+ """Test if the address is reserved for multicast use.
+
+ Returns:
+ A boolean, True if the address is a multicast address.
+ See RFC 2373 2.7 for details.
+
+ """
+ return self in IPv6Network('ff00::/8')
+
+ @property
+ def is_reserved(self):
+ """Test if the address is otherwise IETF reserved.
+
+ Returns:
+ A boolean, True if the address is within one of the
+ reserved IPv6 Network ranges.
+
+ """
+ return (self in IPv6Network('::/8') or
+ self in IPv6Network('100::/8') or
+ self in IPv6Network('200::/7') or
+ self in IPv6Network('400::/6') or
+ self in IPv6Network('800::/5') or
+ self in IPv6Network('1000::/4') or
+ self in IPv6Network('4000::/3') or
+ self in IPv6Network('6000::/3') or
+ self in IPv6Network('8000::/3') or
+ self in IPv6Network('A000::/3') or
+ self in IPv6Network('C000::/3') or
+ self in IPv6Network('E000::/4') or
+ self in IPv6Network('F000::/5') or
+ self in IPv6Network('F800::/6') or
+ self in IPv6Network('FE00::/9'))
+
+ @property
+ def is_unspecified(self):
+ """Test if the address is unspecified.
+
+ Returns:
+ A boolean, True if this is the unspecified address as defined in
+ RFC 2373 2.5.2.
+
+ """
+ return self._ip == 0 and getattr(self, '_prefixlen', 128) == 128
+
+ @property
+ def is_loopback(self):
+ """Test if the address is a loopback address.
+
+ Returns:
+ A boolean, True if the address is a loopback address as defined in
+ RFC 2373 2.5.3.
+
+ """
+ return self._ip == 1 and getattr(self, '_prefixlen', 128) == 128
+
+ @property
+ def is_link_local(self):
+ """Test if the address is reserved for link-local.
+
+ Returns:
+ A boolean, True if the address is reserved per RFC 4291.
+
+ """
+ return self in IPv6Network('fe80::/10')
+
+ @property
+ def is_site_local(self):
+ """Test if the address is reserved for site-local.
+
+ Note that the site-local address space has been deprecated by RFC 3879.
+ Use is_private to test if this address is in the space of unique local
+ addresses as defined by RFC 4193.
+
+ Returns:
+ A boolean, True if the address is reserved per RFC 3513 2.5.6.
+
+ """
+ return self in IPv6Network('fec0::/10')
+
+ @property
+ def is_private(self):
+ """Test if this address is allocated for private networks.
+
+ Returns:
+ A boolean, True if the address is reserved per RFC 4193.
+
+ """
+ return self in IPv6Network('fc00::/7')
+
+ @property
+ def ipv4_mapped(self):
+ """Return the IPv4 mapped address.
+
+ Returns:
+ If the IPv6 address is a v4 mapped address, return the
+ IPv4 mapped address. Return None otherwise.
+
+ """
+ if (self._ip >> 32) != 0xFFFF:
+ return None
+ return IPv4Address(self._ip & 0xFFFFFFFF)
+
+ @property
+ def teredo(self):
+ """Tuple of embedded teredo IPs.
+
+ Returns:
+ Tuple of the (server, client) IPs or None if the address
+ doesn't appear to be a teredo address (doesn't start with
+ 2001::/32)
+
+ """
+ if (self._ip >> 96) != 0x20010000:
+ return None
+ return (IPv4Address((self._ip >> 64) & 0xFFFFFFFF),
+ IPv4Address(~self._ip & 0xFFFFFFFF))
+
+ @property
+ def sixtofour(self):
+ """Return the IPv4 6to4 embedded address.
+
+ Returns:
+ The IPv4 6to4-embedded address if present or None if the
+ address doesn't appear to contain a 6to4 embedded address.
+
+ """
+ if (self._ip >> 112) != 0x2002:
+ return None
+ return IPv4Address((self._ip >> 80) & 0xFFFFFFFF)
+
+
+class IPv6Address(_BaseV6, _BaseIP):
+
+ """Represent and manipulate single IPv6 Addresses.
+ """
+
+ def __init__(self, address):
+ """Instantiate a new IPv6 address object.
+
+ Args:
+ address: A string or integer representing the IP
+
+ Additionally, an integer can be passed, so
+ IPv6Address('2001:4860::') ==
+ IPv6Address(42541956101370907050197289607612071936L).
+ or, more generally
+ IPv6Address(IPv6Address('2001:4860::')._ip) ==
+ IPv6Address('2001:4860::')
+
+ Raises:
+ AddressValueError: If address isn't a valid IPv6 address.
+
+ """
+ _BaseV6.__init__(self, address)
+
+ # Efficient constructor from integer.
+ if isinstance(address, (int, long)):
+ self._ip = address
+ if address < 0 or address > self._ALL_ONES:
+ raise AddressValueError(address)
+ return
+
+ # Constructing from a packed address
+ if isinstance(address, Bytes):
+ try:
+ hi, lo = struct.unpack('!QQ', address)
+ except struct.error:
+ raise AddressValueError(address) # Wrong length.
+ self._ip = (hi << 64) | lo
+ return
+
+ # Assume input argument to be string or any object representation
+ # which converts into a formatted IP string.
+ addr_str = str(address)
+ if not addr_str:
+ raise AddressValueError('')
+
+ self._ip = self._ip_int_from_string(addr_str)
+
+
+class IPv6Network(_BaseV6, _BaseNet):
+
+ """This class represents and manipulates 128-bit IPv6 networks.
+
+ Attributes: [examples for IPv6('2001:658:22A:CAFE:200::1/64')]
+ .ip: IPv6Address('2001:658:22a:cafe:200::1')
+ .network: IPv6Address('2001:658:22a:cafe::')
+ .hostmask: IPv6Address('::ffff:ffff:ffff:ffff')
+ .broadcast: IPv6Address('2001:658:22a:cafe:ffff:ffff:ffff:ffff')
+ .netmask: IPv6Address('ffff:ffff:ffff:ffff::')
+ .prefixlen: 64
+
+ """
+
+ def __init__(self, address, strict=False):
+ """Instantiate a new IPv6 Network object.
+
+ Args:
+ address: A string or integer representing the IPv6 network or the IP
+ and prefix/netmask.
+ '2001:4860::/128'
+ '2001:4860:0000:0000:0000:0000:0000:0000/128'
+ '2001:4860::'
+ are all functionally the same in IPv6. That is to say,
+ failing to provide a subnetmask will create an object with
+ a mask of /128.
+
+ Additionally, an integer can be passed, so
+ IPv6Network('2001:4860::') ==
+ IPv6Network(42541956101370907050197289607612071936L).
+ or, more generally
+ IPv6Network(IPv6Network('2001:4860::')._ip) ==
+ IPv6Network('2001:4860::')
+
+ strict: A boolean. If true, ensure that we have been passed
+ A true network address, eg, 192.168.1.0/24 and not an
+ IP address on a network, eg, 192.168.1.1/24.
+
+ Raises:
+ AddressValueError: If address isn't a valid IPv6 address.
+ NetmaskValueError: If the netmask isn't valid for
+ an IPv6 address.
+ ValueError: If strict was True and a network address was not
+ supplied.
+
+ """
+ _BaseNet.__init__(self, address)
+ _BaseV6.__init__(self, address)
+
+ # Constructing from an integer or packed bytes.
+ if isinstance(address, (int, long, Bytes)):
+ self.ip = IPv6Address(address)
+ self._ip = self.ip._ip
+ self._prefixlen = self._max_prefixlen
+ self.netmask = IPv6Address(self._ALL_ONES)
+ return
+
+ # Assume input argument to be string or any object representation
+ # which converts into a formatted IP prefix string.
+ addr = str(address).split('/')
+
+ if len(addr) > 2:
+ raise AddressValueError(address)
+
+ self._ip = self._ip_int_from_string(addr[0])
+ self.ip = IPv6Address(self._ip)
+
+ if len(addr) == 2:
+ if self._is_valid_netmask(addr[1]):
+ self._prefixlen = int(addr[1])
+ else:
+ raise NetmaskValueError(addr[1])
+ else:
+ self._prefixlen = self._max_prefixlen
+
+ self.netmask = IPv6Address(self._ip_int_from_prefix(self._prefixlen))
+
+ if strict:
+ if self.ip != self.network:
+ raise ValueError('%s has host bits set' %
+ self.ip)
+ if self._prefixlen == (self._max_prefixlen - 1):
+ self.iterhosts = self.__iter__
+
+ def _is_valid_netmask(self, prefixlen):
+ """Verify that the netmask/prefixlen is valid.
+
+ Args:
+ prefixlen: A string, the netmask in prefix length format.
+
+ Returns:
+ A boolean, True if the prefix represents a valid IPv6
+ netmask.
+
+ """
+ try:
+ prefixlen = int(prefixlen)
+ except ValueError:
+ return False
+ return 0 <= prefixlen <= self._max_prefixlen
+
+ @property
+ def with_netmask(self):
+ return self.with_prefixlen
diff --git a/rhodecode/lib/markup_renderer.py b/rhodecode/lib/markup_renderer.py
index 52869a43..63c9a354 100644
--- a/rhodecode/lib/markup_renderer.py
+++ b/rhodecode/lib/markup_renderer.py
@@ -98,7 +98,7 @@ class MarkupRenderer(object):
source = safe_unicode(source)
try:
import markdown as __markdown
- return __markdown.markdown(source, ['codehilite', 'tables'])
+ return __markdown.markdown(source, ['codehilite', 'extra'])
except ImportError:
log.warning('Install markdown to use this function')
return cls.plain(source)
diff --git a/rhodecode/lib/middleware/simplegit.py b/rhodecode/lib/middleware/simplegit.py
index e196bc62..e35f95ae 100644
--- a/rhodecode/lib/middleware/simplegit.py
+++ b/rhodecode/lib/middleware/simplegit.py
@@ -109,7 +109,7 @@ class SimpleGit(BaseVCSController):
if not self._check_ssl(environ, start_response):
return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
- ipaddr = self._get_ip_addr(environ)
+ ip_addr = self._get_ip_addr(environ)
username = None
self._git_first_op = False
# skip passing error to error controller
@@ -140,7 +140,7 @@ class SimpleGit(BaseVCSController):
anonymous_user = self.__get_user('default')
username = anonymous_user.username
anonymous_perm = self._check_permission(action, anonymous_user,
- repo_name)
+ repo_name, ip_addr)
if anonymous_perm is not True or anonymous_user.active is False:
if anonymous_perm is not True:
@@ -182,7 +182,7 @@ class SimpleGit(BaseVCSController):
return HTTPInternalServerError()(environ, start_response)
#check permissions for this repository
- perm = self._check_permission(action, user, repo_name)
+ perm = self._check_permission(action, user, repo_name, ip_addr)
if perm is not True:
return HTTPForbidden()(environ, start_response)
@@ -191,7 +191,7 @@ class SimpleGit(BaseVCSController):
from rhodecode import CONFIG
server_url = get_server_url(environ)
extras = {
- 'ip': ipaddr,
+ 'ip': ip_addr,
'username': username,
'action': action,
'repository': repo_name,
@@ -233,11 +233,12 @@ class SimpleGit(BaseVCSController):
self._invalidate_cache(repo_name)
self._handle_githooks(repo_name, action, baseui, environ)
- log.info('%s action on GIT repo "%s"' % (action, repo_name))
+ log.info('%s action on GIT repo "%s" by "%s" from %s' %
+ (action, repo_name, username, ip_addr))
app = self.__make_app(repo_name, repo_path, extras)
return app(environ, start_response)
except HTTPLockedRC, e:
- log.debug('Repositry LOCKED ret code 423!')
+ log.debug('Repository LOCKED ret code 423!')
return e(environ, start_response)
except Exception:
log.error(traceback.format_exc())
diff --git a/rhodecode/lib/middleware/simplehg.py b/rhodecode/lib/middleware/simplehg.py
index 6834a51b..5d2ef692 100644
--- a/rhodecode/lib/middleware/simplehg.py
+++ b/rhodecode/lib/middleware/simplehg.py
@@ -73,7 +73,7 @@ class SimpleHg(BaseVCSController):
if not self._check_ssl(environ, start_response):
return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
- ipaddr = self._get_ip_addr(environ)
+ ip_addr = self._get_ip_addr(environ)
username = None
# skip passing error to error controller
environ['pylons.status_code_redirect'] = True
@@ -103,7 +103,7 @@ class SimpleHg(BaseVCSController):
anonymous_user = self.__get_user('default')
username = anonymous_user.username
anonymous_perm = self._check_permission(action, anonymous_user,
- repo_name)
+ repo_name, ip_addr)
if anonymous_perm is not True or anonymous_user.active is False:
if anonymous_perm is not True:
@@ -145,7 +145,7 @@ class SimpleHg(BaseVCSController):
return HTTPInternalServerError()(environ, start_response)
#check permissions for this repository
- perm = self._check_permission(action, user, repo_name)
+ perm = self._check_permission(action, user, repo_name, ip_addr)
if perm is not True:
return HTTPForbidden()(environ, start_response)
@@ -154,7 +154,7 @@ class SimpleHg(BaseVCSController):
from rhodecode import CONFIG
server_url = get_server_url(environ)
extras = {
- 'ip': ipaddr,
+ 'ip': ip_addr,
'username': username,
'action': action,
'repository': repo_name,
@@ -194,14 +194,15 @@ class SimpleHg(BaseVCSController):
# invalidate cache on push
if action == 'push':
self._invalidate_cache(repo_name)
- log.info('%s action on HG repo "%s"' % (action, repo_name))
+ log.info('%s action on HG repo "%s" by "%s" from %s' %
+ (action, repo_name, username, ip_addr))
app = self.__make_app(repo_path, baseui, extras)
return app(environ, start_response)
except RepoError, e:
if str(e).find('not found') != -1:
return HTTPNotFound()(environ, start_response)
except HTTPLockedRC, e:
- log.debug('Repositry LOCKED ret code 423!')
+ log.debug('Repository LOCKED ret code 423!')
return e(environ, start_response)
except Exception:
log.error(traceback.format_exc())
diff --git a/rhodecode/lib/update_repoinfo.py b/rhodecode/lib/update_repoinfo.py
index 05d6f37f..f4abc416 100644
--- a/rhodecode/lib/update_repoinfo.py
+++ b/rhodecode/lib/update_repoinfo.py
@@ -34,6 +34,7 @@ from os.path import dirname as dn, join as jn
from rhodecode.model import init_model
from rhodecode.lib.utils2 import engine_from_config, safe_str
from rhodecode.model.db import RhodeCodeUi, Repository
+from rhodecode.lib.vcs.backends.base import EmptyChangeset
#to get the rhodecode import
@@ -73,8 +74,9 @@ class UpdateCommand(BasePasterCommand):
else:
repo_list = Repository.getAll()
for repo in repo_list:
- last_change = repo.scm_instance.last_change
- repo.update_last_change(last_change)
+ last_cs = (repo.scm_instance.get_changeset() if repo.scm_instance
+ else EmptyChangeset())
+ repo.update_changeset_cache(last_cs)
def update_parser(self):
self.parser.add_option('--update-only',
diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py
index 805d2683..24a3ffa6 100644
--- a/rhodecode/lib/utils.py
+++ b/rhodecode/lib/utils.py
@@ -162,10 +162,8 @@ def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
user_log.user_ip = ipaddr
sa.add(user_log)
- log.info(
- 'Adding user %s, action %s on %s' % (user_obj, action,
- safe_unicode(repo))
- )
+ log.info('Logging action %s on %s by %s' %
+ (action, safe_unicode(repo), user_obj))
if commit:
sa.commit()
except:
@@ -309,7 +307,7 @@ def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
cfg.read(path)
for section in ui_sections:
for k, v in cfg.items(section):
- log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
+ log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
elif read_from == 'db':
@@ -321,7 +319,7 @@ def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
hg_ui = ret
for ui_ in hg_ui:
if ui_.ui_active:
- log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
+ log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
ui_.ui_key, ui_.ui_value)
baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
safe_str(ui_.ui_value))
@@ -423,6 +421,13 @@ def repo2db_mapper(initial_repo_list, remove_obsolete=False,
# CacheInvalidation.clear_cache()
# sa.commit()
+ ##creation defaults
+ defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
+ enable_statistics = defs.get('repo_enable_statistics')
+ enable_locking = defs.get('repo_enable_locking')
+ enable_downloads = defs.get('repo_enable_downloads')
+ private = defs.get('repo_private')
+
for name, repo in initial_repo_list.items():
group = map_groups(name)
db_repo = rm.get_by_repo_name(name)
@@ -433,18 +438,24 @@ def repo2db_mapper(initial_repo_list, remove_obsolete=False,
desc = (repo.description
if repo.description != 'unknown'
else '%s repository' % name)
+
new_repo = rm.create_repo(
repo_name=name,
repo_type=repo.alias,
description=desc,
repos_group=getattr(group, 'group_id', None),
owner=user,
- just_db=True
+ just_db=True,
+ enable_locking=enable_locking,
+ enable_downloads=enable_downloads,
+ enable_statistics=enable_statistics,
+ private=private
)
# we added that repo just now, and make sure it has githook
# installed
if new_repo.repo_type == 'git':
ScmModel().install_git_hook(new_repo.scm_instance)
+ new_repo.update_changeset_cache()
elif install_git_hook:
if db_repo.repo_type == 'git':
ScmModel().install_git_hook(db_repo.scm_instance)
@@ -452,8 +463,8 @@ def repo2db_mapper(initial_repo_list, remove_obsolete=False,
# system, this will register all repos and multiple instances
key, _prefix, _org_key = CacheInvalidation._get_key(name)
CacheInvalidation.invalidate(name)
- log.debug("Creating a cache key for %s instance_id=>`%s`"
- % (name, _prefix or '-'))
+ log.debug("Creating a cache key for %s, instance_id %s"
+ % (name, _prefix or 'unknown'))
sa.commit()
removed = []
@@ -740,4 +751,4 @@ def jsonify(func, *args, **kwargs):
warnings.warn(msg, Warning, 2)
log.warning(msg)
log.debug("Returning JSON wrapped action output")
- return json.dumps(data, encoding='utf-8') \ No newline at end of file
+ return json.dumps(data, encoding='utf-8')
diff --git a/rhodecode/lib/vcs/backends/base.py b/rhodecode/lib/vcs/backends/base.py
index 739adb64..cf9026d7 100644
--- a/rhodecode/lib/vcs/backends/base.py
+++ b/rhodecode/lib/vcs/backends/base.py
@@ -376,6 +376,7 @@ class BaseChangeset(object):
return dict(
short_id=self.short_id,
raw_id=self.raw_id,
+ revision=self.revision,
message=self.message,
date=self.date,
author=self.author,
diff --git a/rhodecode/lib/vcs/backends/git/repository.py b/rhodecode/lib/vcs/backends/git/repository.py
index 61d8f600..a1ba545d 100644
--- a/rhodecode/lib/vcs/backends/git/repository.py
+++ b/rhodecode/lib/vcs/backends/git/repository.py
@@ -606,10 +606,13 @@ class GitRepository(BaseRepository):
Tries to pull changes from external location.
"""
url = self._get_url(url)
- cmd = ['fetch']
- cmd.append(url)
- cmd = ' '.join(cmd)
- # If error occurs run_git_command raises RepositoryError already
+ so, se = self.run_git_command('ls-remote -h %s' % url)
+ refs = []
+ for line in (x for x in so.splitlines()):
+ sha, ref = line.split('\t')
+ refs.append(ref)
+ refs = ' '.join(('+%s:%s' % (r, r) for r in refs))
+ cmd = '''fetch %s -- %s''' % (url, refs)
self.run_git_command(cmd)
@LazyProperty
diff --git a/rhodecode/lib/vcs/nodes.py b/rhodecode/lib/vcs/nodes.py
index ca3add8a..bd5214db 100644
--- a/rhodecode/lib/vcs/nodes.py
+++ b/rhodecode/lib/vcs/nodes.py
@@ -362,10 +362,11 @@ class FileNode(Node):
Returns pygment's lexer class. Would try to guess lexer taking file's
content, name and mimetype.
"""
+
try:
- lexer = lexers.guess_lexer_for_filename(self.name, self.content)
+ lexer = lexers.guess_lexer_for_filename(self.name, self.content, stripnl=False)
except lexers.ClassNotFound:
- lexer = lexers.TextLexer()
+ lexer = lexers.TextLexer(stripnl=False)
# returns first alias
return lexer
diff --git a/rhodecode/model/changeset_status.py b/rhodecode/model/changeset_status.py
index c3da0434..a7e0efcf 100644
--- a/rhodecode/model/changeset_status.py
+++ b/rhodecode/model/changeset_status.py
@@ -89,44 +89,39 @@ class ChangesetStatusModel(BaseModel):
with_revisions)
return q.all()
- def get_status(self, repo, revision=None, pull_request=None):
+ def get_status(self, repo, revision=None, pull_request=None, as_str=True):
"""
Returns latest status of changeset for given revision or for given
pull request. Statuses are versioned inside a table itself and
version == 0 is always the current one
:param repo:
- :type repo:
:param revision: 40char hash or None
- :type revision: str
:param pull_request: pull_request reference
- :type:
+ :param as_str: return status as string not object
"""
q = self._get_status_query(repo, revision, pull_request)
# need to use first here since there can be multiple statuses
# returned from pull_request
status = q.first()
- status = status.status if status else status
- st = status or ChangesetStatus.DEFAULT
- return str(st)
+ if as_str:
+ status = status.status if status else status
+ st = status or ChangesetStatus.DEFAULT
+ return str(st)
+ return status
- def set_status(self, repo, status, user, comment, revision=None,
+ def set_status(self, repo, status, user, comment=None, revision=None,
pull_request=None, dont_allow_on_closed_pull_request=False):
"""
Creates new status for changeset or updates the old ones bumping their
version, leaving the current status at
:param repo:
- :type repo:
:param revision:
- :type revision:
:param status:
- :type status:
:param user:
- :type user:
:param comment:
- :type comment:
:param dont_allow_on_closed_pull_request: don't allow a status change
if last status was for pull request and it's closed. We shouldn't
mess around this manually
@@ -134,14 +129,21 @@ class ChangesetStatusModel(BaseModel):
repo = self._get_repo(repo)
q = ChangesetStatus.query()
-
+ if not comment:
+ from rhodecode.model.comment import ChangesetCommentsModel
+ comment = ChangesetCommentsModel().create(
+ text='Auto status change',
+ repo=repo,
+ user=user,
+ pull_request=pull_request,
+ )
if revision:
q = q.filter(ChangesetStatus.repo == repo)
q = q.filter(ChangesetStatus.revision == revision)
elif pull_request:
pull_request = self.__get_pull_request(pull_request)
q = q.filter(ChangesetStatus.repo == pull_request.org_repo)
- q = q.filter(ChangesetStatus.pull_request == pull_request)
+ q = q.filter(ChangesetStatus.revision.in_(pull_request.revisions))
cur_statuses = q.all()
#if statuses exists and last is associated with a closed pull request
@@ -153,6 +155,7 @@ class ChangesetStatusModel(BaseModel):
'Changing status on closed pull request is not allowed'
)
+ #update all current statuses with older version
if cur_statuses:
for st in cur_statuses:
st.version += 1
diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py
index 85ca1fbf..7a2924e4 100755
--- a/rhodecode/model/db.py
+++ b/rhodecode/model/db.py
@@ -370,6 +370,11 @@ class User(Base, BaseModel):
return [self.email] + [x.email 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)
@@ -472,6 +477,7 @@ class User(Base, BaseModel):
admin=user.admin,
ldap_dn=user.ldap_dn,
last_login=user.last_login,
+ ip_addresses=user.ip_addresses
)
return data
@@ -518,6 +524,34 @@ class UserEmailMap(Base, BaseModel):
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'}
+ )
+ __mapper_args__ = {}
+
+ ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+ user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
+ ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+ active = Column("active", Boolean(), nullable=True, unique=None, default=True)
+ user = relationship('User', lazy='joined')
+
+ @classmethod
+ def _get_ip_range(cls, ip_addr):
+ from rhodecode.lib import ipaddr
+ net = ipaddr.IPv4Network(ip_addr)
+ return [str(net.network), str(net.broadcast)]
+
+ def __json__(self):
+ return dict(
+ ip_addr=self.ip_addr,
+ ip_range=self._get_ip_range(self.ip_addr)
+ )
+
+
class UserLog(Base, BaseModel):
__tablename__ = 'user_logs'
__table_args__ = (
@@ -637,6 +671,7 @@ class Repository(Base, BaseModel):
landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
_locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+ _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
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)
@@ -682,11 +717,40 @@ class Repository(Base, BaseModel):
else:
self._locked = None
+ @hybrid_property
+ def changeset_cache(self):
+ from rhodecode.lib.vcs.backends.base import EmptyChangeset
+ dummy = EmptyChangeset().__json__()
+ if not self._changeset_cache:
+ return dummy
+ try:
+ return json.loads(self._changeset_cache)
+ except TypeError:
+ return dummy
+
+ @changeset_cache.setter
+ def changeset_cache(self, val):
+ try:
+ self._changeset_cache = json.dumps(val)
+ except:
+ log.error(traceback.format_exc())
+
@classmethod
def url_sep(cls):
return URL_SEP
@classmethod
+ def normalize_repo_name(cls, repo_name):
+ """
+ Normalizes os specific repo_name to the format internally stored inside
+ dabatabase using URL_SEP
+
+ :param cls:
+ :param repo_name:
+ """
+ return cls.url_sep().join(repo_name.split(os.sep))
+
+ @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))\
@@ -697,6 +761,7 @@ class Repository(Base, BaseModel):
@classmethod
def get_by_full_path(cls, repo_full_path):
repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
+ repo_name = cls.normalize_repo_name(repo_name)
return cls.get_by_repo_name(repo_name.strip(URL_SEP))
@classmethod
@@ -841,7 +906,11 @@ class Repository(Base, BaseModel):
description=repo.description,
landing_rev=repo.landing_rev,
owner=repo.user.username,
- fork_of=repo.fork.repo_name if repo.fork else None
+ fork_of=repo.fork.repo_name if repo.fork else None,
+ enable_statistics=repo.enable_statistics,
+ enable_locking=repo.enable_locking,
+ enable_downloads=repo.enable_downloads,
+ last_changeset=repo.changeset_cache
)
return data
@@ -862,6 +931,25 @@ class Repository(Base, BaseModel):
def last_db_change(self):
return self.updated_on
+ def clone_url(self, **override):
+ from pylons import url
+ from urlparse import urlparse
+ import urllib
+ parsed_url = urlparse(url('home', qualified=True))
+ default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
+ decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
+ args = {
+ 'user': '',
+ 'pass': '',
+ 'scheme': parsed_url.scheme,
+ 'netloc': parsed_url.netloc,
+ 'prefix': decoded_path,
+ 'path': self.repo_name
+ }
+
+ args.update(override)
+ return default_clone_uri % args
+
#==========================================================================
# SCM PROPERTIES
#==========================================================================
@@ -876,12 +964,30 @@ class Repository(Base, BaseModel):
cs = self.get_changeset(self.landing_rev) or self.get_changeset()
return cs
- def update_last_change(self, last_change=None):
- if last_change is None:
- last_change = datetime.datetime.now()
- if self.updated_on is None or self.updated_on != last_change:
- log.debug('updated repo %s with new date %s' % (self, last_change))
+ def update_changeset_cache(self, cs_cache=None):
+ """
+ Update cache of last changeset for repository, keys should be::
+
+ short_id
+ raw_id
+ revision
+ message
+ date
+ author
+
+ :param cs_cache:
+ """
+ from rhodecode.lib.vcs.backends.base import BaseChangeset
+ if cs_cache is None:
+ cs_cache = self.get_changeset()
+ if isinstance(cs_cache, BaseChangeset):
+ cs_cache = cs_cache.__json__()
+
+ if cs_cache != self.changeset_cache:
+ last_change = cs_cache.get('date') or self.last_change
+ log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
self.updated_on = last_change
+ self.changeset_cache = cs_cache
Session().add(self)
Session().commit()
@@ -1708,6 +1814,14 @@ class PullRequest(Base, BaseModel):
def revisions(self, val):
self._revisions = ':'.join(val)
+ @property
+ def org_ref_parts(self):
+ return self.org_ref.split(':')
+
+ @property
+ def other_ref_parts(self):
+ return self.other_ref.split(':')
+
author = relationship('User', lazy='joined')
reviewers = relationship('PullRequestReviewers',
cascade="all, delete, delete-orphan")
diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py
index b46e6274..d7c9d38f 100644
--- a/rhodecode/model/forms.py
+++ b/rhodecode/model/forms.py
@@ -345,11 +345,16 @@ def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
def UserExtraEmailForm():
class _UserExtraEmailForm(formencode.Schema):
- email = All(v.UniqSystemEmail(), v.Email)
-
+ email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
return _UserExtraEmailForm
+def UserExtraIpForm():
+ class _UserExtraIpForm(formencode.Schema):
+ ip = v.ValidIp()(not_empty=True)
+ return _UserExtraIpForm
+
+
def PullRequestForm(repo_id):
class _PullRequestForm(formencode.Schema):
allow_extra_fields = True
@@ -360,7 +365,8 @@ def PullRequestForm(repo_id):
org_ref = v.UnicodeString(strip=True, required=True)
other_repo = v.UnicodeString(strip=True, required=True)
other_ref = v.UnicodeString(strip=True, required=True)
- revisions = All(v.NotReviewedRevisions(repo_id)(), v.UniqueList(not_empty=True))
+ revisions = All(#v.NotReviewedRevisions(repo_id)(),
+ v.UniqueList(not_empty=True))
review_members = v.UniqueList(not_empty=True)
pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
diff --git a/rhodecode/model/notification.py b/rhodecode/model/notification.py
index 0da8cf06..81e30edf 100644
--- a/rhodecode/model/notification.py
+++ b/rhodecode/model/notification.py
@@ -270,8 +270,9 @@ class EmailNotificationModel(BaseModel):
base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
email_template = self._tmpl_lookup.get_template(base)
- # translator inject
- _kwargs = {'_': _}
+ # translator and helpers inject
+ _kwargs = {'_': _,
+ 'h': h}
_kwargs.update(kwargs)
log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
return email_template.render(**_kwargs)
diff --git a/rhodecode/model/pull_request.py b/rhodecode/model/pull_request.py
index 49c45aea..5d0b85dc 100644
--- a/rhodecode/model/pull_request.py
+++ b/rhodecode/model/pull_request.py
@@ -33,7 +33,8 @@ from pylons.i18n.translation import _
from rhodecode.model.meta import Session
from rhodecode.lib import helpers as h
from rhodecode.model import BaseModel
-from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification
+from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\
+ ChangesetStatus
from rhodecode.model.notification import NotificationModel
from rhodecode.lib.utils2 import safe_unicode
@@ -54,8 +55,9 @@ class PullRequestModel(BaseModel):
repo = self._get_repo(repo)
return PullRequest.query().filter(PullRequest.other_repo == repo).all()
- def create(self, created_by, org_repo, org_ref, other_repo,
- other_ref, revisions, reviewers, title, description=None):
+ def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
+ revisions, reviewers, title, description=None):
+ from rhodecode.model.changeset_status import ChangesetStatusModel
created_by_user = self._get_user(created_by)
org_repo = self._get_repo(org_repo)
@@ -78,6 +80,14 @@ class PullRequestModel(BaseModel):
reviewer = PullRequestReviewers(_usr, new)
self.sa.add(reviewer)
+ #reset state to under-review
+ ChangesetStatusModel().set_status(
+ repo=org_repo,
+ status=ChangesetStatus.STATUS_UNDER_REVIEW,
+ user=created_by_user,
+ pull_request=new
+ )
+
#notification to reviewers
notif = NotificationModel()
diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py
index 79536c7e..18db88ce 100644
--- a/rhodecode/model/repo.py
+++ b/rhodecode/model/repo.py
@@ -41,6 +41,7 @@ from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup,\
RhodeCodeSetting
from rhodecode.lib import helpers as h
+from rhodecode.lib.auth import HasRepoPermissionAny
log = logging.getLogger(__name__)
@@ -89,6 +90,22 @@ class RepoModel(BaseModel):
"get_repo_%s" % repo_name))
return repo.scalar()
+ def get_all_user_repos(self, user):
+ """
+ Get's all repositories that user have at least read access
+
+ :param user:
+ :type user:
+ """
+ from rhodecode.lib.auth import AuthUser
+ user = self._get_user(user)
+ repos = AuthUser(user_id=user.user_id).permissions['repositories']
+ access_check = lambda r: r[1] in ['repository.read',
+ 'repository.write',
+ 'repository.admin']
+ repos = [x[0] for x in filter(access_check, repos.items())]
+ return Repository.query().filter(Repository.repo_name.in_(repos))
+
def get_users_js(self):
users = self.sa.query(User).filter(User.active == True).all()
return json.dumps([
@@ -113,6 +130,95 @@ class RepoModel(BaseModel):
} for gr in users_groups]
)
+ @classmethod
+ def _render_datatable(cls, tmpl, *args, **kwargs):
+ import rhodecode
+ from pylons import tmpl_context as c
+ from pylons.i18n.translation import _
+
+ _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
+ template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
+
+ tmpl = template.get_def(tmpl)
+ kwargs.update(dict(_=_, h=h, c=c))
+ return tmpl.render(*args, **kwargs)
+
+ def get_repos_as_dict(self, repos_list=None, admin=False, perm_check=True):
+ _render = self._render_datatable
+
+ def quick_menu(repo_name):
+ return _render('quick_menu', repo_name)
+
+ def repo_lnk(name, rtype, private, fork_of):
+ return _render('repo_name', name, rtype, private, fork_of,
+ short_name=not admin, admin=False)
+
+ def last_change(last_change):
+ return _render("last_change", last_change)
+
+ def rss_lnk(repo_name):
+ return _render("rss", repo_name)
+
+ def atom_lnk(repo_name):
+ return _render("atom", repo_name)
+
+ def last_rev(repo_name, cs_cache):
+ return _render('revision', repo_name, cs_cache.get('revision'),
+ cs_cache.get('raw_id'), cs_cache.get('author'),
+ cs_cache.get('message'))
+
+ def desc(desc):
+ from pylons import tmpl_context as c
+ if c.visual.stylify_metatags:
+ return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
+ else:
+ return h.urlify_text(h.truncate(desc, 60))
+
+ def repo_actions(repo_name):
+ return _render('repo_actions', repo_name)
+
+ def owner_actions(user_id, username):
+ return _render('user_name', user_id, username)
+
+ repos_data = []
+ for repo in repos_list:
+ if perm_check:
+ # check permission at this level
+ if not HasRepoPermissionAny(
+ 'repository.read', 'repository.write', 'repository.admin'
+ )(repo.repo_name, 'get_repos_as_dict check'):
+ continue
+ cs_cache = repo.changeset_cache
+ row = {
+ "menu": quick_menu(repo.repo_name),
+ "raw_name": repo.repo_name.lower(),
+ "name": repo_lnk(repo.repo_name, repo.repo_type,
+ repo.private, repo.fork),
+ "last_change": last_change(repo.last_db_change),
+ "last_changeset": last_rev(repo.repo_name, cs_cache),
+ "raw_tip": cs_cache.get('revision'),
+ "desc": desc(repo.description),
+ "owner": h.person(repo.user.username),
+ "rss": rss_lnk(repo.repo_name),
+ "atom": atom_lnk(repo.repo_name),
+
+ }
+ if admin:
+ row.update({
+ "action": repo_actions(repo.repo_name),
+ "owner": owner_actions(repo.user.user_id,
+ h.person(repo.user.username))
+ })
+ repos_data.append(row)
+
+ return {
+ "totalRecords": len(repos_list),
+ "startIndex": 0,
+ "sort": "name",
+ "dir": "asc",
+ "records": repos_data
+ }
+
def _get_defaults(self, repo_name):
"""
Get's information about repository, and returns a dict for
@@ -339,9 +445,9 @@ class RepoModel(BaseModel):
copy_fork_permissions = form_data.get('copy_permissions')
fork_of = form_data.get('fork_parent_id')
- ##defaults
+ ## repo creation defaults, private and repo_type are filled in form
defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
- enable_statistics = defs.get('repo_enable_statistic')
+ enable_statistics = defs.get('repo_enable_statistics')
enable_locking = defs.get('repo_enable_locking')
enable_downloads = defs.get('repo_enable_downloads')
diff --git a/rhodecode/model/repos_group.py b/rhodecode/model/repos_group.py
index c3ee7629..d4162b39 100644
--- a/rhodecode/model/repos_group.py
+++ b/rhodecode/model/repos_group.py
@@ -273,7 +273,7 @@ class ReposGroupModel(BaseModel):
self.sa.delete(repos_group)
self.__delete_group(repos_group, force_delete)
except:
- log.exception('Error removing repos_group %s' % repos_group)
+ log.error('Error removing repos_group %s' % repos_group)
raise
def delete_permission(self, repos_group, obj, obj_type, recursive):
diff --git a/rhodecode/model/scm.py b/rhodecode/model/scm.py
index 43cb7baa..4490293e 100644
--- a/rhodecode/model/scm.py
+++ b/rhodecode/model/scm.py
@@ -230,7 +230,7 @@ class ScmModel(BaseModel):
# name need to be decomposed and put back together using the /
# since this is internal storage separator for rhodecode
- name = Repository.url_sep().join(name.split(os.sep))
+ name = Repository.normalize_repo_name(name)
try:
if name in repos:
@@ -292,6 +292,9 @@ class ScmModel(BaseModel):
:param repo_name: this repo that should invalidation take place
"""
CacheInvalidation.set_invalidate(repo_name=repo_name)
+ repo = Repository.get_by_repo_name(repo_name)
+ if repo:
+ repo.update_changeset_cache()
def toggle_following_repo(self, follow_repo_id, user_id):
diff --git a/rhodecode/model/user.py b/rhodecode/model/user.py
index 89657793..453c6472 100644
--- a/rhodecode/model/user.py
+++ b/rhodecode/model/user.py
@@ -27,7 +27,6 @@ import logging
import traceback
import itertools
import collections
-import functools
from pylons import url
from pylons.i18n.translation import _
@@ -40,7 +39,7 @@ from rhodecode.model import BaseModel
from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \
- UserEmailMap
+ UserEmailMap, UserIpMap
from rhodecode.lib.exceptions import DefaultUserException, \
UserOwnsReposException
@@ -294,30 +293,6 @@ class UserModel(BaseModel):
log.error(traceback.format_exc())
raise
- def update_my_account(self, user_id, form_data):
- from rhodecode.lib.auth import get_crypt_password
- try:
- user = self.get(user_id, cache=False)
- if user.username == 'default':
- raise DefaultUserException(
- _("You can't Edit this user since it's"
- " crucial for entire application")
- )
- for k, v in form_data.items():
- if k == 'new_password' and v:
- user.password = get_crypt_password(v)
- user.api_key = generate_api_key(user.username)
- else:
- if k == 'firstname':
- k = 'name'
- if k not in ['admin', 'active']:
- setattr(user, k, v)
-
- self.sa.add(user)
- except:
- log.error(traceback.format_exc())
- raise
-
def delete(self, user):
user = self._get_user(user)
@@ -705,3 +680,33 @@ class UserModel(BaseModel):
obj = UserEmailMap.query().get(email_id)
if obj:
self.sa.delete(obj)
+
+ def add_extra_ip(self, user, ip):
+ """
+ Adds ip address to UserIpMap
+
+ :param user:
+ :param ip:
+ """
+ from rhodecode.model import forms
+ form = forms.UserExtraIpForm()()
+ data = form.to_python(dict(ip=ip))
+ user = self._get_user(user)
+
+ obj = UserIpMap()
+ obj.user = user
+ obj.ip_addr = data['ip']
+ self.sa.add(obj)
+ return obj
+
+ def delete_extra_ip(self, user, ip_id):
+ """
+ Removes ip address from UserIpMap
+
+ :param user:
+ :param ip_id:
+ """
+ user = self._get_user(user)
+ obj = UserIpMap.query().get(ip_id)
+ if obj:
+ self.sa.delete(obj)
diff --git a/rhodecode/model/validators.py b/rhodecode/model/validators.py
index 9923f28e..61ed0583 100644
--- a/rhodecode/model/validators.py
+++ b/rhodecode/model/validators.py
@@ -11,7 +11,7 @@ from webhelpers.pylonslib.secure_form import authentication_token
from formencode.validators import (
UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
- NotEmpty
+ NotEmpty, IPAddress, CIDR
)
from rhodecode.lib.compat import OrderedSet
from rhodecode.lib.utils import repo_name_slug
@@ -23,7 +23,7 @@ from rhodecode.lib.auth import HasReposGroupPermissionAny
# silence warnings and pylint
UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
- NotEmpty
+ NotEmpty, IPAddress, CIDR
log = logging.getLogger(__name__)
@@ -566,7 +566,7 @@ def ValidPerms(type_='repo'):
def ValidSettings():
class _validator(formencode.validators.FancyValidator):
def _to_python(self, value, state):
- # settings form for users that are not admin
+ # settings form for users that are not admin
# can't edit certain parameters, it's extra backup if they mangle
# with forms
@@ -706,3 +706,40 @@ def NotReviewedRevisions(repo_id):
)
return _validator
+
+
+def ValidIp():
+ class _validator(CIDR):
+ messages = dict(
+ badFormat=_('Please enter a valid IP address (a.b.c.d)'),
+ illegalOctets=_('The octets must be within the range of 0-255'
+ ' (not %(octet)r)'),
+ illegalBits=_('The network size (bits) must be within the range'
+ ' of 0-32 (not %(bits)r)'))
+
+ def validate_python(self, value, state):
+ try:
+ # Split into octets and bits
+ if '/' in value: # a.b.c.d/e
+ addr, bits = value.split('/')
+ else: # a.b.c.d
+ addr, bits = value, 32
+ # Use IPAddress validator to validate the IP part
+ IPAddress.validate_python(self, addr, state)
+ # Bits (netmask) correct?
+ if not 0 <= int(bits) <= 32:
+ raise formencode.Invalid(
+ self.message('illegalBits', state, bits=bits),
+ value, state)
+ # Splitting faild: wrong syntax
+ except ValueError:
+ raise formencode.Invalid(self.message('badFormat', state),
+ value, state)
+
+ def to_python(self, value, state):
+ v = super(_validator, self).to_python(value, state)
+ #if IP doesn't end with a mask, add /32
+ if '/' not in value:
+ v += '/32'
+ return v
+ return _validator
diff --git a/rhodecode/public/css/style.css b/rhodecode/public/css/style.css
index c9222015..dc8b7cc7 100644
--- a/rhodecode/public/css/style.css
+++ b/rhodecode/public/css/style.css
@@ -2002,7 +2002,6 @@ a.metatag[tag="license"]:hover {
}
#login div.title {
- width: 420px;
clear: both;
overflow: hidden;
position: relative;
@@ -2021,7 +2020,6 @@ a.metatag[tag="license"]:hover {
}
#login div.inner {
- width: 380px;
background: #FFF url("../images/login.png") no-repeat top left;
border-top: none;
border-bottom: none;
@@ -2038,7 +2036,6 @@ a.metatag[tag="license"]:hover {
}
#login div.form div.fields div.field div.input input {
- width: 176px;
background: #FFF;
border-top: 1px solid #b3b3b3;
border-left: 1px solid #b3b3b3;
@@ -2781,7 +2778,9 @@ h3.files_location {
margin: 0px 2px;
}
-.right .logtags .branchtag,.logtags .branchtag {
+.right .logtags .branchtag,
+.logtags .branchtag,
+.spantag {
padding: 1px 3px 1px 3px;
background-color: #bfbfbf;
font-size: 10px;
@@ -3238,7 +3237,7 @@ table.code-browser .submodule-dir {
}
.edit_icon {
- background: url("../images/icons/folder_edit.png") no-repeat scroll 3px;
+ background: url("../images/icons/application_form_edit.png") no-repeat scroll 3px;
padding-left: 20px;
padding-top: 0px;
text-align: left;
@@ -4040,6 +4039,22 @@ div#legend_container table td,div#legend_choices table td {
float: left
}
+.ips_wrap{
+ padding: 0px 20px;
+}
+
+.ips_wrap .ip_entry{
+ height: 30px;
+ padding:0px 0px 0px 10px;
+}
+.ips_wrap .ip_entry .ip{
+ float: left
+}
+.ips_wrap .ip_entry .ip_action{
+ float: left
+}
+
+
/*README STYLE*/
div.readme {
diff --git a/rhodecode/public/js/rhodecode.js b/rhodecode/public/js/rhodecode.js
index 5b1ddb2f..f52a280c 100644
--- a/rhodecode/public/js/rhodecode.js
+++ b/rhodecode/public/js/rhodecode.js
@@ -334,7 +334,7 @@ var show_changeset_tooltip = function(){
YUD.setAttribute(target, 'title',_TM['loading...']);
YAHOO.yuitip.main.set_listeners(target);
YAHOO.yuitip.main.show_yuitip(e, target);
- ajaxGET('/changeset_info/{0}/{1}'.format(repo_name,rid), success)
+ ajaxGET(LAZY_CS_URL.replace('__NAME__',repo_name).replace('__REV__', rid), success)
}
});
};
@@ -416,7 +416,6 @@ YAHOO.yuitip.main = {
},
init: function(){
- yt._tooltip = '';
yt.tipBox = yt.$('tip-box');
if(!yt.tipBox){
yt.tipBox = document.createElement('div');
@@ -457,7 +456,7 @@ YAHOO.yuitip.main = {
if(yt.tipText !== ''){
// save org title
- yt._tooltip = yt.tipText;
+ YUD.setAttribute(el, 'tt_title', yt.tipText);
// reset title to not show org tooltips
YUD.setAttribute(el, 'title', '');
@@ -495,7 +494,7 @@ YAHOO.yuitip.main = {
} else {
YUD.setStyle(yt.tipBox, 'display', 'none');
}
- YUD.setAttribute(el,'title', yt._tooltip);
+ YUD.setAttribute(el,'title', YUD.getAttribute(el, 'tt_title'));
}
}
diff --git a/rhodecode/templates/admin/admin.html b/rhodecode/templates/admin/admin.html
index 80c4b3b4..d77f31cd 100644
--- a/rhodecode/templates/admin/admin.html
+++ b/rhodecode/templates/admin/admin.html
@@ -53,4 +53,3 @@ YUE.on('filter_form','submit',function(e){
fix_j_filter_width(YUD.get('j_filter').value.length);
</script>
</%def>
-
diff --git a/rhodecode/templates/admin/admin_log.html b/rhodecode/templates/admin/admin_log.html
index c50e2163..5c98c2a5 100644
--- a/rhodecode/templates/admin/admin_log.html
+++ b/rhodecode/templates/admin/admin_log.html
@@ -16,7 +16,7 @@
${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}
%else:
${l.username}
- %endif
+ %endif
</td>
<td>${h.action_parser(l)[0]()}
<div class="journal_action_params">
diff --git a/rhodecode/templates/admin/permissions/permissions.html b/rhodecode/templates/admin/permissions/permissions.html
index 653600c1..b36b0e92 100644
--- a/rhodecode/templates/admin/permissions/permissions.html
+++ b/rhodecode/templates/admin/permissions/permissions.html
@@ -16,7 +16,7 @@
</%def>
<%def name="main()">
-<div class="box">
+<div class="box box-left">
<!-- box / title -->
<div class="title">
${self.breadcrumbs()}
@@ -89,10 +89,127 @@
</div>
</div>
<div class="buttons">
- ${h.submit('set',_('set'),class_="ui-btn large")}
+ ${h.submit('save',_('Save'),class_="ui-btn large")}
+ ${h.reset('reset',_('Reset'),class_="ui-btn large")}
</div>
</div>
</div>
${h.end_form()}
</div>
+
+<div style="min-height:780px" class="box box-right">
+ <!-- box / title -->
+ <div class="title">
+ <h5>${_('Default User Permissions')}</h5>
+ </div>
+
+ ## permissions overview
+ <div id="perms" class="table">
+ %for section in sorted(c.perm_user.permissions.keys()):
+ <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
+ %if not c.perm_user.permissions[section]:
+ <span class="empty_data">${_('Nothing here yet')}</span>
+ %else:
+ <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
+ <table id="tbl_list_${section}">
+ <thead>
+ <tr>
+ <th class="left">${_('Name')}</th>
+ <th class="left">${_('Permission')}</th>
+ <th class="left">${_('Edit Permission')}</th>
+ </thead>
+ <tbody>
+ %for k in c.perm_user.permissions[section]:
+ <%
+ if section != 'global':
+ section_perm = c.perm_user.permissions[section].get(k)
+ _perm = section_perm.split('.')[-1]
+ else:
+ _perm = section_perm = None
+ %>
+ <tr>
+ <td>
+ %if section == 'repositories':
+ <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
+ %elif section == 'repositories_groups':
+ <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
+ %else:
+ ${h.get_permission_name(k)}
+ %endif
+ </td>
+ <td>
+ %if section == 'global':
+ ${h.bool2icon(k.split('.')[-1] != 'none')}
+ %else:
+ <span class="perm_tag ${_perm}">${section_perm}</span>
+ %endif
+ </td>
+ <td>
+ %if section == 'repositories':
+ <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
+ %elif section == 'repositories_groups':
+ <a href="${h.url('edit_repos_group',id=k,anchor='permissions_manage')}">${_('edit')}</a>
+ %else:
+ --
+ %endif
+ </td>
+ </tr>
+ %endfor
+ </tbody>
+ </table>
+ </div>
+ %endif
+ %endfor
+ </div>
+</div>
+<div class="box box-left" style="clear:left">
+ <!-- box / title -->
+ <div class="title">
+ <h5>${_('Allowed IP addresses')}</h5>
+ </div>
+
+ <div class="ips_wrap">
+ <table class="noborder">
+ %if c.user_ip_map:
+ %for ip in c.user_ip_map:
+ <tr>
+ <td><div class="ip">${ip.ip_addr}</div></td>
+ <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
+ <td>
+ ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')}
+ ${h.hidden('del_ip',ip.ip_id)}
+ ${h.hidden('default_user', 'True')}
+ ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
+ class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
+ ${h.end_form()}
+ </td>
+ </tr>
+ %endfor
+ %else:
+ <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
+ %endif
+ </table>
+ </div>
+
+ ${h.form(url('user_ips', id=c.user.user_id),method='put')}
+ <div class="form">
+ <!-- fields -->
+ <div class="fields">
+ <div class="field">
+ <div class="label">
+ <label for="new_ip">${_('New ip address')}:</label>
+ </div>
+ <div class="input">
+ ${h.hidden('default_user', 'True')}
+ ${h.text('new_ip', class_='medium')}
+ </div>
+ </div>
+ <div class="buttons">
+ ${h.submit('save',_('Add'),class_="ui-btn large")}
+ ${h.reset('reset',_('Reset'),class_="ui-btn large")}
+ </div>
+ </div>
+ </div>
+ ${h.end_form()}
+</div>
</%def>
diff --git a/rhodecode/templates/admin/repos/repos.html b/rhodecode/templates/admin/repos/repos.html
index 02582d62..67ad9ddd 100644
--- a/rhodecode/templates/admin/repos/repos.html
+++ b/rhodecode/templates/admin/repos/repos.html
@@ -40,6 +40,7 @@
{key:"raw_name"},
{key:"name"},
{key:"desc"},
+ {key:"last_changeset"},
{key:"owner"},
{key:"action"},
]
@@ -70,6 +71,8 @@
{key:"name",label:"${_('Name')}",sortable:true,
sortOptions: { sortFunction: nameSort }},
{key:"desc",label:"${_('Description')}",sortable:true},
+ {key:"last_changeset",label:"${_('Tip')}",sortable:true,
+ sortOptions: { sortFunction: revisionSort }},
{key:"owner",label:"${_('Owner')}",sortable:true},
{key:"action",label:"${_('Action')}",sortable:false},
];
@@ -77,7 +80,7 @@
var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
sortedBy:{key:"name",dir:"asc"},
paginator: new YAHOO.widget.Paginator({
- rowsPerPage: 15,
+ rowsPerPage: 25,
alwaysVisible: false,
template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
pageLinks: 5,
@@ -111,7 +114,7 @@
// Reset sort
var state = myDataTable.getState();
- state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
+ state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
// Get filtered data
myDataSource.sendRequest(YUD.get('q_filter').value,{
@@ -123,7 +126,11 @@
};
YUE.on('q_filter','click',function(){
- YUD.get('q_filter').value = '';
+ if(!YUD.hasClass('q_filter', 'loaded')){
+ YUD.get('q_filter').value = '';
+ //TODO: load here full list later to do search within groups
+ YUD.addClass('q_filter', 'loaded');
+ }
});
YUE.on('q_filter','keyup',function (e) {
diff --git a/rhodecode/templates/admin/users/user_edit.html b/rhodecode/templates/admin/users/user_edit.html
index 0b8e1fb7..71bd3a3a 100644
--- a/rhodecode/templates/admin/users/user_edit.html
+++ b/rhodecode/templates/admin/users/user_edit.html
@@ -43,7 +43,11 @@
<label>${_('API key')}</label> ${c.user.api_key}
</div>
</div>
-
+ <div class="field">
+ <div class="label">
+ <label>${_('Your IP')}</label> ${c.perm_user.ip_addr or "?"}
+ </div>
+ </div>
<div class="fields">
<div class="field">
<div class="label">
@@ -271,7 +275,7 @@
<div class="fields">
<div class="field">
<div class="label">
- <label for="email">${_('New email address')}:</label>
+ <label for="new_email">${_('New email address')}:</label>
</div>
<div class="input">
${h.text('new_email', class_='medium')}
@@ -285,4 +289,52 @@
</div>
${h.end_form()}
</div>
+<div class="box box-left" style="clear:left">
+ <!-- box / title -->
+ <div class="title">
+ <h5>${_('Allowed IP addresses')}</h5>
+ </div>
+
+ <div class="ips_wrap">
+ <table class="noborder">
+ %if c.user_ip_map:
+ %for ip in c.user_ip_map:
+ <tr>
+ <td><div class="ip">${ip.ip_addr}</div></td>
+ <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
+ <td>
+ ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')}
+ ${h.hidden('del_ip',ip.ip_id)}
+ ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
+ class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
+ ${h.end_form()}
+ </td>
+ </tr>
+ %endfor
+ %else:
+ <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
+ %endif
+ </table>
+ </div>
+
+ ${h.form(url('user_ips', id=c.user.user_id),method='put')}
+ <div class="form">
+ <!-- fields -->
+ <div class="fields">
+ <div class="field">
+ <div class="label">
+ <label for="new_ip">${_('New ip address')}:</label>
+ </div>
+ <div class="input">
+ ${h.text('new_ip', class_='medium')}
+ </div>
+ </div>
+ <div class="buttons">
+ ${h.submit('save',_('Add'),class_="ui-btn large")}
+ ${h.reset('reset',_('Reset'),class_="ui-btn large")}
+ </div>
+ </div>
+ </div>
+ ${h.end_form()}
+</div>
</%def>
diff --git a/rhodecode/templates/admin/users/user_edit_my_account.html b/rhodecode/templates/admin/users/user_edit_my_account.html
index 9d341ede..868b0158 100644
--- a/rhodecode/templates/admin/users/user_edit_my_account.html
+++ b/rhodecode/templates/admin/users/user_edit_my_account.html
@@ -48,7 +48,7 @@
</ul>
</div>
<!-- end box / title -->
- <div id="perms" class="table">
+ <div id="perms_container" class="table">
%for section in sorted(c.rhodecode_user.permissions.keys()):
<div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
@@ -94,30 +94,26 @@
</div>
%endfor
</div>
- <div id="my" class="table" style="display:none">
+ <div id="my_container" style="display:none">
+ <div class="table yui-skin-sam" id="repos_list_wrap"></div>
+ <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
+ </div>
+ <div id="pullrequests_container" class="table" style="display:none">
+ ## loaded via AJAX
+ ${_('Loading...')}
</div>
- <div id="pullrequests" class="table" style="display:none"></div>
</div>
-
-
<script type="text/javascript">
-var filter_activate = function(){
- var nodes = YUQ('#my tr td a.repo_name');
- var func = function(node){
- return node.parentNode.parentNode.parentNode.parentNode;
- }
- q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
-}
var show_perms = function(e){
YUD.addClass('show_perms', 'current');
YUD.removeClass('show_my','current');
YUD.removeClass('show_pullrequests','current');
- YUD.setStyle('my','display','none');
- YUD.setStyle('pullrequests','display','none');
- YUD.setStyle('perms','display','');
+ YUD.setStyle('my_container','display','none');
+ YUD.setStyle('pullrequests_container','display','none');
+ YUD.setStyle('perms_container','display','');
YUD.setStyle('q_filter','display','none');
}
YUE.on('show_perms','click',function(e){
@@ -129,17 +125,14 @@ var show_my = function(e){
YUD.removeClass('show_perms','current');
YUD.removeClass('show_pullrequests','current');
- YUD.setStyle('perms','display','none');
- YUD.setStyle('pullrequests','display','none');
- YUD.setStyle('my','display','');
+ YUD.setStyle('perms_container','display','none');
+ YUD.setStyle('pullrequests_container','display','none');
+ YUD.setStyle('my_container','display','');
YUD.setStyle('q_filter','display','');
-
-
- var url = "${h.url('journal_my_repos')}";
- ypjax(url, 'my', function(){
- table_sort();
- filter_activate();
- });
+ if(!YUD.hasClass('show_my', 'loaded')){
+ table_renderer(${c.data |n});
+ YUD.addClass('show_my', 'loaded');
+ }
}
YUE.on('show_my','click',function(e){
show_my(e);
@@ -150,13 +143,13 @@ var show_pullrequests = function(e){
YUD.removeClass('show_my','current');
YUD.removeClass('show_perms','current');
- YUD.setStyle('my','display','none');
- YUD.setStyle('perms','display','none');
- YUD.setStyle('pullrequests','display','');
+ YUD.setStyle('my_container','display','none');
+ YUD.setStyle('perms_container','display','none');
+ YUD.setStyle('pullrequests_container','display','');
YUD.setStyle('q_filter','display','none');
var url = "${h.url('admin_settings_my_pullrequests')}";
- ypjax(url, 'pullrequests');
+ ypjax(url, 'pullrequests_container');
}
YUE.on('show_pullrequests','click',function(e){
show_pullrequests(e)
@@ -171,75 +164,115 @@ var url = location.href.split('#');
if (url[1]) {
//We have a hash
var tabHash = url[1];
- tabs[tabHash]();
+ var func = tabs[tabHash]
+ if (func){
+ func();
+ }
}
-// main table sorting
-var myColumnDefs = [
- {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
- {key:"name",label:"${_('Name')}",sortable:true,
- sortOptions: { sortFunction: nameSort }},
- {key:"tip",label:"${_('Tip')}",sortable:true,
- sortOptions: { sortFunction: revisionSort }},
- {key:"action1",label:"",sortable:false},
- {key:"action2",label:"",sortable:false},
-];
-
-function table_sort(){
-var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
-myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
-myDataSource.responseSchema = {
- fields: [
- {key:"menu"},
- {key:"name"},
- {key:"tip"},
- {key:"action1"},
- {key:"action2"},
- ]
-};
-var trans_defs = {
- sortedBy:{key:"name",dir:"asc"},
- MSG_SORTASC:"${_('Click to sort ascending')}",
- MSG_SORTDESC:"${_('Click to sort descending')}",
- MSG_EMPTY:"${_('No records found.')}",
- MSG_ERROR:"${_('Data error.')}",
- MSG_LOADING:"${_('Loading...')}",
-}
-var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,trans_defs);
-myDataTable.subscribe('postRenderEvent',function(oArgs) {
- tooltip_activate();
- quick_repo_menu();
- filter_activate();
-});
-
-var permsColumnDefs = [
- {key:"name",label:"${_('Name')}",sortable:true, sortOptions: { sortFunction: permNameSort }},
- {key:"perm",label:"${_('Permission')}",sortable:false,},
-];
-
-// perms repos table
-var myDataSource2 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories"));
-myDataSource2.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
-myDataSource2.responseSchema = {
- fields: [
- {key:"name"},
- {key:"perm"},
- ]
-};
-
-new YAHOO.widget.DataTable("tbl_list_wrap_repositories", permsColumnDefs, myDataSource2, trans_defs);
-
-//perms groups table
-var myDataSource3 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories_groups"));
-myDataSource3.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
-myDataSource3.responseSchema = {
- fields: [
- {key:"name"},
- {key:"perm"},
- ]
-};
-
-new YAHOO.widget.DataTable("tbl_list_wrap_repositories_groups", permsColumnDefs, myDataSource3, trans_defs);
-}
+function table_renderer(data){
+ var myDataSource = new YAHOO.util.DataSource(data);
+ myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
+
+ myDataSource.responseSchema = {
+ resultsList: "records",
+ fields: [
+ {key:"menu"},
+ {key:"raw_name"},
+ {key:"name"},
+ {key:"last_changeset"},
+ {key:"action"},
+ ]
+ };
+ myDataSource.doBeforeCallback = function(req,raw,res,cb) {
+ // This is the filter function
+ var data = res.results || [],
+ filtered = [],
+ i,l;
+
+ if (req) {
+ req = req.toLowerCase();
+ for (i = 0; i<data.length; i++) {
+ var pos = data[i].raw_name.toLowerCase().indexOf(req)
+ if (pos != -1) {
+ filtered.push(data[i]);
+ }
+ }
+ res.results = filtered;
+ }
+ return res;
+ }
+
+ // main table sorting
+ var myColumnDefs = [
+ {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
+ {key:"name",label:"${_('Name')}",sortable:true,
+ sortOptions: { sortFunction: nameSort }},
+ {key:"last_changeset",label:"${_('Tip')}",sortable:true,
+ sortOptions: { sortFunction: revisionSort }},
+ {key:"action",label:"${_('Action')}",sortable:false},
+ ];
+
+ var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
+ sortedBy:{key:"name",dir:"asc"},
+ paginator: new YAHOO.widget.Paginator({
+ rowsPerPage: 50,
+ alwaysVisible: false,
+ template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
+ pageLinks: 5,
+ containerClass: 'pagination-wh',
+ currentPageClass: 'pager_curpage',
+ pageLinkClass: 'pager_link',
+ nextPageLinkLabel: '&gt;',
+ previousPageLinkLabel: '&lt;',
+ firstPageLinkLabel: '&lt;&lt;',
+ lastPageLinkLabel: '&gt;&gt;',
+ containers:['user-paginator']
+ }),
+
+ MSG_SORTASC:"${_('Click to sort ascending')}",
+ MSG_SORTDESC:"${_('Click to sort descending')}",
+ MSG_EMPTY:"${_('No records found.')}",
+ MSG_ERROR:"${_('Data error.')}",
+ MSG_LOADING:"${_('Loading...')}",
+ }
+ );
+ myDataTable.subscribe('postRenderEvent',function(oArgs) {
+ tooltip_activate();
+ quick_repo_menu();
+ });
+
+ var filterTimeout = null;
+
+ updateFilter = function() {
+ // Reset timeout
+ filterTimeout = null;
+
+ // Reset sort
+ var state = myDataTable.getState();
+ state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
+
+ // Get filtered data
+ myDataSource.sendRequest(YUD.get('q_filter').value,{
+ success : myDataTable.onDataReturnInitializeTable,
+ failure : myDataTable.onDataReturnInitializeTable,
+ scope : myDataTable,
+ argument: state
+ });
+
+ };
+ YUE.on('q_filter','click',function(){
+ if(!YUD.hasClass('q_filter', 'loaded')){
+ YUD.get('q_filter').value = '';
+ //TODO: load here full list later to do search within groups
+ YUD.addClass('q_filter', 'loaded');
+ }
+ });
+
+ YUE.on('q_filter','keyup',function (e) {
+ clearTimeout(filterTimeout);
+ filterTimeout = setTimeout(updateFilter,600);
+ });
+ }
</script>
</%def>
diff --git a/rhodecode/templates/admin/users/user_edit_my_account_form.html b/rhodecode/templates/admin/users/user_edit_my_account_form.html
index 4620fd9e..a2fabd77 100644
--- a/rhodecode/templates/admin/users/user_edit_my_account_form.html
+++ b/rhodecode/templates/admin/users/user_edit_my_account_form.html
@@ -26,7 +26,11 @@
<label for="username">${_('Username')}:</label>
</div>
<div class="input">
- ${h.text('username',class_="medium")}
+ %if c.ldap_dn:
+ ${h.text('username',class_='medium disabled', readonly="readonly")}
+ %else:
+ ${h.text('username',class_='medium')}
+ %endif:
</div>
</div>
diff --git a/rhodecode/templates/admin/users/user_edit_my_account_repos.html b/rhodecode/templates/admin/users/user_edit_my_account_repos.html
index 42b25edf..e69de29b 100644
--- a/rhodecode/templates/admin/users/user_edit_my_account_repos.html
+++ b/rhodecode/templates/admin/users/user_edit_my_account_repos.html
@@ -1,46 +0,0 @@
-<div id='repos_list_wrap' class="yui-skin-sam">
- <table id="repos_list">
- <thead>
- <tr>
- <th></th>
- <th class="left">${_('Name')}</th>
- <th class="left">${_('Revision')}</th>
- <th class="left">${_('Action')}</th>
- <th class="left">${_('Action')}</th>
- </thead>
- <tbody>
- <%namespace name="dt" file="/data_table/_dt_elements.html"/>
- %if c.user_repos:
- %for repo in c.user_repos:
- <tr>
- ##QUICK MENU
- <td class="quick_repo_menu">
- ${dt.quick_menu(repo['name'])}
- </td>
- ##REPO NAME AND ICONS
- <td class="reponame">
- ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],h.AttributeDict(repo['dbrepo_fork']))}
- </td>
- ##LAST REVISION
- <td>
- ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
- </td>
- <td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url('/images/icons/application_form_edit.png')}"/></a></td>
- <td>
- ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
- ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo['name']+"');")}
- ${h.end_form()}
- </td>
- </tr>
- %endfor
- %else:
- <div style="padding:5px 0px 10px 0px;">
- ${_('No repositories yet')}
- %if h.HasPermissionAny('hg.admin','hg.create.repository')():
- ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-btn")}
- %endif
- </div>
- %endif
- </tbody>
- </table>
-</div>
diff --git a/rhodecode/templates/base/root.html b/rhodecode/templates/base/root.html
index e207b4ed..0753de49 100644
--- a/rhodecode/templates/base/root.html
+++ b/rhodecode/templates/base/root.html
@@ -54,6 +54,7 @@
};
var _TM = TRANSLATION_MAP;
var TOGGLE_FOLLOW_URL = "${h.url('toggle_following')}";
+ var LAZY_CS_URL = "${h.url('changeset_info', repo_name='__NAME__', revision='__REV__')}"
</script>
<script type="text/javascript" src="${h.url('/js/yui.2.9.js', ver=c.rhodecode_version)}"></script>
<!--[if lt IE 9]>
@@ -74,7 +75,7 @@
return false;
}
})(window);
-
+
YUE.onDOMReady(function(){
tooltip_activate();
show_more_event();
@@ -83,7 +84,7 @@
YUE.on('quick_login_link','click',function(e){
// make sure we don't redirect
YUE.preventDefault(e);
-
+
if(YUD.hasClass('quick_login_link','enabled')){
YUD.setStyle('quick_login','display','none');
YUD.removeClass('quick_login_link','enabled');
diff --git a/rhodecode/templates/changelog/changelog.html b/rhodecode/templates/changelog/changelog.html
index e6e66625..2e7ad1fa 100644
--- a/rhodecode/templates/changelog/changelog.html
+++ b/rhodecode/templates/changelog/changelog.html
@@ -149,6 +149,7 @@ ${_('%s Changelog') % c.repo_name} - ${c.rhodecode_name}
//ranges
var checkboxes = YUD.getElementsByClassName('changeset_range');
var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
+ var pr_tmpl = "${h.url('pullrequest_home',repo_name=c.repo_name)}";
YUE.on(checkboxes,'click',function(e){
var clicked_cb = e.currentTarget;
var checked_checkboxes = [];
@@ -203,7 +204,7 @@ ${_('%s Changelog') % c.repo_name} - ${c.rhodecode_name}
YUD.setStyle('rev_range_container','display','');
YUD.setStyle('rev_range_clear','display','');
- YUD.get('open_new_pr').href += '?rev_start={0}&rev_end={1}'.format(rev_start,rev_end);
+ YUD.get('open_new_pr').href = pr_tmpl + '?rev_start={0}&rev_end={1}'.format(rev_start,rev_end);
}
else{
diff --git a/rhodecode/templates/changeset/changeset.html b/rhodecode/templates/changeset/changeset.html
index 37649206..1be9452a 100644
--- a/rhodecode/templates/changeset/changeset.html
+++ b/rhodecode/templates/changeset/changeset.html
@@ -40,7 +40,7 @@
%endfor
%else:
<span>${_('No parents')}</span>
- %endif
+ %endif
</div>
<div class="children">
%if c.changeset.children:
@@ -50,10 +50,10 @@
%endfor
%else:
<span>${_('No children')}</span>
- %endif
- </div>
+ %endif
+ </div>
<div class="code-header banner">
-
+
<div class="hash">
r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
</div>
@@ -74,7 +74,7 @@
${c.context_url(request.GET)}
</div>
<div class="comments-number" style="float:right;padding-right:5px">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
- </div>
+ </div>
</div>
<div id="changeset_content">
<div class="container">
diff --git a/rhodecode/templates/changeset/changeset_file_comment.html b/rhodecode/templates/changeset/changeset_file_comment.html
index 50d69136..edbce729 100644
--- a/rhodecode/templates/changeset/changeset_file_comment.html
+++ b/rhodecode/templates/changeset/changeset_file_comment.html
@@ -19,6 +19,11 @@
<div style="float:left;padding:0px 2px 0px 2px"><span style="font-size: 18px;">&rsaquo;</span></div>
<div title="${_('Changeset status')}" class="changeset-status-lbl"> ${co.status_change[0].status_lbl}</div>
<div class="changeset-status-ico"><img src="${h.url(str('/images/icons/flag_status_%s.png' % co.status_change[0].status))}" /></div>
+ <div style="float:left;padding:3px 0px 0px 5px"> <span class="">
+ %if co.pull_request:
+ <a href="${h.url('pullrequest_show',repo_name=co.pull_request.other_repo.repo_name,pull_request_id=co.pull_request.pull_request_id)}">${_('Status from pull request %s') % co.pull_request.pull_request_id}</a>
+ %endif
+ </span> </div>
</div>
%endif
%if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
@@ -130,7 +135,7 @@
<div id="status_block_container" class="status-block" style="display:none">
%for status,lbl in c.changeset_statuses:
<div class="">
- <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == cur_status else ''}" type="radio" class="status_change_radio" name="changeset_status" id="${status}" value="${status}">
+ <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == cur_status else ''}" type="radio" class="status_change_radio" name="changeset_status" id="${status}" value="${status}">
<label for="${status}">${lbl}</label>
</div>
%endfor
diff --git a/rhodecode/templates/compare/compare_diff.html b/rhodecode/templates/compare/compare_diff.html
index fbb478b9..77eb968c 100644
--- a/rhodecode/templates/compare/compare_diff.html
+++ b/rhodecode/templates/compare/compare_diff.html
@@ -2,7 +2,7 @@
<%inherit file="/base/base.html"/>
<%def name="title()">
- ${c.repo_name} ${_('Compare')} ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)}
+ ${c.repo_name} ${_('Compare')} ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -&gt; ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)}
</%def>
<%def name="breadcrumbs_links()">
@@ -28,7 +28,7 @@
<div class="code-header cv">
<h3 class="code-header-title">${_('Compare View')}</h3>
<div>
- ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)} <a href="${c.swap_url}">[swap]</a>
+ ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -&gt; ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)} <a href="${c.swap_url}">[swap]</a>
</div>
</div>
</div>
diff --git a/rhodecode/templates/data_table/_dt_elements.html b/rhodecode/templates/data_table/_dt_elements.html
index 5eee3f83..8ea532a9 100644
--- a/rhodecode/templates/data_table/_dt_elements.html
+++ b/rhodecode/templates/data_table/_dt_elements.html
@@ -2,12 +2,6 @@
## usage:
## <%namespace name="dt" file="/data_table/_dt_elements.html"/>
-<%def name="repo_actions(repo_name)">
- ${h.form(h.url('repo', repo_name=repo_name),method='delete')}
- ${h.submit('remove_%s' % repo_name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
- ${h.end_form()}
-</%def>
-
<%def name="quick_menu(repo_name)">
<ul class="menu_items hidden">
<li style="border-top:1px solid #003367;margin-left:18px;padding-left:-99px"></li>
@@ -46,7 +40,7 @@
</ul>
</%def>
-<%def name="repo_name(name,rtype,private,fork_of,short_name=False, admin=False)">
+<%def name="repo_name(name,rtype,private,fork_of,short_name=False,admin=False)">
<%
def get_name(name,short_name=short_name):
if short_name:
@@ -116,6 +110,21 @@
<div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(email, size)}"/> </div>
</%def>
+<%def name="repo_actions(repo_name)">
+ <div>
+ <div style="float:left">
+ <a href="${h.url('repo_settings_home',repo_name=repo_name)}" title="${_('edit')}">
+ ${h.submit('edit_%s' % repo_name,_('edit'),class_="edit_icon action_button")}
+ </a>
+ </div>
+ <div style="float:left">
+ ${h.form(h.url('repo', repo_name=repo_name),method='delete')}
+ ${h.submit('remove_%s' % repo_name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
+ ${h.end_form()}
+ </div>
+ </div>
+</%def>
+
<%def name="user_actions(user_id, username)">
${h.form(h.url('delete_user', id=user_id),method='delete')}
${h.submit('remove_',_('delete'),id="remove_user_%s" % user_id,
@@ -126,3 +135,9 @@
<%def name="user_name(user_id, username)">
${h.link_to(username,h.url('edit_user', id=user_id))}
</%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},'${str(h.get_token())}')">
+ </span>
+</%def>
diff --git a/rhodecode/templates/email_templates/pull_request.html b/rhodecode/templates/email_templates/pull_request.html
index ca742b7d..c84ff5dd 100644
--- a/rhodecode/templates/email_templates/pull_request.html
+++ b/rhodecode/templates/email_templates/pull_request.html
@@ -1,9 +1,10 @@
## -*- coding: utf-8 -*-
<%inherit file="main.html"/>
-${_('User %s opened pull request for repository %s and wants you to review changes.') % ('<b>%s</b>' % pr_user_created,pr_repo_url)}
+${_('User %s opened pull request for repository %s and wants you to review changes.') % (('<b>%s</b>' % pr_user_created),pr_repo_url) |n}
<div>${_('title')}: ${pr_title}</div>
<div>${_('description')}:</div>
+<div>${_('View this pull request here')}: ${pr_url}</div>
<p>
${body}
</p>
@@ -14,5 +15,3 @@ ${body}
<li>${r}</li>
%endfor
</ul>
-
-${_('View this pull request here')}: ${pr_url}
diff --git a/rhodecode/templates/files/files_source.html b/rhodecode/templates/files/files_source.html
index d63f29e1..c48a3e6f 100644
--- a/rhodecode/templates/files/files_source.html
+++ b/rhodecode/templates/files/files_source.html
@@ -86,15 +86,6 @@ YUE.onDOMReady(function(){
}
highlight_lines(h_lines);
- //remember original location
- var old_hash = location.href.substring(location.href.indexOf('#'));
-
- // this makes a jump to anchor moved by 3 posstions for padding
- window.location.hash = '#L'+Math.max(parseInt(h_lines[0])-3,1);
-
- //sets old anchor
- window.location.hash = old_hash;
-
}
// select code link event
diff --git a/rhodecode/templates/index_base.html b/rhodecode/templates/index_base.html
index 38ff3d10..c97e3458 100644
--- a/rhodecode/templates/index_base.html
+++ b/rhodecode/templates/index_base.html
@@ -51,9 +51,9 @@
##<td><b>${gr.repositories_recursive_count}</b></td>
</tr>
% endfor
-
</table>
</div>
+ <div id="group-user-paginator" style="padding: 0px 0px 0px 0px"></div>
<div style="height: 20px"></div>
% endif
<div id="welcome" style="display:none;text-align:center">
@@ -127,9 +127,6 @@
% if c.visual.lightweight_dashboard is False:
<script>
YUD.get('repo_count').innerHTML = ${cnt+1 if cnt else 0};
- var func = function(node){
- return node.parentNode.parentNode.parentNode.parentNode;
- }
// groups table sorting
var myColumnDefs = [
@@ -151,7 +148,7 @@
var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,{
sortedBy:{key:"name",dir:"asc"},
paginator: new YAHOO.widget.Paginator({
- rowsPerPage: 5,
+ rowsPerPage: 50,
alwaysVisible: false,
template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
pageLinks: 5,
@@ -162,7 +159,7 @@
previousPageLinkLabel: '&lt;',
firstPageLinkLabel: '&lt;&lt;',
lastPageLinkLabel: '&gt;&gt;',
- containers:['user-paginator']
+ containers:['group-user-paginator']
}),
MSG_SORTASC:"${_('Click to sort ascending')}",
MSG_SORTDESC:"${_('Click to sort descending')}"
@@ -214,13 +211,15 @@
myDataTable.subscribe('postRenderEvent',function(oArgs) {
tooltip_activate();
quick_repo_menu();
+ var func = function(node){
+ return node.parentNode.parentNode.parentNode.parentNode;
+ }
q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
});
</script>
% else:
<script>
- //var url = "${h.url('formatted_users', format='json')}";
var data = ${c.data|n};
var myDataSource = new YAHOO.util.DataSource(data);
myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
@@ -233,6 +232,7 @@
{key:"name"},
{key:"desc"},
{key:"last_change"},
+ {key:"last_changeset"},
{key:"owner"},
{key:"rss"},
{key:"atom"},
@@ -266,6 +266,8 @@
{key:"desc",label:"${_('Description')}",sortable:true},
{key:"last_change",label:"${_('Last Change')}",sortable:true,
sortOptions: { sortFunction: ageSort }},
+ {key:"last_changeset",label:"${_('Tip')}",sortable:true,
+ sortOptions: { sortFunction: revisionSort }},
{key:"owner",label:"${_('Owner')}",sortable:true},
{key:"rss",label:"",sortable:false},
{key:"atom",label:"",sortable:false},
@@ -308,7 +310,7 @@
// Reset sort
var state = myDataTable.getState();
- state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
+ state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
// Get filtered data
myDataSource.sendRequest(YUD.get('q_filter').value,{
@@ -320,7 +322,11 @@
};
YUE.on('q_filter','click',function(){
- YUD.get('q_filter').value = '';
+ if(!YUD.hasClass('q_filter', 'loaded')){
+ YUD.get('q_filter').value = '';
+ //TODO: load here full list later to do search within groups
+ YUD.addClass('q_filter', 'loaded');
+ }
});
YUE.on('q_filter','keyup',function (e) {
diff --git a/rhodecode/templates/journal/journal.html b/rhodecode/templates/journal/journal.html
index 0d41db06..664a7d18 100644
--- a/rhodecode/templates/journal/journal.html
+++ b/rhodecode/templates/journal/journal.html
@@ -43,78 +43,41 @@
</div>
<div class="box box-right">
<!-- box / title -->
+
<div class="title">
<h5>
- <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
- <a id="show_watched" class="link-white" href="#watched">${_('Watched')}</a> / <a id="show_my" class="link-white" href="#my">${_('My repos')}</a>
+ <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}" style="display: none"/>
+ <input class="q_filter_box" id="q_filter_watched" size="15" type="text" name="filter" value="${_('quick filter...')}" style="display: none"/>
</h5>
- %if h.HasPermissionAny('hg.admin','hg.create.repository')():
- <ul class="links">
+ <ul class="links" style="color:#DADADA">
+ <li>
+ <span><a id="show_watched" class="link-white current" href="#watched">${_('Watched')}</a> </span>
+ </li>
<li>
- <span>${h.link_to(_('ADD'),h.url('admin_settings_create_repository'))}</span>
+ <span><a id="show_my" class="link-white" href="#my">${_('My repos')}</a> </span>
</li>
+ %if h.HasPermissionAny('hg.admin','hg.create.repository')():
+ <li>
+ <span>${h.link_to(_('Add repo'),h.url('admin_settings_create_repository'))}</span>
+ </li>
+ %endif
</ul>
- %endif
</div>
+
<!-- end box / title -->
- <div id="my" class="table" style="display:none">
- ## loaded via AJAX
- ${_('Loading...')}
+ <div id="my_container" style="display:none">
+ <div class="table yui-skin-sam" id="repos_list_wrap"></div>
+ <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
</div>
- <div id="watched" class="table">
- %if c.following:
- <table>
- <thead>
- <tr>
- <th class="left">${_('Name')}</th>
- </thead>
- <tbody>
- %for entry in c.following:
- <tr>
- <td>
- %if entry.follows_user_id:
- <img title="${_('following user')}" alt="${_('user')}" src="${h.url('/images/icons/user.png')}"/>
- ${entry.follows_user.full_contact}
- %endif
-
- %if entry.follows_repo_id:
- <div style="float:right;padding-right:5px">
- <span id="follow_toggle_${entry.follows_repository.repo_id}" class="following" title="${_('Stop following this repository')}"
- onclick="javascript:toggleFollowingRepo(this,${entry.follows_repository.repo_id},'${str(h.get_token())}')">
- </span>
- </div>
-
- %if h.is_hg(entry.follows_repository):
- <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
- %elif h.is_git(entry.follows_repository):
- <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
- %endif
-
- %if entry.follows_repository.private and c.visual.show_private_icon:
- <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
- %elif not entry.follows_repository.private and c.visual.show_public_icon:
- <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
- %endif
- <span class="watched_repo">
- ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',repo_name=entry.follows_repository.repo_name))}
- </span>
- %endif
- </td>
- </tr>
- %endfor
- </tbody>
- </table>
- %else:
- <div style="padding:5px 0px 10px 0px;">
- ${_('You are not following any users or repositories')}
- </div>
- %endif
+ <div id="watched_container">
+ <div class="table yui-skin-sam" id="watched_repos_list_wrap"></div>
+ <div id="watched-user-paginator" style="padding: 0px 0px 0px 20px"></div>
</div>
</div>
<script type="text/javascript">
-
+
YUE.on('j_filter','click',function(){
var jfilter = YUD.get('j_filter');
if(YUD.hasClass(jfilter, 'initial')){
@@ -132,31 +95,49 @@
var val = YUD.get('j_filter').value;
window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
});
- fix_j_filter_width(YUD.get('j_filter').value.length);
-
- var show_my = function(e){
- YUD.setStyle('watched','display','none');
- YUD.setStyle('my','display','');
+ fix_j_filter_width(YUD.get('j_filter').value.length);
- var url = "${h.url('admin_settings_my_repos')}";
- ypjax(url, 'my', function(){
+ YUE.on('refresh','click',function(e){
+ ypjax("${h.url.current(filter=c.search_term)}","journal",function(){
+ show_more_event();
tooltip_activate();
- quick_repo_menu();
- var nodes = YUQ('#my tr td a.repo_name');
- var func = function(node){
- return node.parentNode.parentNode.parentNode;
- }
- q_filter('q_filter',nodes,func);
- });
+ show_changeset_tooltip();
+ });
+ YUE.preventDefault(e);
+ });
+
+ var show_my = function(e){
+ YUD.setStyle('watched_container','display','none');
+ YUD.setStyle('my_container','display','');
+ YUD.setStyle('q_filter','display','');
+ YUD.setStyle('q_filter_watched','display','none');
+ YUD.addClass('show_my', 'current');
+ YUD.removeClass('show_watched','current');
+
+ if(!YUD.hasClass('show_my', 'loaded')){
+ table_renderer(${c.data |n});
+ YUD.addClass('show_my', 'loaded');
+ }
}
YUE.on('show_my','click',function(e){
show_my(e);
})
var show_watched = function(e){
- YUD.setStyle('my','display','none');
- YUD.setStyle('watched','display','');
- var nodes = YUQ('#watched .watched_repo a');
+ YUD.setStyle('my_container','display','none');
+ YUD.setStyle('watched_container','display','');
+ YUD.setStyle('q_filter_watched','display','');
+ YUD.setStyle('q_filter','display','none');
+
+ YUD.addClass('show_watched', 'current');
+ YUD.removeClass('show_my','current');
+ if(!YUD.hasClass('show_watched', 'loaded')){
+ watched_renderer(${c.watched_data |n});
+ YUD.addClass('show_watched', 'loaded');
+ }
+
+ return
+ var nodes = YUQ('#watched_container .watched_repo a');
var target = 'q_filter';
var func = function(node){
return node.parentNode.parentNode;
@@ -177,62 +158,218 @@
if (url[1]) {
//We have a hash
var tabHash = url[1];
- tabs[tabHash]();
+ var func = tabs[tabHash]
+ if (func){
+ func();
+ }
}
+ function watched_renderer(data){
+ var myDataSource = new YAHOO.util.DataSource(data);
+ myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
- YUE.on('refresh','click',function(e){
- ypjax("${h.url.current(filter=c.search_term)}","journal",function(){
- show_more_event();
- tooltip_activate();
- show_changeset_tooltip();
- });
- YUE.preventDefault(e);
- });
+ myDataSource.responseSchema = {
+ resultsList: "records",
+ fields: [
+ {key:"menu"},
+ {key:"raw_name"},
+ {key:"name"},
+ {key:"last_changeset"},
+ {key:"action"},
+ ]
+ };
+ myDataSource.doBeforeCallback = function(req,raw,res,cb) {
+ // This is the filter function
+ var data = res.results || [],
+ filtered = [],
+ i,l;
+ if (req) {
+ req = req.toLowerCase();
+ for (i = 0; i<data.length; i++) {
+ var pos = data[i].raw_name.toLowerCase().indexOf(req)
+ if (pos != -1) {
+ filtered.push(data[i]);
+ }
+ }
+ res.results = filtered;
+ }
+ return res;
+ }
+ // main table sorting
+ var myColumnDefs = [
+ {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
+ {key:"name",label:"${_('Name')}",sortable:true,
+ sortOptions: { sortFunction: nameSort }},
+ {key:"last_changeset",label:"${_('Tip')}",sortable:true,
+ sortOptions: { sortFunction: revisionSort }},
+ {key:"action",label:"${_('Action')}",sortable:false},
+ ];
- // main table sorting
- var myColumnDefs = [
- {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
- {key:"name",label:"${_('Name')}",sortable:true,
- sortOptions: { sortFunction: nameSort }},
- {key:"tip",label:"${_('Tip')}",sortable:true,
- sortOptions: { sortFunction: revisionSort }},
- {key:"action1",label:"",sortable:false},
- {key:"action2",label:"",sortable:false},
- ];
-
- var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
-
- myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
-
- myDataSource.responseSchema = {
- fields: [
- {key:"menu"},
- {key:"name"},
- {key:"tip"},
- {key:"action1"},
- {key:"action2"}
- ]
- };
-
- var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
- {
- sortedBy:{key:"name",dir:"asc"},
- MSG_SORTASC:"${_('Click to sort ascending')}",
- MSG_SORTDESC:"${_('Click to sort descending')}",
- MSG_EMPTY:"${_('No records found.')}",
- MSG_ERROR:"${_('Data error.')}",
- MSG_LOADING:"${_('Loading...')}",
+ var myDataTable = new YAHOO.widget.DataTable("watched_repos_list_wrap", myColumnDefs, myDataSource,{
+ sortedBy:{key:"name",dir:"asc"},
+ paginator: new YAHOO.widget.Paginator({
+ rowsPerPage: 25,
+ alwaysVisible: false,
+ template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
+ pageLinks: 5,
+ containerClass: 'pagination-wh',
+ currentPageClass: 'pager_curpage',
+ pageLinkClass: 'pager_link',
+ nextPageLinkLabel: '&gt;',
+ previousPageLinkLabel: '&lt;',
+ firstPageLinkLabel: '&lt;&lt;',
+ lastPageLinkLabel: '&gt;&gt;',
+ containers:['watched-user-paginator']
+ }),
+
+ MSG_SORTASC:"${_('Click to sort ascending')}",
+ MSG_SORTDESC:"${_('Click to sort descending')}",
+ MSG_EMPTY:"${_('No records found.')}",
+ MSG_ERROR:"${_('Data error.')}",
+ MSG_LOADING:"${_('Loading...')}",
+ }
+ );
+ myDataTable.subscribe('postRenderEvent',function(oArgs) {
+ tooltip_activate();
+ quick_repo_menu();
+ });
+
+ var filterTimeout = null;
+
+ updateFilter = function () {
+ // Reset timeout
+ filterTimeout = null;
+
+ // Reset sort
+ var state = myDataTable.getState();
+ state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
+
+ // Get filtered data
+ myDataSource.sendRequest(YUD.get('q_filter_watched').value,{
+ success : myDataTable.onDataReturnInitializeTable,
+ failure : myDataTable.onDataReturnInitializeTable,
+ scope : myDataTable,
+ argument: state
+ });
+
+ };
+ YUE.on('q_filter_watched','click',function(){
+ if(!YUD.hasClass('q_filter_watched', 'loaded')){
+ YUD.get('q_filter_watched').value = '';
+ //TODO: load here full list later to do search within groups
+ YUD.addClass('q_filter_watched', 'loaded');
}
- );
- myDataTable.subscribe('postRenderEvent',function(oArgs) {
- tooltip_activate();
- quick_repo_menu();
- var func = function(node){
- return node.parentNode.parentNode.parentNode.parentNode;
+ });
+
+ YUE.on('q_filter_watched','keyup',function (e) {
+ clearTimeout(filterTimeout);
+ filterTimeout = setTimeout(updateFilter,600);
+ });
+ }
+
+ function table_renderer(data){
+ var myDataSource = new YAHOO.util.DataSource(data);
+ myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
+
+ myDataSource.responseSchema = {
+ resultsList: "records",
+ fields: [
+ {key:"menu"},
+ {key:"raw_name"},
+ {key:"name"},
+ {key:"last_changeset"},
+ {key:"action"},
+ ]
+ };
+ myDataSource.doBeforeCallback = function(req,raw,res,cb) {
+ // This is the filter function
+ var data = res.results || [],
+ filtered = [],
+ i,l;
+
+ if (req) {
+ req = req.toLowerCase();
+ for (i = 0; i<data.length; i++) {
+ var pos = data[i].raw_name.toLowerCase().indexOf(req)
+ if (pos != -1) {
+ filtered.push(data[i]);
+ }
+ }
+ res.results = filtered;
+ }
+ return res;
}
- q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
- });
+ // main table sorting
+ var myColumnDefs = [
+ {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
+ {key:"name",label:"${_('Name')}",sortable:true,
+ sortOptions: { sortFunction: nameSort }},
+ {key:"last_changeset",label:"${_('Tip')}",sortable:true,
+ sortOptions: { sortFunction: revisionSort }},
+ {key:"action",label:"${_('Action')}",sortable:false},
+ ];
+
+ var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
+ sortedBy:{key:"name",dir:"asc"},
+ paginator: new YAHOO.widget.Paginator({
+ rowsPerPage: 25,
+ alwaysVisible: false,
+ template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
+ pageLinks: 5,
+ containerClass: 'pagination-wh',
+ currentPageClass: 'pager_curpage',
+ pageLinkClass: 'pager_link',
+ nextPageLinkLabel: '&gt;',
+ previousPageLinkLabel: '&lt;',
+ firstPageLinkLabel: '&lt;&lt;',
+ lastPageLinkLabel: '&gt;&gt;',
+ containers:['user-paginator']
+ }),
+
+ MSG_SORTASC:"${_('Click to sort ascending')}",
+ MSG_SORTDESC:"${_('Click to sort descending')}",
+ MSG_EMPTY:"${_('No records found.')}",
+ MSG_ERROR:"${_('Data error.')}",
+ MSG_LOADING:"${_('Loading...')}",
+ }
+ );
+ myDataTable.subscribe('postRenderEvent',function(oArgs) {
+ tooltip_activate();
+ quick_repo_menu();
+ });
+
+ var filterTimeout = null;
+
+ updateFilter = function () {
+ // Reset timeout
+ filterTimeout = null;
+
+ // Reset sort
+ var state = myDataTable.getState();
+ state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
+
+ // Get filtered data
+ myDataSource.sendRequest(YUD.get('q_filter').value,{
+ success : myDataTable.onDataReturnInitializeTable,
+ failure : myDataTable.onDataReturnInitializeTable,
+ scope : myDataTable,
+ argument: state
+ });
+
+ };
+ YUE.on('q_filter','click',function(){
+ if(!YUD.hasClass('q_filter', 'loaded')){
+ YUD.get('q_filter').value = '';
+ //TODO: load here full list later to do search within groups
+ YUD.addClass('q_filter', 'loaded');
+ }
+ });
+
+ YUE.on('q_filter','keyup',function (e) {
+ clearTimeout(filterTimeout);
+ filterTimeout = setTimeout(updateFilter,600);
+ });
+ }
</script>
</%def>
diff --git a/rhodecode/templates/journal/journal_page_repos.html b/rhodecode/templates/journal/journal_page_repos.html
deleted file mode 100644
index 35ed8ec0..00000000
--- a/rhodecode/templates/journal/journal_page_repos.html
+++ /dev/null
@@ -1,47 +0,0 @@
-%if c.user_repos:
-<div id='repos_list_wrap' class="yui-skin-sam">
-<table id="repos_list">
- <thead>
- <tr>
- <th></th>
- <th class="left">${_('Name')}</th>
- <th class="left">${_('Revision')}</th>
- <th class="left">${_('Action')}</th>
- <th class="left">${_('Action')}</th>
- </thead>
- <tbody>
- <%namespace name="dt" file="/data_table/_dt_elements.html"/>
- %for repo in c.user_repos:
- <tr>
- ##QUICK MENU
- <td class="quick_repo_menu">
- ${dt.quick_menu(repo['name'])}
- </td>
- ##REPO NAME AND ICONS
- <td class="reponame">
- ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],h.AttributeDict(repo['dbrepo_fork']))}
- </td>
- ##LAST REVISION
- <td>
- ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
- </td>
- ##
- <td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url('/images/icons/application_form_edit.png')}"/></a></td>
- <td>
- ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
- ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo['name']+"');")}
- ${h.end_form()}
- </td>
- </tr>
- %endfor
- </tbody>
- </table>
- </div>
- %else:
- <div style="padding:5px 0px 10px 0px;">
- ${_('No repositories yet')}
- %if h.HasPermissionAny('hg.admin','hg.create.repository')():
- ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-btn")}
- %endif
- </div>
- %endif
diff --git a/rhodecode/templates/pullrequests/pullrequest_show.html b/rhodecode/templates/pullrequests/pullrequest_show.html
index 2388b6dc..2257528b 100644
--- a/rhodecode/templates/pullrequests/pullrequest_show.html
+++ b/rhodecode/templates/pullrequests/pullrequest_show.html
@@ -51,6 +51,24 @@
%endif
</div>
</div>
+ <div class="field">
+ <div class="label-summary">
+ <label>${_('Origin repository')}:</label>
+ </div>
+ <div class="input">
+ <div>
+ ##%if h.is_hg(c.pull_request.org_repo):
+ ## <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
+ ##%elif h.is_git(c.pull_request.org_repo):
+ ## <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
+ ##%endif
+ <span class="spantag">${c.pull_request.org_ref_parts[0]}</span>
+ :
+ <span class="spantag">${c.pull_request.org_ref_parts[1]}</span>
+ <span><a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name)}">${c.pull_request.org_repo.clone_url()}</a></span>
+ </div>
+ </div>
+ </div>
</div>
</div>
<div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
@@ -198,7 +216,7 @@
// inject comments into they proper positions
var file_comments = YUQ('.inline-comment-placeholder');
renderInlineComments(file_comments);
-
+
YUE.on(YUD.get('update_pull_request'),'click',function(e){
updateReviewers();
})
diff --git a/rhodecode/templates/settings/repo_settings.html b/rhodecode/templates/settings/repo_settings.html
index 37ca4cf7..1b61be27 100644
--- a/rhodecode/templates/settings/repo_settings.html
+++ b/rhodecode/templates/settings/repo_settings.html
@@ -94,7 +94,7 @@
${h.submit('save',_('Save'),class_="ui-btn large")}
${h.reset('reset',_('Reset'),class_="ui-btn large")}
</div>
-
+
</div>
${h.end_form()}
</div>
diff --git a/rhodecode/tests/api/api_base.py b/rhodecode/tests/api/api_base.py
index 7ada1a9b..5e290582 100644
--- a/rhodecode/tests/api/api_base.py
+++ b/rhodecode/tests/api/api_base.py
@@ -59,13 +59,13 @@ def destroy_users_group(name=TEST_USERS_GROUP):
Session().commit()
-def create_repo(repo_name, repo_type):
+def create_repo(repo_name, repo_type, owner=None):
# create new repo
form_data = _get_repo_create_params(
repo_name_full=repo_name,
repo_description='description %s' % repo_name,
)
- cur_user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
+ cur_user = UserModel().get_by_username(owner or TEST_USER_ADMIN_LOGIN)
r = RepoModel().create(form_data, cur_user)
Session().commit()
return r
@@ -93,7 +93,7 @@ class BaseTestApi(object):
def setUpClass(self):
self.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
self.apikey = self.usr.api_key
- self.TEST_USER = UserModel().create_or_update(
+ self.test_user = UserModel().create_or_update(
username='test-api',
password='test',
email='test@api.rhodecode.org',
@@ -101,7 +101,8 @@ class BaseTestApi(object):
lastname='last'
)
Session().commit()
- self.TEST_USER_LOGIN = self.TEST_USER.username
+ self.TEST_USER_LOGIN = self.test_user.username
+ self.apikey_regular = self.test_user.api_key
@classmethod
def teardownClass(self):
@@ -148,12 +149,40 @@ class BaseTestApi(object):
self._compare_error(id_, expected, given=response.body)
def test_api_missing_non_optional_param(self):
- id_, params = _build_data(self.apikey, 'get_user')
+ id_, params = _build_data(self.apikey, 'get_repo')
response = api_call(self, params)
- expected = 'Missing non optional `userid` arg in JSON DATA'
+ expected = 'Missing non optional `repoid` arg in JSON DATA'
self._compare_error(id_, expected, given=response.body)
+ def test_api_missing_non_optional_param_args_null(self):
+ id_, params = _build_data(self.apikey, 'get_repo')
+ params = params.replace('"args": {}', '"args": null')
+ response = api_call(self, params)
+
+ expected = 'Missing non optional `repoid` arg in JSON DATA'
+ self._compare_error(id_, expected, given=response.body)
+
+ def test_api_missing_non_optional_param_args_bad(self):
+ id_, params = _build_data(self.apikey, 'get_repo')
+ params = params.replace('"args": {}', '"args": 1')
+ response = api_call(self, params)
+
+ expected = 'Missing non optional `repoid` arg in JSON DATA'
+ self._compare_error(id_, expected, given=response.body)
+
+ def test_api_args_is_null(self):
+ id_, params = _build_data(self.apikey, 'get_users',)
+ params = params.replace('"args": {}', '"args": null')
+ response = api_call(self, params)
+ self.assertEqual(response.status, '200 OK')
+
+ def test_api_args_is_bad(self):
+ id_, params = _build_data(self.apikey, 'get_users',)
+ params = params.replace('"args": {}', '"args": 1')
+ response = api_call(self, params)
+ self.assertEqual(response.status, '200 OK')
+
def test_api_get_users(self):
id_, params = _build_data(self.apikey, 'get_users',)
response = api_call(self, params)
@@ -184,6 +213,36 @@ class BaseTestApi(object):
expected = "user `%s` does not exist" % 'trololo'
self._compare_error(id_, expected, given=response.body)
+ def test_api_get_user_without_giving_userid(self):
+ id_, params = _build_data(self.apikey, 'get_user')
+ response = api_call(self, params)
+
+ usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
+ ret = usr.get_api_data()
+ ret['permissions'] = AuthUser(usr.user_id).permissions
+
+ expected = ret
+ self._compare_ok(id_, expected, given=response.body)
+
+ def test_api_get_user_without_giving_userid_non_admin(self):
+ id_, params = _build_data(self.apikey_regular, 'get_user')
+ response = api_call(self, params)
+
+ usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
+ ret = usr.get_api_data()
+ ret['permissions'] = AuthUser(usr.user_id).permissions
+
+ expected = ret
+ self._compare_ok(id_, expected, given=response.body)
+
+ def test_api_get_user_with_giving_userid_non_admin(self):
+ id_, params = _build_data(self.apikey_regular, 'get_user',
+ userid=self.TEST_USER_LOGIN)
+ response = api_call(self, params)
+
+ expected = 'userid is not the same as your user'
+ self._compare_error(id_, expected, given=response.body)
+
def test_api_pull(self):
#TODO: issues with rhodecode_extras here.. not sure why !
pass
@@ -237,6 +296,42 @@ class BaseTestApi(object):
% (TEST_USER_ADMIN_LOGIN, self.REPO, True))
self._compare_ok(id_, expected, given=response.body)
+ def test_api_lock_repo_lock_aquire_by_non_admin(self):
+ repo_name = 'api_delete_me'
+ create_repo(repo_name, self.REPO_TYPE, owner=self.TEST_USER_LOGIN)
+ try:
+ id_, params = _build_data(self.apikey_regular, 'lock',
+ repoid=repo_name,
+ locked=True)
+ response = api_call(self, params)
+ expected = ('User `%s` set lock state for repo `%s` to `%s`'
+ % (self.TEST_USER_LOGIN, repo_name, True))
+ self._compare_ok(id_, expected, given=response.body)
+ finally:
+ destroy_repo(repo_name)
+
+ def test_api_lock_repo_lock_aquire_non_admin_with_userid(self):
+ repo_name = 'api_delete_me'
+ create_repo(repo_name, self.REPO_TYPE, owner=self.TEST_USER_LOGIN)
+ try:
+ id_, params = _build_data(self.apikey_regular, 'lock',
+ userid=TEST_USER_ADMIN_LOGIN,
+ repoid=repo_name,
+ locked=True)
+ response = api_call(self, params)
+ expected = 'userid is not the same as your user'
+ self._compare_error(id_, expected, given=response.body)
+ finally:
+ destroy_repo(repo_name)
+
+ def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self):
+ id_, params = _build_data(self.apikey_regular, 'lock',
+ repoid=self.REPO,
+ locked=True)
+ response = api_call(self, params)
+ expected = 'repository `%s` does not exist' % (self.REPO)
+ self._compare_error(id_, expected, given=response.body)
+
def test_api_lock_repo_lock_release(self):
id_, params = _build_data(self.apikey, 'lock',
userid=TEST_USER_ADMIN_LOGIN,
@@ -247,6 +342,15 @@ class BaseTestApi(object):
% (TEST_USER_ADMIN_LOGIN, self.REPO, False))
self._compare_ok(id_, expected, given=response.body)
+ def test_api_lock_repo_lock_aquire_optional_userid(self):
+ id_, params = _build_data(self.apikey, 'lock',
+ repoid=self.REPO,
+ locked=True)
+ response = api_call(self, params)
+ expected = ('User `%s` set lock state for repo `%s` to `%s`'
+ % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
+ self._compare_ok(id_, expected, given=response.body)
+
@mock.patch.object(Repository, 'lock', crash)
def test_api_lock_error(self):
id_, params = _build_data(self.apikey, 'lock',
@@ -457,6 +561,48 @@ class BaseTestApi(object):
self._compare_ok(id_, expected, given=response.body)
destroy_users_group(new_group)
+ def test_api_get_repo_by_non_admin(self):
+ id_, params = _build_data(self.apikey, 'get_repo',
+ repoid=self.REPO)
+ response = api_call(self, params)
+
+ repo = RepoModel().get_by_repo_name(self.REPO)
+ ret = repo.get_api_data()
+
+ members = []
+ for user in repo.repo_to_perm:
+ perm = user.permission.permission_name
+ user = user.user
+ user_data = user.get_api_data()
+ user_data['type'] = "user"
+ user_data['permission'] = perm
+ members.append(user_data)
+
+ for users_group in repo.users_group_to_perm:
+ perm = users_group.permission.permission_name
+ users_group = users_group.users_group
+ users_group_data = users_group.get_api_data()
+ users_group_data['type'] = "users_group"
+ users_group_data['permission'] = perm
+ members.append(users_group_data)
+
+ ret['members'] = members
+
+ expected = ret
+ self._compare_ok(id_, expected, given=response.body)
+
+ def test_api_get_repo_by_non_admin_no_permission_to_repo(self):
+ RepoModel().grant_user_permission(repo=self.REPO,
+ user=self.TEST_USER_LOGIN,
+ perm='repository.none')
+
+ id_, params = _build_data(self.apikey_regular, 'get_repo',
+ repoid=self.REPO)
+ response = api_call(self, params)
+
+ expected = 'repository `%s` does not exist' % (self.REPO)
+ self._compare_error(id_, expected, given=response.body)
+
def test_api_get_repo_that_doesn_not_exist(self):
id_, params = _build_data(self.apikey, 'get_repo',
repoid='no-such-repo')
@@ -478,6 +624,18 @@ class BaseTestApi(object):
expected = ret
self._compare_ok(id_, expected, given=response.body)
+ def test_api_get_repos_non_admin(self):
+ id_, params = _build_data(self.apikey_regular, 'get_repos')
+ response = api_call(self, params)
+
+ result = []
+ for repo in RepoModel().get_all_user_repos(self.TEST_USER_LOGIN):
+ result.append(repo.get_api_data())
+ ret = jsonify(result)
+
+ expected = ret
+ self._compare_ok(id_, expected, given=response.body)
+
@parameterized.expand([('all', 'all'),
('dirs', 'dirs'),
('files', 'files'), ])
@@ -560,6 +718,56 @@ class BaseTestApi(object):
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='hg',
+ )
+ response = api_call(self, params)
+
+ repo = RepoModel().get_by_repo_name(repo_name)
+ ret = {
+ 'msg': 'Created new repository `%s`' % repo_name,
+ 'repo': jsonify(repo.get_api_data())
+ }
+ expected = ret
+ self._compare_ok(id_, expected, given=response.body)
+ 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='hg',
+ )
+ response = api_call(self, params)
+
+ repo = RepoModel().get_by_repo_name(repo_name)
+ ret = {
+ 'msg': 'Created new repository `%s`' % repo_name,
+ 'repo': jsonify(repo.get_api_data())
+ }
+ expected = ret
+ self._compare_ok(id_, expected, given=response.body)
+ 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='hg',
+ owner=owner
+ )
+ response = api_call(self, params)
+
+ expected = 'Only RhodeCode admin can specify `owner` param'
+ self._compare_error(id_, expected, given=response.body)
+ destroy_repo(repo_name)
+
def test_api_create_repo_exists(self):
repo_name = self.REPO
id_, params = _build_data(self.apikey, 'create_repo',
@@ -598,6 +806,35 @@ class BaseTestApi(object):
expected = ret
self._compare_ok(id_, expected, given=response.body)
+ def test_api_delete_repo_by_non_admin(self):
+ repo_name = 'api_delete_me'
+ create_repo(repo_name, self.REPO_TYPE, owner=self.TEST_USER_LOGIN)
+ try:
+ 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
+ }
+ expected = ret
+ self._compare_ok(id_, expected, given=response.body)
+ finally:
+ destroy_repo(repo_name)
+
+ def test_api_delete_repo_by_non_admin_no_permission(self):
+ repo_name = 'api_delete_me'
+ create_repo(repo_name, 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:
+ destroy_repo(repo_name)
+
def test_api_delete_repo_exception_occurred(self):
repo_name = 'api_delete_me'
create_repo(repo_name, self.REPO_TYPE)
@@ -630,6 +867,49 @@ class BaseTestApi(object):
self._compare_ok(id_, expected, given=response.body)
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
+ }
+ expected = ret
+ self._compare_ok(id_, expected, given=response.body)
+ 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 RhodeCode admin can specify `owner` param'
+ self._compare_error(id_, expected, given=response.body)
+ 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)
+ destroy_repo(fork_name)
+
def test_api_fork_repo_unknown_owner(self):
fork_name = 'api-repo-fork'
owner = 'i-dont-exist'
diff --git a/rhodecode/tests/functional/test_admin_notifications.py b/rhodecode/tests/functional/test_admin_notifications.py
index 66431a68..05ead3b2 100644
--- a/rhodecode/tests/functional/test_admin_notifications.py
+++ b/rhodecode/tests/functional/test_admin_notifications.py
@@ -82,6 +82,7 @@ class TestNotificationsController(TestController):
response = self.app.delete(url('notification',
notification_id=
notification.notification_id))
+ self.assertEqual(response.body, 'ok')
cur_user = User.get(cur_usr_id)
self.assertEqual(cur_user.notifications, [])
diff --git a/rhodecode/tests/functional/test_compare.py b/rhodecode/tests/functional/test_compare.py
index 20861594..09ba4762 100644
--- a/rhodecode/tests/functional/test_compare.py
+++ b/rhodecode/tests/functional/test_compare.py
@@ -98,7 +98,7 @@ class TestCompareController(TestController):
))
try:
- response.mustcontain('%s@%s -> %s@%s' % (repo2.repo_name, rev1, repo1.repo_name, rev2))
+ response.mustcontain('%s@%s -&gt; %s@%s' % (repo2.repo_name, rev1, repo1.repo_name, rev2))
response.mustcontain("""Showing 2 commits""")
response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
@@ -156,7 +156,7 @@ class TestCompareController(TestController):
))
try:
- response.mustcontain('%s@%s -> %s@%s' % (repo2.repo_name, rev1, repo1.repo_name, rev2))
+ response.mustcontain('%s@%s -&gt; %s@%s' % (repo2.repo_name, rev1, repo1.repo_name, rev2))
response.mustcontain("""Showing 2 commits""")
response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
@@ -191,7 +191,7 @@ class TestCompareController(TestController):
# ))
#
# try:
-# response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
+# response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
# ## outgoing changesets between those revisions
#
# response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_REPO))
@@ -226,7 +226,7 @@ class TestCompareController(TestController):
# ))
#
# try:
-# response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
+# response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
# ## outgoing changesets between those revisions
#
# response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_REPO))
@@ -312,7 +312,7 @@ class TestCompareController(TestController):
# ))
#
# try:
-# #response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
+# #response.mustcontain('%s@%s -&gt; %s@%s' % (r2_name, rev1, r1_name, rev2))
#
# #add new commit into parent !
# cs0 = ScmModel().create_node(
@@ -336,7 +336,7 @@ class TestCompareController(TestController):
# bundle=False
# ))
#
-# response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
+# response.mustcontain('%s@%s -&gt; %s@%s' % (r2_name, rev1, r1_name, rev2))
# response.mustcontain("""file1-line1-from-fork""")
# response.mustcontain("""file2-line1-from-fork""")
# response.mustcontain("""file3-line1-from-fork""")
diff --git a/rhodecode/tests/functional/test_compare_local.py b/rhodecode/tests/functional/test_compare_local.py
index 0ff8d284..349b3dfe 100644
--- a/rhodecode/tests/functional/test_compare_local.py
+++ b/rhodecode/tests/functional/test_compare_local.py
@@ -19,7 +19,7 @@ class TestCompareController(TestController):
other_ref_type="tag",
other_ref=tag2,
))
- response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
+ response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
## outgoing changesets between tags
response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
@@ -56,7 +56,7 @@ class TestCompareController(TestController):
other_ref=tag2,
bundle=False
))
- response.mustcontain('%s@%s -> %s@%s' % (GIT_REPO, tag1, GIT_REPO, tag2))
+ response.mustcontain('%s@%s -&gt; %s@%s' % (GIT_REPO, tag1, GIT_REPO, tag2))
## outgoing changesets between tags
response.mustcontain('''<a href="/%s/changeset/794bbdd31545c199f74912709ea350dedcd189a2">r113:794bbdd31545</a>''' % GIT_REPO)
@@ -92,7 +92,7 @@ class TestCompareController(TestController):
other_ref='default',
))
- response.mustcontain('%s@default -> %s@default' % (HG_REPO, HG_REPO))
+ response.mustcontain('%s@default -&gt; %s@default' % (HG_REPO, HG_REPO))
# branch are equal
response.mustcontain('<span class="empty_data">No files</span>')
response.mustcontain('<span class="empty_data">No changesets</span>')
@@ -107,7 +107,7 @@ class TestCompareController(TestController):
other_ref='master',
))
- response.mustcontain('%s@master -> %s@master' % (GIT_REPO, GIT_REPO))
+ response.mustcontain('%s@master -&gt; %s@master' % (GIT_REPO, GIT_REPO))
# branch are equal
response.mustcontain('<span class="empty_data">No files</span>')
response.mustcontain('<span class="empty_data">No changesets</span>')
@@ -124,7 +124,7 @@ class TestCompareController(TestController):
other_ref_type="rev",
other_ref=rev2,
))
- response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_REPO, rev2))
+ response.mustcontain('%s@%s -&gt; %s@%s' % (HG_REPO, rev1, HG_REPO, rev2))
## outgoing changesets between those revisions
response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev2))
@@ -144,7 +144,7 @@ class TestCompareController(TestController):
other_ref_type="rev",
other_ref=rev2,
))
- response.mustcontain('%s@%s -> %s@%s' % (GIT_REPO, rev1, GIT_REPO, rev2))
+ response.mustcontain('%s@%s -&gt; %s@%s' % (GIT_REPO, rev1, GIT_REPO, rev2))
## outgoing changesets between those revisions
response.mustcontain("""<a href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (GIT_REPO, rev2[:12]))
response.mustcontain('1 file changed with 7 insertions and 0 deletions')
diff --git a/rhodecode/tests/functional/test_home.py b/rhodecode/tests/functional/test_home.py
index a916d560..b23df06c 100644
--- a/rhodecode/tests/functional/test_home.py
+++ b/rhodecode/tests/functional/test_home.py
@@ -3,6 +3,9 @@ from rhodecode.tests import *
from rhodecode.model.meta import Session
from rhodecode.model.db import User, RhodeCodeSetting, Repository
from rhodecode.lib.utils import set_rhodecode_config
+from rhodecode.tests.models.common import _make_repo, _make_group
+from rhodecode.model.repo import RepoModel
+from rhodecode.model.repos_group import ReposGroupModel
class TestHomeController(TestController):
@@ -61,18 +64,45 @@ merge" class="tooltip" href="/vcs_test_hg/changeset/27cd5cce30c96924232"""
Session().add(anon)
Session().commit()
+ def _set_l_dash(self, set_to):
+ self.app.post(url('admin_setting', setting_id='visual'),
+ params=dict(_method='put',
+ rhodecode_lightweight_dashboard=set_to,))
+
def test_index_with_lightweight_dashboard(self):
self.log_user()
-
- def set_l_dash(set_to):
- self.app.post(url('admin_setting', setting_id='visual'),
- params=dict(_method='put',
- rhodecode_lightweight_dashboard=set_to,))
-
- set_l_dash(True)
+ self._set_l_dash(True)
try:
response = self.app.get(url(controller='home', action='index'))
response.mustcontain("""var data = {"totalRecords": %s""" % len(Repository.getAll()))
finally:
- set_l_dash(False)
+ self._set_l_dash(False)
+
+ def test_index_page_on_groups(self):
+ self.log_user()
+ _make_repo(name='gr1/repo_in_group', repos_group=_make_group('gr1'))
+ Session().commit()
+ response = self.app.get(url('repos_group_home', group_name='gr1'))
+
+ try:
+ response.mustcontain("""gr1/repo_in_group""")
+ finally:
+ RepoModel().delete('gr1/repo_in_group')
+ ReposGroupModel().delete(repos_group='gr1', force_delete=True)
+ Session().commit()
+
+ def test_index_page_on_groups_with_lightweight_dashboard(self):
+ self.log_user()
+ self._set_l_dash(True)
+ _make_repo(name='gr1/repo_in_group', repos_group=_make_group('gr1'))
+ Session().commit()
+ response = self.app.get(url('repos_group_home', group_name='gr1'))
+
+ try:
+ response.mustcontain("""gr1/repo_in_group""")
+ finally:
+ self._set_l_dash(False)
+ RepoModel().delete('gr1/repo_in_group')
+ ReposGroupModel().delete(repos_group='gr1', force_delete=True)
+ Session().commit()
diff --git a/rhodecode/tests/functional/test_journal.py b/rhodecode/tests/functional/test_journal.py
index eb24dc29..e22d3a2f 100644
--- a/rhodecode/tests/functional/test_journal.py
+++ b/rhodecode/tests/functional/test_journal.py
@@ -10,10 +10,7 @@ class TestJournalController(TestController):
self.log_user()
response = self.app.get(url(controller='journal', action='index'))
- # Test response...
- assert """ <span id="follow_toggle_1" class="following" title="Stop following this repository""" in response.body, 'no info about stop follwoing repo id 1'
-
- assert """<div class="journal_day">%s</div>""" % datetime.date.today() in response.body, 'no info about action journal day'
+ response.mustcontain("""<div class="journal_day">%s</div>""" % datetime.date.today())
def test_stop_following_repository(self):
session = self.log_user()
diff --git a/rhodecode/tests/models/test_user_permissions_on_repos.py b/rhodecode/tests/models/test_user_permissions_on_repos.py
index bd8841cc..da810542 100644
--- a/rhodecode/tests/models/test_user_permissions_on_repos.py
+++ b/rhodecode/tests/models/test_user_permissions_on_repos.py
@@ -1 +1 @@
-#TODO; write tests when we activate algo for permissions. \ No newline at end of file
+#TODO; write tests when we activate algo for permissions.
diff --git a/rhodecode/tests/scripts/test_vcs_operations.py b/rhodecode/tests/scripts/test_vcs_operations.py
index 2e9ca726..1ff86130 100755
--- a/rhodecode/tests/scripts/test_vcs_operations.py
+++ b/rhodecode/tests/scripts/test_vcs_operations.py
@@ -29,6 +29,7 @@
import os
import tempfile
import unittest
+import time
from os.path import join as jn
from os.path import dirname as dn
@@ -36,9 +37,10 @@ from tempfile import _RandomNameSequence
from subprocess import Popen, PIPE
from rhodecode.tests import *
-from rhodecode.model.db import User, Repository, UserLog
+from rhodecode.model.db import User, Repository, UserLog, UserIpMap
from rhodecode.model.meta import Session
from rhodecode.model.repo import RepoModel
+from rhodecode.model.user import UserModel
DEBUG = True
HOST = '127.0.0.1:5000' # test host
@@ -420,3 +422,49 @@ class TestVCSOperations(unittest.TestCase):
# Session.remove()
# r = Repository.get_by_repo_name(GIT_REPO)
# assert r.locked == [None, None]
+
+ def test_ip_restriction_hg(self):
+ user_model = UserModel()
+ try:
+ user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
+ Session().commit()
+ clone_url = _construct_url(HG_REPO)
+ stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+ assert 'abort: HTTP Error 403: Forbidden' in stderr
+ finally:
+ #release IP restrictions
+ for ip in UserIpMap.getAll():
+ UserIpMap.delete(ip.ip_id)
+ Session().commit()
+
+ time.sleep(2)
+ clone_url = _construct_url(HG_REPO)
+ stdout, stderr = Command('/tmp').execute('hg clone', clone_url)
+
+ assert 'requesting all changes' in stdout
+ assert 'adding changesets' in stdout
+ assert 'adding manifests' in stdout
+ assert 'adding file changes' in stdout
+
+ assert stderr == ''
+
+ def test_ip_restriction_git(self):
+ user_model = UserModel()
+ try:
+ user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
+ Session().commit()
+ clone_url = _construct_url(GIT_REPO)
+ stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+ assert 'error: The requested URL returned error: 403 Forbidden' in stderr
+ finally:
+ #release IP restrictions
+ for ip in UserIpMap.getAll():
+ UserIpMap.delete(ip.ip_id)
+ Session().commit()
+
+ time.sleep(2)
+ clone_url = _construct_url(GIT_REPO)
+ stdout, stderr = Command('/tmp').execute('git clone', clone_url)
+
+ assert 'Cloning into' in stdout
+ assert stderr == ''
diff --git a/rhodecode/tests/test_libs.py b/rhodecode/tests/test_libs.py
index b0482aef..fe541081 100644
--- a/rhodecode/tests/test_libs.py
+++ b/rhodecode/tests/test_libs.py
@@ -123,15 +123,16 @@ class TestLibs(unittest.TestCase):
from rhodecode.lib.utils2 import age
n = datetime.datetime.now()
delt = lambda *args, **kwargs: datetime.timedelta(*args, **kwargs)
+ prev_month = n.month - 1 if n.month != 1 else n.month - 2
self.assertEqual(age(n), u'just now')
self.assertEqual(age(n - delt(seconds=1)), u'1 second ago')
self.assertEqual(age(n - delt(seconds=60 * 2)), u'2 minutes ago')
self.assertEqual(age(n - delt(hours=1)), u'1 hour ago')
self.assertEqual(age(n - delt(hours=24)), u'1 day ago')
self.assertEqual(age(n - delt(hours=24 * 5)), u'5 days ago')
- self.assertEqual(age(n - delt(hours=24 * (calendar.mdays[n.month - 1]))),
+ self.assertEqual(age(n - delt(hours=24 * (calendar.mdays[prev_month]))),
u'1 month ago')
- self.assertEqual(age(n - delt(hours=24 * (calendar.mdays[n.month - 1] + 2))),
+ self.assertEqual(age(n - delt(hours=24 * (calendar.mdays[prev_month] + 2))),
u'1 month and 2 days ago')
self.assertEqual(age(n - delt(hours=24 * 400)), u'1 year and 1 month ago')
diff --git a/setup.py b/setup.py
index a5e7026c..7c79cb17 100644
--- a/setup.py
+++ b/setup.py
@@ -52,9 +52,9 @@ if sys.version_info < (2, 7):
requirements.append("unittest2")
if is_windows:
- requirements.append("mercurial==2.4.1")
+ requirements.append("mercurial==2.4.2")
else:
- requirements.append("mercurial==2.4.1")
+ requirements.append("mercurial==2.4.2")
dependency_links = [
diff --git a/test.ini b/test.ini
index 9af71121..bf7c15e1 100644
--- a/test.ini
+++ b/test.ini
@@ -30,16 +30,16 @@ pdebug = false
[server:main]
##nr of threads to spawn
-#threadpool_workers = 5
+threadpool_workers = 5
##max request before thread respawn
-#threadpool_max_requests = 2
+threadpool_max_requests = 2
##option to use threads of process
-#use_threadpool = true
+use_threadpool = true
-#use = egg:Paste#http
-use = egg:waitress#main
+use = egg:Paste#http
+#use = egg:waitress#main
host = 127.0.0.1
port = 5000
@@ -295,4 +295,4 @@ datefmt = %Y-%m-%d %H:%M:%S
[formatter_color_formatter]
class=rhodecode.lib.colored_formatter.ColorFormatter
format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %Y-%m-%d %H:%M:%S \ No newline at end of file
+datefmt = %Y-%m-%d %H:%M:%S